Fehler beseitigt, Leistungspositonen pro Stundenzettel, Kundenpreis berücksichtigt,
Prototyp Handy ansicht
This commit is contained in:
parent
9627e4fea4
commit
6bfc565121
10 changed files with 1295 additions and 83 deletions
232
card.php
232
card.php
|
|
@ -13,6 +13,7 @@ if (!$res) die("Include of main fails");
|
||||||
|
|
||||||
require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
|
require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
|
||||||
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
|
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
|
||||||
|
require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
|
||||||
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php';
|
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php';
|
||||||
require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
|
require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
|
||||||
dol_include_once('/stundenzettel/class/stundenzettel.class.php');
|
dol_include_once('/stundenzettel/class/stundenzettel.class.php');
|
||||||
|
|
@ -233,8 +234,9 @@ if ($action == 'add_leistung' && $permissiontoadd) {
|
||||||
$time_start = GETPOST('time_start', 'alpha');
|
$time_start = GETPOST('time_start', 'alpha');
|
||||||
$time_end = GETPOST('time_end', 'alpha');
|
$time_end = GETPOST('time_end', 'alpha');
|
||||||
$description = GETPOST('leistung_description', 'restricthtml');
|
$description = GETPOST('leistung_description', 'restricthtml');
|
||||||
|
$fk_product = GETPOST('fk_product', 'int');
|
||||||
|
|
||||||
$result = $object->addLeistung($user, $date, $time_start, $time_end, $description);
|
$result = $object->addLeistung($user, $date, $time_start, $time_end, $description, $fk_product);
|
||||||
if ($result > 0) {
|
if ($result > 0) {
|
||||||
setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
|
setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -259,8 +261,9 @@ if ($action == 'update_leistung' && $permissiontoadd) {
|
||||||
$time_start = GETPOST('time_start', 'alpha');
|
$time_start = GETPOST('time_start', 'alpha');
|
||||||
$time_end = GETPOST('time_end', 'alpha');
|
$time_end = GETPOST('time_end', 'alpha');
|
||||||
$description = GETPOST('leistung_description', 'restricthtml');
|
$description = GETPOST('leistung_description', 'restricthtml');
|
||||||
|
$fk_product = GETPOST('fk_product', 'int');
|
||||||
|
|
||||||
$result = $object->updateLeistung($leistung_id, $date, $time_start, $time_end, $description);
|
$result = $object->updateLeistung($leistung_id, $date, $time_start, $time_end, $description, $fk_product);
|
||||||
if ($result > 0) {
|
if ($result > 0) {
|
||||||
setEventMessages($langs->trans('RecordModified'), null, 'mesgs');
|
setEventMessages($langs->trans('RecordModified'), null, 'mesgs');
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -423,16 +426,31 @@ if ($action == 'add_product' && $permissiontoadd) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Entfällt hinzufügen (Produkt aus Auftrag das nicht verbaut wird)
|
// Entfällt hinzufügen (Produkt aus Auftrag oder Mehraufwand das nicht verbaut wird)
|
||||||
if ($action == 'add_entfaellt' && $permissiontoadd) {
|
if ($action == 'add_entfaellt' && $permissiontoadd) {
|
||||||
$entfaellt_product_raw = GETPOST('entfaellt_product', 'alpha');
|
$entfaellt_product_raw = GETPOST('entfaellt_product', 'alpha');
|
||||||
$qty = GETPOST('entfaellt_qty', 'int');
|
$qty = GETPOST('entfaellt_qty', 'int');
|
||||||
$reason = GETPOST('entfaellt_description', 'restricthtml');
|
$reason = GETPOST('entfaellt_description', 'restricthtml');
|
||||||
|
|
||||||
// Prüfen ob es ein Freitext-Produkt ist (Format: "freetext_ROWID")
|
// Prüfen ob es ein Freitext-Produkt, Mehraufwand oder normales Produkt ist
|
||||||
$fk_product = 0;
|
$fk_product = 0;
|
||||||
$freetext_description = '';
|
$freetext_description = '';
|
||||||
if (strpos($entfaellt_product_raw, 'freetext_') === 0) {
|
$commandedet_id = 0;
|
||||||
|
$mehraufwand_id = 0;
|
||||||
|
|
||||||
|
if (strpos($entfaellt_product_raw, 'mehraufwand_') === 0) {
|
||||||
|
// Mehraufwand-Produkt (Format: "mehraufwand_ROWID")
|
||||||
|
$mehraufwand_id = (int)substr($entfaellt_product_raw, 12);
|
||||||
|
// Produkt-ID und Beschreibung aus stundenzettel_product laden
|
||||||
|
$sqlMehr = "SELECT fk_product, description FROM ".MAIN_DB_PREFIX."stundenzettel_product WHERE rowid = ".((int)$mehraufwand_id);
|
||||||
|
$resqlMehr = $db->query($sqlMehr);
|
||||||
|
if ($resqlMehr && ($objMehr = $db->fetch_object($resqlMehr))) {
|
||||||
|
$fk_product = (int)$objMehr->fk_product;
|
||||||
|
if ($fk_product == 0) {
|
||||||
|
$freetext_description = $objMehr->description;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elseif (strpos($entfaellt_product_raw, 'freetext_') === 0) {
|
||||||
// Freitext-Produkt aus dem Auftrag
|
// Freitext-Produkt aus dem Auftrag
|
||||||
$commandedet_id = (int)substr($entfaellt_product_raw, 9);
|
$commandedet_id = (int)substr($entfaellt_product_raw, 9);
|
||||||
// Beschreibung aus commandedet laden
|
// Beschreibung aus commandedet laden
|
||||||
|
|
@ -455,7 +473,18 @@ if ($action == 'add_entfaellt' && $permissiontoadd) {
|
||||||
|
|
||||||
if ($fk_product > 0 || !empty($freetext_description)) {
|
if ($fk_product > 0 || !empty($freetext_description)) {
|
||||||
// Server-seitige Validierung: Prüfen ob Menge noch verfügbar ist
|
// Server-seitige Validierung: Prüfen ob Menge noch verfügbar ist
|
||||||
if ($object->fk_commande > 0) {
|
if ($mehraufwand_id > 0) {
|
||||||
|
// Mehraufwand-Validierung: Prüfe verfügbare Menge
|
||||||
|
$sqlCheck = "SELECT qty, qty_done FROM ".MAIN_DB_PREFIX."stundenzettel_product WHERE rowid = ".((int)$mehraufwand_id);
|
||||||
|
$resqlCheck = $db->query($sqlCheck);
|
||||||
|
if ($resqlCheck && ($objCheck = $db->fetch_object($resqlCheck))) {
|
||||||
|
$qty_available = $objCheck->qty - $objCheck->qty_done;
|
||||||
|
if ($qty > $qty_available) {
|
||||||
|
setEventMessages($langs->trans('ErrorQtyExceedsAvailable', $qty_available), null, 'errors');
|
||||||
|
$error++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} elseif ($object->fk_commande > 0) {
|
||||||
if ($fk_product > 0) {
|
if ($fk_product > 0) {
|
||||||
// Produkt-Validierung
|
// Produkt-Validierung
|
||||||
$sqlCheck = "SELECT cd.qty,";
|
$sqlCheck = "SELECT cd.qty,";
|
||||||
|
|
@ -503,20 +532,49 @@ if ($action == 'add_entfaellt' && $permissiontoadd) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$error) {
|
if (!$error) {
|
||||||
// Produkt zum Stundenzettel hinzufügen mit origin='omitted'
|
if ($mehraufwand_id > 0) {
|
||||||
$result = $object->addProduct(
|
// Mehraufwand: Menge vom qty_done erhöhen statt neuen Eintrag erstellen
|
||||||
$fk_product,
|
// Oder: Menge vom Mehraufwand reduzieren und als Entfällt anlegen
|
||||||
0, // fk_commandedet
|
// Wir reduzieren qty des Mehraufwands und legen einen neuen Entfällt-Eintrag an
|
||||||
0, // fk_manager_line
|
$sqlUpdateMehr = "UPDATE ".MAIN_DB_PREFIX."stundenzettel_product SET qty = qty - ".((int)$qty);
|
||||||
0, // qty_original
|
$sqlUpdateMehr .= " WHERE rowid = ".((int)$mehraufwand_id);
|
||||||
$qty, // qty_done (Menge die entfällt)
|
$db->query($sqlUpdateMehr);
|
||||||
'omitted', // origin (entfällt)
|
|
||||||
$description // description (Grund)
|
// Entfällt-Eintrag mit Hinweis auf Mehraufwand anlegen
|
||||||
);
|
$entfaelltDesc = $langs->trans("Mehraufwand").': '.$description;
|
||||||
if ($result > 0) {
|
if (!empty($reason)) {
|
||||||
setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
|
$entfaelltDesc .= ' - '.$reason;
|
||||||
|
}
|
||||||
|
$result = $object->addProduct(
|
||||||
|
$fk_product,
|
||||||
|
0, // fk_commandedet
|
||||||
|
0, // fk_manager_line
|
||||||
|
0, // qty_original
|
||||||
|
$qty, // qty_done (Menge die entfällt)
|
||||||
|
'omitted', // origin (entfällt)
|
||||||
|
$entfaelltDesc // description (Grund)
|
||||||
|
);
|
||||||
|
if ($result > 0) {
|
||||||
|
setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
|
||||||
|
} else {
|
||||||
|
setEventMessages($object->error, null, 'errors');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setEventMessages($object->error, null, 'errors');
|
// Produkt zum Stundenzettel hinzufügen mit origin='omitted'
|
||||||
|
$result = $object->addProduct(
|
||||||
|
$fk_product,
|
||||||
|
0, // fk_commandedet
|
||||||
|
0, // fk_manager_line
|
||||||
|
0, // qty_original
|
||||||
|
$qty, // qty_done (Menge die entfällt)
|
||||||
|
'omitted', // origin (entfällt)
|
||||||
|
$description // description (Grund)
|
||||||
|
);
|
||||||
|
if ($result > 0) {
|
||||||
|
setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
|
||||||
|
} else {
|
||||||
|
setEventMessages($object->error, null, 'errors');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -724,6 +782,9 @@ $_GET['mainmenu'] = 'stundenzettel';
|
||||||
|
|
||||||
llxHeader('', $title, '', '', 0, 0, '', '', '', 'mod-stundenzettel page-card');
|
llxHeader('', $title, '', '', 0, 0, '', '', '', 'mod-stundenzettel page-card');
|
||||||
|
|
||||||
|
// Mobile CSS einbinden
|
||||||
|
print '<link rel="stylesheet" type="text/css" href="'.dol_buildpath('/stundenzettel/css/stundenzettel-mobile.css', 1).'?v='.filemtime(dol_buildpath('/stundenzettel/css/stundenzettel-mobile.css', 0)).'">';
|
||||||
|
|
||||||
// JavaScript für Mengenprüfung
|
// JavaScript für Mengenprüfung
|
||||||
print '<script type="text/javascript">
|
print '<script type="text/javascript">
|
||||||
function plusQtyWithCheck(rowid, qtyOriginal, totalQtyAllStz) {
|
function plusQtyWithCheck(rowid, qtyOriginal, totalQtyAllStz) {
|
||||||
|
|
@ -859,6 +920,11 @@ elseif ($object->id > 0) {
|
||||||
// BEREICH: LEISTUNGEN (immer anzeigen außer bei Notizen)
|
// BEREICH: LEISTUNGEN (immer anzeigen außer bei Notizen)
|
||||||
// =============================================
|
// =============================================
|
||||||
if ($subtab != 'notes') {
|
if ($subtab != 'notes') {
|
||||||
|
// Standard-Leistung vom Kunden laden
|
||||||
|
$thirdparty = new Societe($db);
|
||||||
|
$thirdparty->fetch($object->fk_soc);
|
||||||
|
$thirdparty->fetch_optionals();
|
||||||
|
|
||||||
// Prüfen ob eine Leistung bearbeitet wird
|
// Prüfen ob eine Leistung bearbeitet wird
|
||||||
$editLeistungId = GETPOST('edit_leistung', 'int');
|
$editLeistungId = GETPOST('edit_leistung', 'int');
|
||||||
|
|
||||||
|
|
@ -876,6 +942,7 @@ elseif ($object->id > 0) {
|
||||||
print '<th>'.$langs->trans("LeistungTimeStart").'</th>';
|
print '<th>'.$langs->trans("LeistungTimeStart").'</th>';
|
||||||
print '<th>'.$langs->trans("LeistungTimeEnd").'</th>';
|
print '<th>'.$langs->trans("LeistungTimeEnd").'</th>';
|
||||||
print '<th class="center">'.$langs->trans("Duration").'</th>';
|
print '<th class="center">'.$langs->trans("Duration").'</th>';
|
||||||
|
print '<th>'.$langs->trans("DefaultService").'</th>';
|
||||||
print '<th>'.$langs->trans("Description").'</th>';
|
print '<th>'.$langs->trans("Description").'</th>';
|
||||||
if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) {
|
if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) {
|
||||||
print '<th class="center" width="40"></th>'; // Edit
|
print '<th class="center" width="40"></th>'; // Edit
|
||||||
|
|
@ -904,6 +971,10 @@ elseif ($object->id > 0) {
|
||||||
print $hours.'h '.sprintf('%02d', $mins).'min';
|
print $hours.'h '.sprintf('%02d', $mins).'min';
|
||||||
}
|
}
|
||||||
print '</td>';
|
print '</td>';
|
||||||
|
// Service/Product Selection
|
||||||
|
print '<td>';
|
||||||
|
$form->select_produits($leistung->fk_product, 'fk_product', 1, 0, 0, -1, 2, '', 0, array(), 0, '1', 0, 'maxwidth200');
|
||||||
|
print '</td>';
|
||||||
print '<td><textarea name="leistung_description" class="flat" rows="5" style="width: 300px; resize: vertical;">'.dol_escape_htmltag($leistung->description).'</textarea></td>';
|
print '<td><textarea name="leistung_description" class="flat" rows="5" style="width: 300px; resize: vertical;">'.dol_escape_htmltag($leistung->description).'</textarea></td>';
|
||||||
// Save Button
|
// Save Button
|
||||||
print '<td class="center">';
|
print '<td class="center">';
|
||||||
|
|
@ -928,6 +999,14 @@ elseif ($object->id > 0) {
|
||||||
print $hours.'h '.sprintf('%02d', $mins).'min';
|
print $hours.'h '.sprintf('%02d', $mins).'min';
|
||||||
}
|
}
|
||||||
print '</td>';
|
print '</td>';
|
||||||
|
// Leistungsposition anzeigen
|
||||||
|
print '<td>';
|
||||||
|
if (!empty($leistung->fk_product)) {
|
||||||
|
print dol_escape_htmltag($leistung->product_label);
|
||||||
|
} else {
|
||||||
|
print '<span class="opacitymedium">'.$langs->trans("NotSet").'</span>';
|
||||||
|
}
|
||||||
|
print '</td>';
|
||||||
print '<td>'.dol_htmlentitiesbr($leistung->description).'</td>';
|
print '<td>'.dol_htmlentitiesbr($leistung->description).'</td>';
|
||||||
if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) {
|
if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) {
|
||||||
// Edit Button
|
// Edit Button
|
||||||
|
|
@ -943,7 +1022,7 @@ elseif ($object->id > 0) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$colspan = ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) ? 7 : 5;
|
$colspan = ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) ? 8 : 6;
|
||||||
print '<tr class="oddeven"><td colspan="'.$colspan.'" class="opacitymedium">'.$langs->trans("NoRecordFound").'</td></tr>';
|
print '<tr class="oddeven"><td colspan="'.$colspan.'" class="opacitymedium">'.$langs->trans("NoRecordFound").'</td></tr>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -989,6 +1068,16 @@ elseif ($object->id > 0) {
|
||||||
print '<td class="center">';
|
print '<td class="center">';
|
||||||
print '</td>';
|
print '</td>';
|
||||||
|
|
||||||
|
// Leistungsposition (Service/Dienstleistung)
|
||||||
|
print '<td>';
|
||||||
|
// Standard-Leistung vom Kunden holen falls vorhanden
|
||||||
|
$defaultServiceId = 0;
|
||||||
|
if (!empty($thirdparty->array_options['options_stundenzettel_default_service'])) {
|
||||||
|
$defaultServiceId = $thirdparty->array_options['options_stundenzettel_default_service'];
|
||||||
|
}
|
||||||
|
$form->select_produits($defaultServiceId, 'fk_product', 1, 0, 0, -1, 2, '', 0, array(), 0, '1', 0, 'maxwidth200');
|
||||||
|
print '</td>';
|
||||||
|
|
||||||
// Beschreibung (Tagesbeschreibung - größeres Textarea, 5 Zeilen)
|
// Beschreibung (Tagesbeschreibung - größeres Textarea, 5 Zeilen)
|
||||||
print '<td>';
|
print '<td>';
|
||||||
print '<textarea name="leistung_description" class="flat" rows="5" style="width: 300px; resize: vertical;" placeholder="'.$langs->trans("Description").'"></textarea>';
|
print '<textarea name="leistung_description" class="flat" rows="5" style="width: 300px; resize: vertical;" placeholder="'.$langs->trans("Description").'"></textarea>';
|
||||||
|
|
@ -1012,7 +1101,7 @@ elseif ($object->id > 0) {
|
||||||
if ($totalDuration > 0) {
|
if ($totalDuration > 0) {
|
||||||
$hours = floor($totalDuration / 60);
|
$hours = floor($totalDuration / 60);
|
||||||
$mins = $totalDuration % 60;
|
$mins = $totalDuration % 60;
|
||||||
$colspan = ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) ? 7 : 5;
|
$colspan = ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) ? 8 : 6;
|
||||||
print '<tr class="liste_total">';
|
print '<tr class="liste_total">';
|
||||||
print '<td colspan="3" class="right"><strong>'.$langs->trans("Total").'</strong></td>';
|
print '<td colspan="3" class="right"><strong>'.$langs->trans("Total").'</strong></td>';
|
||||||
print '<td class="center"><strong>'.$hours.'h '.sprintf('%02d', $mins).'min</strong></td>';
|
print '<td class="center"><strong>'.$hours.'h '.sprintf('%02d', $mins).'min</strong></td>';
|
||||||
|
|
@ -1237,6 +1326,13 @@ elseif ($object->id > 0) {
|
||||||
print '<div class="div-table-responsive-no-min" style="margin-top: 15px;">';
|
print '<div class="div-table-responsive-no-min" style="margin-top: 15px;">';
|
||||||
print '<table class="noborder centpercent">';
|
print '<table class="noborder centpercent">';
|
||||||
print '<tr class="liste_titre"><th colspan="5" style="border-bottom: 2px solid #dee2e6;"><strong>'.$langs->trans("Entfaellt").'</strong> - <span class="opacitymedium" style="font-weight:normal;">'.$langs->trans("EntfaelltDesc").'</span></th></tr>';
|
print '<tr class="liste_titre"><th colspan="5" style="border-bottom: 2px solid #dee2e6;"><strong>'.$langs->trans("Entfaellt").'</strong> - <span class="opacitymedium" style="font-weight:normal;">'.$langs->trans("EntfaelltDesc").'</span></th></tr>';
|
||||||
|
print '<tr class="liste_titre">';
|
||||||
|
print '<th>'.$langs->trans("Product").'</th>';
|
||||||
|
print '<th class="center" style="width:80px;">'.$langs->trans("Qty").'</th>';
|
||||||
|
print '<th style="width:200px;">'.$langs->trans("Reason").'</th>';
|
||||||
|
print '<th class="center" style="width:40px;"></th>'; // Save
|
||||||
|
print '<th class="center" style="width:40px;"></th>'; // Delete
|
||||||
|
print '</tr>';
|
||||||
|
|
||||||
// Zuerst bereits erfasste Entfällt-Produkte anzeigen
|
// Zuerst bereits erfasste Entfällt-Produkte anzeigen
|
||||||
$entfaelltProducts = array();
|
$entfaelltProducts = array();
|
||||||
|
|
@ -1354,28 +1450,76 @@ elseif ($object->id > 0) {
|
||||||
print '<input type="hidden" name="action" value="add_entfaellt">';
|
print '<input type="hidden" name="action" value="add_entfaellt">';
|
||||||
|
|
||||||
// Produkt-Auswahl - NUR Produkte aus dem Auftrag mit verfügbarer Menge
|
// Produkt-Auswahl - NUR Produkte aus dem Auftrag mit verfügbarer Menge
|
||||||
print '<td>';
|
// Mehraufwand-Produkte mit verbleibender Menge laden (aus allen Stundenzetteln des Auftrags)
|
||||||
if ($hasAvailableProducts) {
|
$mehraufwandAvailable = array();
|
||||||
print '<select name="entfaellt_product" class="flat minwidth300" id="entfaellt_product_select" onchange="updateMaxQty(this)">';
|
if ($object->fk_commande > 0) {
|
||||||
print '<option value="" data-max="1">-- '.$langs->trans("SelectProducts").' --</option>';
|
$sqlMehraufwand = "SELECT sp.rowid, sp.fk_product, sp.qty, sp.qty_done, sp.description,";
|
||||||
foreach ($orderProducts as $op) {
|
$sqlMehraufwand .= " p.ref as product_ref, p.label as product_label";
|
||||||
// Nur Produkte anzeigen wo noch etwas entfallen kann
|
$sqlMehraufwand .= " FROM ".MAIN_DB_PREFIX."stundenzettel_product sp";
|
||||||
if ($op->qty_available <= 0) continue;
|
$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";
|
||||||
if ($op->fk_product > 0) {
|
$sqlMehraufwand .= " WHERE s.fk_commande = ".((int)$object->fk_commande);
|
||||||
print '<option value="'.$op->fk_product.'" data-commandedet="'.$op->rowid.'" data-max="'.((int)$op->qty_available).'">';
|
$sqlMehraufwand .= " AND sp.origin = 'additional'";
|
||||||
print $op->product_ref.' - '.$op->product_label;
|
$sqlMehraufwand .= " AND (sp.qty - sp.qty_done) > 0"; // Nur wo noch etwas verfügbar ist
|
||||||
print ' ('.$langs->trans("QtyRemaining").': '.((int)$op->qty_available).')';
|
$sqlMehraufwand .= " ORDER BY p.label, sp.description";
|
||||||
print '</option>';
|
$resqlMehraufwand = $db->query($sqlMehraufwand);
|
||||||
} else {
|
if ($resqlMehraufwand) {
|
||||||
// Freitext-Positionen aus dem Auftrag - Beschreibung als Bezeichnung
|
while ($objM = $db->fetch_object($resqlMehraufwand)) {
|
||||||
$desc = strip_tags($op->description);
|
$objM->qty_available = $objM->qty - $objM->qty_done;
|
||||||
$descShort = (strlen($desc) > 50) ? substr($desc, 0, 47).'...' : $desc;
|
$mehraufwandAvailable[] = $objM;
|
||||||
print '<option value="freetext_'.$op->rowid.'" data-commandedet="'.$op->rowid.'" data-description="'.dol_escape_htmltag($desc).'" data-max="'.((int)$op->qty_available).'">';
|
|
||||||
print $descShort.' ('.$langs->trans("QtyRemaining").': '.((int)$op->qty_available).')';
|
|
||||||
print '</option>';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$hasMehraufwandProducts = (count($mehraufwandAvailable) > 0);
|
||||||
|
|
||||||
|
print '<td>';
|
||||||
|
if ($hasAvailableProducts || $hasMehraufwandProducts) {
|
||||||
|
print '<select name="entfaellt_product" class="flat minwidth300" id="entfaellt_product_select" onchange="updateMaxQty(this)">';
|
||||||
|
print '<option value="" data-max="1">-- '.$langs->trans("SelectProducts").' --</option>';
|
||||||
|
|
||||||
|
// Produkte aus Auftrag
|
||||||
|
if ($hasAvailableProducts) {
|
||||||
|
print '<optgroup label="'.$langs->trans("FromOrder").'">';
|
||||||
|
foreach ($orderProducts as $op) {
|
||||||
|
if ($op->qty_available <= 0) continue;
|
||||||
|
|
||||||
|
if ($op->fk_product > 0) {
|
||||||
|
print '<option value="'.$op->fk_product.'" data-commandedet="'.$op->rowid.'" data-max="'.((int)$op->qty_available).'">';
|
||||||
|
print $op->product_ref.' - '.$op->product_label;
|
||||||
|
print ' ('.$langs->trans("QtyRemaining").': '.((int)$op->qty_available).')';
|
||||||
|
print '</option>';
|
||||||
|
} else {
|
||||||
|
$desc = strip_tags($op->description);
|
||||||
|
$descShort = (strlen($desc) > 50) ? substr($desc, 0, 47).'...' : $desc;
|
||||||
|
print '<option value="freetext_'.$op->rowid.'" data-commandedet="'.$op->rowid.'" data-description="'.dol_escape_htmltag($desc).'" data-max="'.((int)$op->qty_available).'">';
|
||||||
|
print $descShort.' ('.$langs->trans("QtyRemaining").': '.((int)$op->qty_available).')';
|
||||||
|
print '</option>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print '</optgroup>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mehraufwand-Produkte mit verbleibender Menge
|
||||||
|
if ($hasMehraufwandProducts) {
|
||||||
|
print '<optgroup label="'.$langs->trans("Mehraufwand").'">';
|
||||||
|
foreach ($mehraufwandAvailable as $mp) {
|
||||||
|
if ($mp->fk_product > 0) {
|
||||||
|
print '<option value="mehraufwand_'.$mp->rowid.'" data-max="'.((int)$mp->qty_available).'">';
|
||||||
|
print $mp->product_ref.' - '.$mp->product_label;
|
||||||
|
print ' ('.$langs->trans("QtyRemaining").': '.((int)$mp->qty_available).')';
|
||||||
|
print '</option>';
|
||||||
|
} else {
|
||||||
|
$desc = strip_tags($mp->description);
|
||||||
|
$descShort = (strlen($desc) > 50) ? substr($desc, 0, 47).'...' : $desc;
|
||||||
|
print '<option value="mehraufwand_'.$mp->rowid.'" data-description="'.dol_escape_htmltag($desc).'" data-max="'.((int)$mp->qty_available).'">';
|
||||||
|
print $descShort.' ('.$langs->trans("QtyRemaining").': '.((int)$mp->qty_available).')';
|
||||||
|
print '</option>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print '</optgroup>';
|
||||||
|
}
|
||||||
|
|
||||||
print '</select>';
|
print '</select>';
|
||||||
} else {
|
} else {
|
||||||
print '<small class="opacitymedium">'.$langs->trans("AllProductsUsedOrOmitted").'</small>';
|
print '<small class="opacitymedium">'.$langs->trans("AllProductsUsedOrOmitted").'</small>';
|
||||||
|
|
@ -1389,7 +1533,7 @@ elseif ($object->id > 0) {
|
||||||
|
|
||||||
// Grund
|
// Grund
|
||||||
print '<td style="width:200px;">';
|
print '<td style="width:200px;">';
|
||||||
print '<input type="text" name="entfaellt_description" class="flat" style="width:190px;" placeholder="'.$langs->trans("Reason").'">';
|
print '<input type="text" name="entfaellt_description" class="flat" style="width:100%;" placeholder="'.$langs->trans("Reason").'">';
|
||||||
print '</td>';
|
print '</td>';
|
||||||
|
|
||||||
// Hinzufügen-Button (colspan für beide Button-Spalten)
|
// Hinzufügen-Button (colspan für beide Button-Spalten)
|
||||||
|
|
@ -1549,9 +1693,9 @@ elseif ($object->id > 0) {
|
||||||
print '<input type="number" name="mehraufwand_qty" class="flat" style="width:70px; text-align:center;" value="1" min="1">';
|
print '<input type="number" name="mehraufwand_qty" class="flat" style="width:70px; text-align:center;" value="1" min="1">';
|
||||||
print '</td>';
|
print '</td>';
|
||||||
|
|
||||||
// Grund (optional)
|
// Grund
|
||||||
print '<td style="width:200px;">';
|
print '<td style="width:200px;">';
|
||||||
print '<input type="text" name="mehraufwand_reason" class="flat" style="width:100%;" placeholder="'.$langs->trans("Reason").' ('.$langs->trans("Optional").')">';
|
print '<input type="text" name="mehraufwand_reason" class="flat" style="width:100%;" placeholder="'.$langs->trans("Reason").'">';
|
||||||
print '</td>';
|
print '</td>';
|
||||||
|
|
||||||
// Hinzufügen-Button (colspan für Save, Delete)
|
// Hinzufügen-Button (colspan für Save, Delete)
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,16 @@ class Stundenzettel extends CommonObject
|
||||||
*/
|
*/
|
||||||
public $note_public;
|
public $note_public;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var float|null Stundenpreis (NULL = Standard verwenden)
|
||||||
|
*/
|
||||||
|
public $hourly_rate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int 1 = Stundenpreis wurde manuell geändert
|
||||||
|
*/
|
||||||
|
public $hourly_rate_is_custom = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var array Leistungen
|
* @var array Leistungen
|
||||||
*/
|
*/
|
||||||
|
|
@ -139,7 +149,8 @@ class Stundenzettel extends CommonObject
|
||||||
|
|
||||||
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
|
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
|
||||||
$sql .= "ref, entity, fk_commande, fk_soc, fk_user_author,";
|
$sql .= "ref, entity, fk_commande, fk_soc, fk_user_author,";
|
||||||
$sql .= "date_stundenzettel, datec, status, note_private, note_public";
|
$sql .= "date_stundenzettel, datec, status, note_private, note_public,";
|
||||||
|
$sql .= "hourly_rate, hourly_rate_is_custom";
|
||||||
$sql .= ") VALUES (";
|
$sql .= ") VALUES (";
|
||||||
$sql .= "'".$this->db->escape($this->ref)."',";
|
$sql .= "'".$this->db->escape($this->ref)."',";
|
||||||
$sql .= ((int)$conf->entity).",";
|
$sql .= ((int)$conf->entity).",";
|
||||||
|
|
@ -150,7 +161,9 @@ class Stundenzettel extends CommonObject
|
||||||
$sql .= "'".$this->db->idate(dol_now())."',";
|
$sql .= "'".$this->db->idate(dol_now())."',";
|
||||||
$sql .= "0,"; // STATUS_DRAFT
|
$sql .= "0,"; // STATUS_DRAFT
|
||||||
$sql .= ($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL").",";
|
$sql .= ($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL").",";
|
||||||
$sql .= ($this->note_public ? "'".$this->db->escape($this->note_public)."'" : "NULL");
|
$sql .= ($this->note_public ? "'".$this->db->escape($this->note_public)."'" : "NULL").",";
|
||||||
|
$sql .= (isset($this->hourly_rate) && $this->hourly_rate !== null ? ((float)$this->hourly_rate) : "NULL").",";
|
||||||
|
$sql .= ((int)$this->hourly_rate_is_custom);
|
||||||
$sql .= ")";
|
$sql .= ")";
|
||||||
|
|
||||||
dol_syslog(get_class($this)."::create", LOG_DEBUG);
|
dol_syslog(get_class($this)."::create", LOG_DEBUG);
|
||||||
|
|
@ -184,7 +197,8 @@ class Stundenzettel extends CommonObject
|
||||||
{
|
{
|
||||||
$sql = "SELECT s.rowid, s.ref, s.entity, s.fk_commande, s.fk_facture, s.fk_soc,";
|
$sql = "SELECT s.rowid, s.ref, s.entity, s.fk_commande, s.fk_facture, s.fk_soc,";
|
||||||
$sql .= " s.fk_user_author, s.fk_user_valid, s.date_stundenzettel, s.datec,";
|
$sql .= " s.fk_user_author, s.fk_user_valid, s.date_stundenzettel, s.datec,";
|
||||||
$sql .= " s.date_valid, s.status, s.note_private, s.note_public, s.tms";
|
$sql .= " s.date_valid, s.status, s.note_private, s.note_public, s.tms,";
|
||||||
|
$sql .= " s.hourly_rate, s.hourly_rate_is_custom";
|
||||||
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as s";
|
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as s";
|
||||||
if ($ref) {
|
if ($ref) {
|
||||||
$sql .= " WHERE s.ref = '".$this->db->escape($ref)."'";
|
$sql .= " WHERE s.ref = '".$this->db->escape($ref)."'";
|
||||||
|
|
@ -212,6 +226,8 @@ class Stundenzettel extends CommonObject
|
||||||
$this->status = $obj->status;
|
$this->status = $obj->status;
|
||||||
$this->note_private = $obj->note_private;
|
$this->note_private = $obj->note_private;
|
||||||
$this->note_public = $obj->note_public;
|
$this->note_public = $obj->note_public;
|
||||||
|
$this->hourly_rate = $obj->hourly_rate;
|
||||||
|
$this->hourly_rate_is_custom = $obj->hourly_rate_is_custom;
|
||||||
|
|
||||||
// Load lines
|
// Load lines
|
||||||
$this->fetchLeistungen();
|
$this->fetchLeistungen();
|
||||||
|
|
@ -242,7 +258,9 @@ class Stundenzettel extends CommonObject
|
||||||
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
|
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
|
||||||
$sql .= " date_stundenzettel = '".$this->db->idate($this->date_stundenzettel)."',";
|
$sql .= " date_stundenzettel = '".$this->db->idate($this->date_stundenzettel)."',";
|
||||||
$sql .= " note_private = ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL").",";
|
$sql .= " note_private = ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "NULL").",";
|
||||||
$sql .= " note_public = ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : "NULL");
|
$sql .= " note_public = ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : "NULL").",";
|
||||||
|
$sql .= " hourly_rate = ".(isset($this->hourly_rate) && $this->hourly_rate !== null && $this->hourly_rate !== '' ? ((float)$this->hourly_rate) : "NULL").",";
|
||||||
|
$sql .= " hourly_rate_is_custom = ".((int)$this->hourly_rate_is_custom);
|
||||||
$sql .= " WHERE rowid = ".((int)$this->id);
|
$sql .= " WHERE rowid = ".((int)$this->id);
|
||||||
|
|
||||||
dol_syslog(get_class($this)."::update", LOG_DEBUG);
|
dol_syslog(get_class($this)."::update", LOG_DEBUG);
|
||||||
|
|
@ -378,10 +396,12 @@ class Stundenzettel extends CommonObject
|
||||||
{
|
{
|
||||||
$this->leistungen = array();
|
$this->leistungen = array();
|
||||||
|
|
||||||
$sql = "SELECT rowid, fk_user, date_leistung, time_start, time_end, duration, description, rang";
|
$sql = "SELECT l.rowid, l.fk_user, l.fk_product, l.date_leistung, l.time_start, l.time_end, l.duration, l.description, l.rang,";
|
||||||
$sql .= " FROM ".MAIN_DB_PREFIX."stundenzettel_leistung";
|
$sql .= " p.ref as product_ref, p.label as product_label";
|
||||||
$sql .= " WHERE fk_stundenzettel = ".((int)$this->id);
|
$sql .= " FROM ".MAIN_DB_PREFIX."stundenzettel_leistung as l";
|
||||||
$sql .= " ORDER BY rang, date_leistung, time_start";
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON l.fk_product = p.rowid";
|
||||||
|
$sql .= " WHERE l.fk_stundenzettel = ".((int)$this->id);
|
||||||
|
$sql .= " ORDER BY l.rang, l.date_leistung, l.time_start";
|
||||||
|
|
||||||
$resql = $this->db->query($sql);
|
$resql = $this->db->query($sql);
|
||||||
if ($resql) {
|
if ($resql) {
|
||||||
|
|
@ -432,7 +452,7 @@ class Stundenzettel extends CommonObject
|
||||||
* @param string $description Description
|
* @param string $description Description
|
||||||
* @return int <0 if KO, >0 if OK
|
* @return int <0 if KO, >0 if OK
|
||||||
*/
|
*/
|
||||||
public function addLeistung($user, $date, $time_start = null, $time_end = null, $description = '')
|
public function addLeistung($user, $date, $time_start = null, $time_end = null, $description = '', $fk_product = null)
|
||||||
{
|
{
|
||||||
global $langs;
|
global $langs;
|
||||||
|
|
||||||
|
|
@ -475,10 +495,11 @@ class Stundenzettel extends CommonObject
|
||||||
}
|
}
|
||||||
|
|
||||||
$sql = "INSERT INTO ".MAIN_DB_PREFIX."stundenzettel_leistung (";
|
$sql = "INSERT INTO ".MAIN_DB_PREFIX."stundenzettel_leistung (";
|
||||||
$sql .= "fk_stundenzettel, fk_user, date_leistung, time_start, time_end, duration, description, rang";
|
$sql .= "fk_stundenzettel, fk_user, fk_product, date_leistung, time_start, time_end, duration, description, rang";
|
||||||
$sql .= ") VALUES (";
|
$sql .= ") VALUES (";
|
||||||
$sql .= ((int)$this->id).",";
|
$sql .= ((int)$this->id).",";
|
||||||
$sql .= ((int)$user->id).",";
|
$sql .= ((int)$user->id).",";
|
||||||
|
$sql .= ($fk_product > 0 ? ((int)$fk_product) : "NULL").",";
|
||||||
$sql .= "'".$this->db->idate($date)."',";
|
$sql .= "'".$this->db->idate($date)."',";
|
||||||
$sql .= ($time_start ? "'".$this->db->escape($time_start)."'" : "NULL").",";
|
$sql .= ($time_start ? "'".$this->db->escape($time_start)."'" : "NULL").",";
|
||||||
$sql .= ($time_end ? "'".$this->db->escape($time_end)."'" : "NULL").",";
|
$sql .= ($time_end ? "'".$this->db->escape($time_end)."'" : "NULL").",";
|
||||||
|
|
@ -502,9 +523,10 @@ class Stundenzettel extends CommonObject
|
||||||
* @param string $time_start Start time
|
* @param string $time_start Start time
|
||||||
* @param string $time_end End time
|
* @param string $time_end End time
|
||||||
* @param string $description Description
|
* @param string $description Description
|
||||||
|
* @param int $fk_product Product/Service ID
|
||||||
* @return int <0 if KO, >0 if OK
|
* @return int <0 if KO, >0 if OK
|
||||||
*/
|
*/
|
||||||
public function updateLeistung($leistung_id, $date, $time_start = null, $time_end = null, $description = '')
|
public function updateLeistung($leistung_id, $date, $time_start = null, $time_end = null, $description = '', $fk_product = null)
|
||||||
{
|
{
|
||||||
global $langs;
|
global $langs;
|
||||||
|
|
||||||
|
|
@ -539,6 +561,7 @@ class Stundenzettel extends CommonObject
|
||||||
}
|
}
|
||||||
|
|
||||||
$sql = "UPDATE ".MAIN_DB_PREFIX."stundenzettel_leistung SET";
|
$sql = "UPDATE ".MAIN_DB_PREFIX."stundenzettel_leistung SET";
|
||||||
|
$sql .= " fk_product = ".($fk_product > 0 ? ((int)$fk_product) : "NULL").",";
|
||||||
$sql .= " date_leistung = '".$this->db->idate($date)."',";
|
$sql .= " date_leistung = '".$this->db->idate($date)."',";
|
||||||
$sql .= " time_start = ".($time_start ? "'".$this->db->escape($time_start)."'" : "NULL").",";
|
$sql .= " time_start = ".($time_start ? "'".$this->db->escape($time_start)."'" : "NULL").",";
|
||||||
$sql .= " time_end = ".($time_end ? "'".$this->db->escape($time_end)."'" : "NULL").",";
|
$sql .= " time_end = ".($time_end ? "'".$this->db->escape($time_end)."'" : "NULL").",";
|
||||||
|
|
|
||||||
|
|
@ -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.1.0';
|
$this->version = '1.2.0';
|
||||||
|
|
||||||
// Autor
|
// Autor
|
||||||
$this->editor_name = 'Data IT Solution';
|
$this->editor_name = 'Data IT Solution';
|
||||||
|
|
@ -107,7 +107,7 @@ class modStundenzettel extends DolibarrModules
|
||||||
|
|
||||||
// Tabs - Tab im Auftrag (order = commande)
|
// Tabs - Tab im Auftrag (order = commande)
|
||||||
$this->tabs = array(
|
$this->tabs = array(
|
||||||
'order:+stundenzettel:Stundenzettel:stundenzettel@stundenzettel:$user->hasRight("stundenzettel","read"):/custom/stundenzettel/stundenzettel_commande.php?id=__ID__'
|
'order:+stundenzettel:Stundenzettel:stundenzettel@stundenzettel:$user->hasRight("stundenzettel","read"):/custom/stundenzettel/stundenzettel_commande.php?id=__ID__&tab=products&noredirect=1'
|
||||||
);
|
);
|
||||||
|
|
||||||
// Boxen/Widgets
|
// Boxen/Widgets
|
||||||
|
|
@ -320,6 +320,9 @@ class modStundenzettel extends DolibarrModules
|
||||||
// Extrafeld "Standard-Leistung" für Kunden anlegen
|
// Extrafeld "Standard-Leistung" für Kunden anlegen
|
||||||
$this->createExtraFieldDefaultService();
|
$this->createExtraFieldDefaultService();
|
||||||
|
|
||||||
|
// Stundenpreis-Felder hinzufügen (Update 1.2.0)
|
||||||
|
$this->addHourlyRateFields();
|
||||||
|
|
||||||
$sql = array();
|
$sql = array();
|
||||||
|
|
||||||
return $this->_init($sql, $options);
|
return $this->_init($sql, $options);
|
||||||
|
|
@ -454,6 +457,35 @@ class modStundenzettel extends DolibarrModules
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fügt die Leistungsposition zur Leistungstabelle hinzu (Update 1.2.0)
|
||||||
|
* Wird bei jeder Modulaktivierung ausgeführt - IF NOT EXISTS verhindert Fehler
|
||||||
|
*
|
||||||
|
* @return int 1 if OK, -1 if error
|
||||||
|
*/
|
||||||
|
private function addHourlyRateFields()
|
||||||
|
{
|
||||||
|
// Spalte fk_product zur Leistungstabelle hinzufügen falls nicht vorhanden
|
||||||
|
$sql1 = "ALTER TABLE ".MAIN_DB_PREFIX."stundenzettel_leistung
|
||||||
|
ADD COLUMN IF NOT EXISTS fk_product INTEGER DEFAULT NULL
|
||||||
|
COMMENT 'Verknüpfung zur Leistungsposition (Dienstleistung)'";
|
||||||
|
|
||||||
|
$resql1 = $this->db->query($sql1);
|
||||||
|
if (!$resql1) {
|
||||||
|
// Fallback für ältere MySQL-Versionen ohne IF NOT EXISTS
|
||||||
|
$sql1b = "SHOW COLUMNS FROM ".MAIN_DB_PREFIX."stundenzettel_leistung LIKE 'fk_product'";
|
||||||
|
$resql1b = $this->db->query($sql1b);
|
||||||
|
if ($resql1b && $this->db->num_rows($resql1b) == 0) {
|
||||||
|
$sql1c = "ALTER TABLE ".MAIN_DB_PREFIX."stundenzettel_leistung
|
||||||
|
ADD COLUMN fk_product INTEGER DEFAULT NULL";
|
||||||
|
$this->db->query($sql1c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dol_syslog("modStundenzettel::addHourlyRateFields Service product field checked/added", LOG_DEBUG);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Funktion beim Deaktivieren des Moduls
|
* Funktion beim Deaktivieren des Moduls
|
||||||
*
|
*
|
||||||
|
|
|
||||||
330
css/stundenzettel-mobile.css
Normal file
330
css/stundenzettel-mobile.css
Normal file
|
|
@ -0,0 +1,330 @@
|
||||||
|
/**
|
||||||
|
* Stundenzettel Mobile CSS
|
||||||
|
* Responsive Styles für Touch-Geräte
|
||||||
|
*
|
||||||
|
* Wird automatisch bei Bildschirmbreite < 768px aktiv
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
MOBILE STYLES (max-width: 768px)
|
||||||
|
============================================ */
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
|
||||||
|
/* Allgemeine Anpassungen */
|
||||||
|
.mod-stundenzettel .fichecenter {
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tabs kompakter */
|
||||||
|
.mod-stundenzettel .tabsElem a {
|
||||||
|
padding: 8px 10px !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Banner kompakter */
|
||||||
|
.mod-stundenzettel .arearef {
|
||||||
|
padding: 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
LEISTUNGEN TABELLE -> KARTEN
|
||||||
|
============================================ */
|
||||||
|
.mod-stundenzettel table.noborder tr.liste_titre th {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Jede Zeile wird zur Karte */
|
||||||
|
.mod-stundenzettel table.noborder tr.oddeven {
|
||||||
|
display: block;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 12px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mod-stundenzettel table.noborder tr.oddeven td {
|
||||||
|
display: block;
|
||||||
|
text-align: left !important;
|
||||||
|
padding: 4px 0;
|
||||||
|
border: none !important;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Labels vor den Werten (via CSS content) */
|
||||||
|
.mod-stundenzettel table.noborder tr.oddeven td:before {
|
||||||
|
content: attr(data-label);
|
||||||
|
font-weight: bold;
|
||||||
|
color: #666;
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 80px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zeit-Inputs nebeneinander */
|
||||||
|
.mod-stundenzettel input[type="time"],
|
||||||
|
.mod-stundenzettel select[name="time_start"],
|
||||||
|
.mod-stundenzettel select[name="time_end"] {
|
||||||
|
width: 45% !important;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 16px !important; /* Verhindert Zoom auf iOS */
|
||||||
|
padding: 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Textarea volle Breite */
|
||||||
|
.mod-stundenzettel textarea {
|
||||||
|
width: 100% !important;
|
||||||
|
min-height: 80px;
|
||||||
|
font-size: 16px !important;
|
||||||
|
padding: 10px !important;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Number Inputs größer */
|
||||||
|
.mod-stundenzettel input[type="number"] {
|
||||||
|
width: 80px !important;
|
||||||
|
font-size: 18px !important;
|
||||||
|
padding: 12px 8px !important;
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Text Inputs */
|
||||||
|
.mod-stundenzettel input[type="text"] {
|
||||||
|
width: 100% !important;
|
||||||
|
font-size: 16px !important;
|
||||||
|
padding: 10px !important;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
BUTTONS - TOUCH-FREUNDLICH
|
||||||
|
============================================ */
|
||||||
|
.mod-stundenzettel .fas,
|
||||||
|
.mod-stundenzettel .far {
|
||||||
|
font-size: 1.5em !important;
|
||||||
|
padding: 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Action Buttons (Plus/Minus/Save/Delete) */
|
||||||
|
.mod-stundenzettel td .fa-plus-circle,
|
||||||
|
.mod-stundenzettel td .fa-minus-circle,
|
||||||
|
.mod-stundenzettel td .fa-save,
|
||||||
|
.mod-stundenzettel td .fa-trash,
|
||||||
|
.mod-stundenzettel td .fa-pencil-alt {
|
||||||
|
font-size: 1.8em !important;
|
||||||
|
padding: 12px !important;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button-Reihe horizontal */
|
||||||
|
.mod-stundenzettel .mobile-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
PRODUKTE SECTION
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
/* Produkt-Karte */
|
||||||
|
.mod-stundenzettel table.noborder tr.oddeven td:first-child {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 14px;
|
||||||
|
padding-bottom: 8px !important;
|
||||||
|
border-bottom: 1px solid #eee !important;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mengen-Steuerung als Inline-Block */
|
||||||
|
.mod-stundenzettel .qty-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 15px;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Plus/Minus Buttons größer */
|
||||||
|
.mod-stundenzettel a[onclick*="plusQty"],
|
||||||
|
.mod-stundenzettel a[onclick*="Qty"] {
|
||||||
|
padding: 15px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
SUMMENZEILEN
|
||||||
|
============================================ */
|
||||||
|
.mod-stundenzettel tr.liste_total {
|
||||||
|
display: block;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border: 2px solid #28a745;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 15px;
|
||||||
|
margin: 10px 0;
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mod-stundenzettel tr.liste_total td {
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center !important;
|
||||||
|
padding: 5px !important;
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
SECTION HEADER
|
||||||
|
============================================ */
|
||||||
|
.mod-stundenzettel h3 {
|
||||||
|
font-size: 18px;
|
||||||
|
padding: 10px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 15px 0 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mod-stundenzettel tr.liste_titre th[colspan] {
|
||||||
|
display: block !important;
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 12px !important;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 15px 0 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
FORMULAR-CARDS (Hinzufügen)
|
||||||
|
============================================ */
|
||||||
|
.mod-stundenzettel tr.oddeven form {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mod-stundenzettel tr.oddeven td[colspan] {
|
||||||
|
padding: 10px 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Produkt-Auswahl Dropdown */
|
||||||
|
.mod-stundenzettel select.minwidth200,
|
||||||
|
.mod-stundenzettel select.minwidth300 {
|
||||||
|
width: 100% !important;
|
||||||
|
font-size: 16px !important;
|
||||||
|
padding: 12px !important;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
MERKZETTEL/NOTIZEN
|
||||||
|
============================================ */
|
||||||
|
.mod-stundenzettel .fa-check-square,
|
||||||
|
.mod-stundenzettel .fa-square {
|
||||||
|
font-size: 1.8em !important;
|
||||||
|
padding: 10px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
BADGES
|
||||||
|
============================================ */
|
||||||
|
.mod-stundenzettel .badge {
|
||||||
|
font-size: 12px !important;
|
||||||
|
padding: 4px 8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
ACTION BUTTONS (Freigeben/Löschen)
|
||||||
|
============================================ */
|
||||||
|
.mod-stundenzettel .tabsAction {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mod-stundenzettel .tabsAction a.butAction,
|
||||||
|
.mod-stundenzettel .tabsAction a.butActionDelete {
|
||||||
|
flex: 1 1 45%;
|
||||||
|
text-align: center;
|
||||||
|
padding: 15px 10px !important;
|
||||||
|
font-size: 14px !important;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
HIDE UNNECESSARY ELEMENTS ON MOBILE
|
||||||
|
============================================ */
|
||||||
|
.mod-stundenzettel .opacitymedium small {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
SELECT2 DROPDOWNS FIX
|
||||||
|
============================================ */
|
||||||
|
.mod-stundenzettel .select2-container {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mod-stundenzettel .select2-selection {
|
||||||
|
min-height: 44px !important;
|
||||||
|
font-size: 16px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
EXTRA SMALL DEVICES (< 480px)
|
||||||
|
============================================ */
|
||||||
|
@media screen and (max-width: 480px) {
|
||||||
|
|
||||||
|
/* Noch kompaktere Tabs */
|
||||||
|
.mod-stundenzettel .tabsElem a {
|
||||||
|
padding: 6px 8px !important;
|
||||||
|
font-size: 11px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons nebeneinander */
|
||||||
|
.mod-stundenzettel .tabsAction a.butAction,
|
||||||
|
.mod-stundenzettel .tabsAction a.butActionDelete {
|
||||||
|
flex: 1 1 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zeit-Inputs untereinander */
|
||||||
|
.mod-stundenzettel input[type="time"],
|
||||||
|
.mod-stundenzettel select[name="time_start"],
|
||||||
|
.mod-stundenzettel select[name="time_end"] {
|
||||||
|
width: 100% !important;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
LANDSCAPE MODE ADJUSTMENTS
|
||||||
|
============================================ */
|
||||||
|
@media screen and (max-width: 768px) and (orientation: landscape) {
|
||||||
|
.mod-stundenzettel table.noborder tr.oddeven {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mod-stundenzettel table.noborder tr.oddeven td {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
TOUCH FEEDBACK
|
||||||
|
============================================ */
|
||||||
|
@media (hover: none) and (pointer: coarse) {
|
||||||
|
.mod-stundenzettel a:active,
|
||||||
|
.mod-stundenzettel button:active {
|
||||||
|
opacity: 0.7;
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mod-stundenzettel .fas:active,
|
||||||
|
.mod-stundenzettel .far:active {
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -217,7 +217,7 @@ ResetStundenzettel = Status zurücksetzen
|
||||||
StundenzettelReset = Stundenzettel-Status wurde zurückgesetzt
|
StundenzettelReset = Stundenzettel-Status wurde zurückgesetzt
|
||||||
|
|
||||||
# Standard-Leistung
|
# Standard-Leistung
|
||||||
DefaultService = Standard-Leistung
|
DefaultService = Leistungsposition
|
||||||
DefaultServiceDesc = Standard-Dienstleistung für Stundenzettel (wird beim Kunden hinterlegt)
|
DefaultServiceDesc = Standard-Dienstleistung für Stundenzettel (wird beim Kunden hinterlegt)
|
||||||
DefaultServiceFromCustomer = Standard-Leistung vom Kunden
|
DefaultServiceFromCustomer = Standard-Leistung vom Kunden
|
||||||
NoDefaultServiceSet = Keine Standard-Leistung hinterlegt
|
NoDefaultServiceSet = Keine Standard-Leistung hinterlegt
|
||||||
|
|
@ -225,9 +225,27 @@ SetDefaultServiceInCustomer = Standard-Leistung beim Kunden hinterlegen
|
||||||
PlannedHours = Geplante Stunden
|
PlannedHours = Geplante Stunden
|
||||||
TotalHours = Gesamtstunden
|
TotalHours = Gesamtstunden
|
||||||
HoursFromOrder = Stunden aus Auftrag
|
HoursFromOrder = Stunden aus Auftrag
|
||||||
|
NotSet = Nicht gesetzt
|
||||||
|
SelectService = Leistungsposition wählen
|
||||||
|
|
||||||
|
# Stundenpreis
|
||||||
|
HourlyRate = Stundenpreis
|
||||||
|
CustomPrice = Abweichender Preis
|
||||||
|
CustomerPrice = Kundenpreis
|
||||||
|
StandardPrice = Standardpreis
|
||||||
|
CustomHourlyRateSet = Es wurde ein abweichender Stundenpreis gesetzt
|
||||||
|
CustomerSpecificPrice = Kundenspezifischer Preis
|
||||||
|
StandardServicePrice = Standard-Produktpreis
|
||||||
|
ResetToDefault = Auf Standard zurücksetzen
|
||||||
|
Reset = Zurücksetzen
|
||||||
|
|
||||||
# Rechnungsübernahme Stunden
|
# Rechnungsübernahme Stunden
|
||||||
InvoiceHoursMode = Stunden-Übernahme
|
InvoiceHoursMode = Stunden-Übernahme
|
||||||
InvoiceHoursModeTotal = Gesamtstunden auf einer Zeile
|
InvoiceHoursModeTotal = Gesamtstunden auf einer Zeile
|
||||||
InvoiceHoursModePerDay = Pro Tag eine Zeile
|
InvoiceHoursModePerDay = Pro Tag eine Zeile
|
||||||
SelectInvoiceHoursMode = Wie sollen die Arbeitsstunden übernommen werden?
|
SelectInvoiceHoursMode = Wie sollen die Arbeitsstunden übernommen werden?
|
||||||
|
|
||||||
|
# Lieferauflistung Leistungen
|
||||||
|
perStundenzettel = pro Stundenzettel
|
||||||
|
Entries = Einträge
|
||||||
|
incl = inkl.
|
||||||
|
|
|
||||||
|
|
@ -87,3 +87,182 @@ function stundenzettel_prepare_head($object)
|
||||||
|
|
||||||
return $head;
|
return $head;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare array of tabs for Stundenzettel Commande page (order-level view)
|
||||||
|
* Diese Tabs werden immer angezeigt, unabhängig davon ob ein Stundenzettel ausgewählt ist
|
||||||
|
*
|
||||||
|
* @param Commande $order Das Auftrags-Objekt
|
||||||
|
* @param int $stundenzettel_id Optional: ID des ausgewählten Stundenzettels
|
||||||
|
* @return array Array of tabs
|
||||||
|
*/
|
||||||
|
function stundenzettel_commande_prepare_head($order, $stundenzettel_id = 0)
|
||||||
|
{
|
||||||
|
global $db, $langs, $conf, $user;
|
||||||
|
|
||||||
|
$langs->load("stundenzettel@stundenzettel");
|
||||||
|
|
||||||
|
$h = 0;
|
||||||
|
$head = array();
|
||||||
|
|
||||||
|
// Tab 1: Kundenauftrag (Link zurück zum Auftrag)
|
||||||
|
$head[$h][0] = DOL_URL_ROOT.'/commande/card.php?id='.$order->id;
|
||||||
|
$head[$h][1] = $langs->trans("Order");
|
||||||
|
$head[$h][2] = 'order';
|
||||||
|
$h++;
|
||||||
|
|
||||||
|
// Tab 2: Produktliste
|
||||||
|
$head[$h][0] = dol_buildpath('/stundenzettel/stundenzettel_commande.php', 1).'?id='.$order->id.'&tab=products&noredirect=1'.($stundenzettel_id > 0 ? '&stundenzettel_id='.$stundenzettel_id : '');
|
||||||
|
$head[$h][1] = $langs->trans("ProductList");
|
||||||
|
$head[$h][2] = 'products';
|
||||||
|
$h++;
|
||||||
|
|
||||||
|
// Tab 3: Stundenzettel (aktueller/ausgewählter Stundenzettel - card.php)
|
||||||
|
// Wenn kein Stundenzettel ausgewählt, den letzten offenen für diesen Auftrag suchen
|
||||||
|
$activeStundenzettelId = $stundenzettel_id;
|
||||||
|
if ($activeStundenzettelId <= 0) {
|
||||||
|
$sqlActive = "SELECT rowid FROM ".MAIN_DB_PREFIX."stundenzettel";
|
||||||
|
$sqlActive .= " WHERE fk_commande = ".((int)$order->id);
|
||||||
|
$sqlActive .= " AND status = 0"; // Nur Entwürfe
|
||||||
|
$sqlActive .= " ORDER BY date_stundenzettel DESC, rowid DESC";
|
||||||
|
$sqlActive .= " LIMIT 1";
|
||||||
|
$resqlActive = $db->query($sqlActive);
|
||||||
|
if ($resqlActive && $db->num_rows($resqlActive) > 0) {
|
||||||
|
$objActive = $db->fetch_object($resqlActive);
|
||||||
|
$activeStundenzettelId = $objActive->rowid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($activeStundenzettelId > 0) {
|
||||||
|
// Lade Stundenzettel für Badge-Berechnung
|
||||||
|
require_once dol_buildpath('/stundenzettel/class/stundenzettel.class.php', 0);
|
||||||
|
$tmpStz = new Stundenzettel($db);
|
||||||
|
$tmpStz->fetch($activeStundenzettelId);
|
||||||
|
$tmpStz->fetchLeistungen();
|
||||||
|
$tmpStz->fetchProducts();
|
||||||
|
|
||||||
|
$nbItems = count($tmpStz->leistungen) + count($tmpStz->products);
|
||||||
|
|
||||||
|
$head[$h][0] = dol_buildpath('/stundenzettel/card.php', 1).'?id='.$activeStundenzettelId;
|
||||||
|
$head[$h][1] = $langs->trans("Stundenzettel");
|
||||||
|
if ($nbItems > 0) {
|
||||||
|
$head[$h][1] .= '<span class="badge marginleftonlyshort">'.$nbItems.'</span>';
|
||||||
|
}
|
||||||
|
$head[$h][2] = 'card';
|
||||||
|
$h++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tab 4: Alle Stundenzettel (Liste aller Stundenzettel für diesen Auftrag)
|
||||||
|
$sql = "SELECT COUNT(*) as nb FROM ".MAIN_DB_PREFIX."stundenzettel WHERE fk_commande = ".((int)$order->id);
|
||||||
|
$resql = $db->query($sql);
|
||||||
|
$nbStundenzettel = 0;
|
||||||
|
if ($resql && ($obj = $db->fetch_object($resql))) {
|
||||||
|
$nbStundenzettel = $obj->nb;
|
||||||
|
}
|
||||||
|
$head[$h][0] = dol_buildpath('/stundenzettel/stundenzettel_commande.php', 1).'?id='.$order->id.'&tab=stundenzettel&noredirect=1'.($stundenzettel_id > 0 ? '&stundenzettel_id='.$stundenzettel_id : '');
|
||||||
|
$head[$h][1] = $langs->trans("StundenzettelList");
|
||||||
|
if ($nbStundenzettel > 0) {
|
||||||
|
$head[$h][1] .= '<span class="badge marginleftonlyshort">'.$nbStundenzettel.'</span>';
|
||||||
|
}
|
||||||
|
$head[$h][2] = 'stundenzettel';
|
||||||
|
$h++;
|
||||||
|
|
||||||
|
// Tab 4: Lieferauflistung (Tracking)
|
||||||
|
$head[$h][0] = dol_buildpath('/stundenzettel/stundenzettel_commande.php', 1).'?id='.$order->id.'&tab=tracking&noredirect=1'.($stundenzettel_id > 0 ? '&stundenzettel_id='.$stundenzettel_id : '');
|
||||||
|
$head[$h][1] = $langs->trans("DeliveryTracking");
|
||||||
|
$head[$h][2] = 'tracking';
|
||||||
|
$h++;
|
||||||
|
|
||||||
|
return $head;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holt den kundenspezifischen Preis für ein Produkt
|
||||||
|
* Falls kein kundenspezifischer Preis existiert, wird der Standardpreis zurückgegeben
|
||||||
|
*
|
||||||
|
* @param DoliDB $db Datenbankverbindung
|
||||||
|
* @param int $fk_product Produkt-ID
|
||||||
|
* @param int $fk_soc Kunden-ID (Societe)
|
||||||
|
* @param Product|null $product Optional: bereits geladenes Produkt-Objekt
|
||||||
|
* @return array Array mit 'price' (HT), 'price_ttc', 'tva_tx', 'price_base_type', 'is_customer_price'
|
||||||
|
*/
|
||||||
|
function getCustomerPrice($db, $fk_product, $fk_soc, $product = null) {
|
||||||
|
global $conf;
|
||||||
|
|
||||||
|
$now = dol_now();
|
||||||
|
|
||||||
|
// Suche kundenspezifischen Preis in der Tabelle product_customer_price
|
||||||
|
$sql = "SELECT price, price_ttc, tva_tx, price_base_type";
|
||||||
|
$sql .= " FROM ".MAIN_DB_PREFIX."product_customer_price";
|
||||||
|
$sql .= " WHERE fk_product = ".((int)$fk_product);
|
||||||
|
$sql .= " AND fk_soc = ".((int)$fk_soc);
|
||||||
|
$sql .= " AND entity IN (".getEntity('productprice').")";
|
||||||
|
// Prüfe Gültigkeitszeitraum (date_begin <= now und (date_end IS NULL oder date_end >= now))
|
||||||
|
$sql .= " AND date_begin <= '".$db->idate($now)."'";
|
||||||
|
$sql .= " AND (date_end IS NULL OR date_end >= '".$db->idate($now)."')";
|
||||||
|
$sql .= " ORDER BY date_begin DESC";
|
||||||
|
$sql .= " LIMIT 1";
|
||||||
|
|
||||||
|
$resql = $db->query($sql);
|
||||||
|
if ($resql && $db->num_rows($resql) > 0) {
|
||||||
|
$obj = $db->fetch_object($resql);
|
||||||
|
return array(
|
||||||
|
'price' => (float)$obj->price,
|
||||||
|
'price_ttc' => (float)$obj->price_ttc,
|
||||||
|
'tva_tx' => (float)$obj->tva_tx,
|
||||||
|
'price_base_type' => $obj->price_base_type,
|
||||||
|
'is_customer_price' => true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kein kundenspezifischer Preis gefunden - lade Standardpreis vom Produkt
|
||||||
|
if ($product === null) {
|
||||||
|
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
|
||||||
|
$product = new Product($db);
|
||||||
|
$product->fetch($fk_product);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'price' => (float)$product->price,
|
||||||
|
'price_ttc' => (float)$product->price_ttc,
|
||||||
|
'tva_tx' => (float)$product->tva_tx,
|
||||||
|
'price_base_type' => $product->price_base_type,
|
||||||
|
'is_customer_price' => false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holt den effektiven Stundenpreis für einen Stundenzettel
|
||||||
|
* Berücksichtigt: 1. Manuell gesetzter Preis im Stundenzettel
|
||||||
|
* 2. Kundenspezifischer Preis
|
||||||
|
* 3. Standard-Produktpreis
|
||||||
|
*
|
||||||
|
* @param DoliDB $db Datenbankverbindung
|
||||||
|
* @param Stundenzettel $stundenzettel Das Stundenzettel-Objekt
|
||||||
|
* @param int $defaultServiceId ID der Standard-Leistung
|
||||||
|
* @return array Array mit 'price', 'source' ('custom', 'customer', 'standard')
|
||||||
|
*/
|
||||||
|
function getEffectiveHourlyRate($db, $stundenzettel, $defaultServiceId) {
|
||||||
|
// 1. Prüfe ob manueller Preis im Stundenzettel gesetzt
|
||||||
|
if ($stundenzettel->hourly_rate_is_custom && $stundenzettel->hourly_rate !== null) {
|
||||||
|
return array(
|
||||||
|
'price' => (float)$stundenzettel->hourly_rate,
|
||||||
|
'source' => 'custom'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Hole kundenspezifischen oder Standard-Preis
|
||||||
|
if ($defaultServiceId > 0) {
|
||||||
|
$priceInfo = getCustomerPrice($db, $defaultServiceId, $stundenzettel->fk_soc);
|
||||||
|
return array(
|
||||||
|
'price' => $priceInfo['price'],
|
||||||
|
'source' => $priceInfo['is_customer_price'] ? 'customer' : 'standard'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Kein Preis verfügbar
|
||||||
|
return array(
|
||||||
|
'price' => 0,
|
||||||
|
'source' => 'none'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,10 @@ CREATE TABLE llx_stundenzettel (
|
||||||
-- Status: 0=Entwurf, 1=Freigegeben, 2=In Rechnung übertragen, 9=Storniert
|
-- Status: 0=Entwurf, 1=Freigegeben, 2=In Rechnung übertragen, 9=Storniert
|
||||||
status TINYINT DEFAULT 0 NOT NULL,
|
status TINYINT DEFAULT 0 NOT NULL,
|
||||||
|
|
||||||
|
-- Stundenpreis (abweichend vom Standard)
|
||||||
|
hourly_rate DOUBLE(24,8) DEFAULT NULL, -- Stundenpreis (NULL = Standard verwenden)
|
||||||
|
hourly_rate_is_custom TINYINT DEFAULT 0 NOT NULL, -- 1 = manuell geändert
|
||||||
|
|
||||||
-- Notizen
|
-- Notizen
|
||||||
note_private TEXT,
|
note_private TEXT,
|
||||||
note_public TEXT,
|
note_public TEXT,
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ CREATE TABLE llx_stundenzettel_leistung (
|
||||||
rowid INTEGER AUTO_INCREMENT PRIMARY KEY,
|
rowid INTEGER AUTO_INCREMENT PRIMARY KEY,
|
||||||
fk_stundenzettel INTEGER NOT NULL, -- Verknüpfung zum Stundenzettel
|
fk_stundenzettel INTEGER NOT NULL, -- Verknüpfung zum Stundenzettel
|
||||||
fk_user INTEGER DEFAULT NULL, -- Welcher Mitarbeiter
|
fk_user INTEGER DEFAULT NULL, -- Welcher Mitarbeiter
|
||||||
|
fk_product INTEGER DEFAULT NULL, -- Verknüpfung zur Leistungsposition (Dienstleistung)
|
||||||
|
|
||||||
-- Zeitraum
|
-- Zeitraum
|
||||||
date_leistung DATE NOT NULL, -- Datum der Leistung
|
date_leistung DATE NOT NULL, -- Datum der Leistung
|
||||||
|
|
|
||||||
8
sql/update_1.2.0.sql
Normal file
8
sql/update_1.2.0.sql
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
-- ============================================================================
|
||||||
|
-- Stundenzettel Update auf Version 1.2.0
|
||||||
|
-- Fügt Leistungsposition zu Arbeitszeiten hinzu
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- Leistungsposition (Dienstleistung) pro Arbeitszeit
|
||||||
|
ALTER TABLE llx_stundenzettel_leistung
|
||||||
|
ADD COLUMN IF NOT EXISTS fk_product INTEGER DEFAULT NULL COMMENT 'Verknüpfung zur Leistungsposition (Dienstleistung)';
|
||||||
|
|
@ -17,6 +17,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/order.lib.php';
|
||||||
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
|
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
|
||||||
require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
|
require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
|
||||||
dol_include_once('/stundenzettel/class/stundenzettel.class.php');
|
dol_include_once('/stundenzettel/class/stundenzettel.class.php');
|
||||||
|
dol_include_once('/stundenzettel/lib/stundenzettel.lib.php');
|
||||||
|
|
||||||
// Load translation files
|
// Load translation files
|
||||||
$langs->loadLangs(array("stundenzettel@stundenzettel", "orders", "products"));
|
$langs->loadLangs(array("stundenzettel@stundenzettel", "orders", "products"));
|
||||||
|
|
@ -712,11 +713,16 @@ if ($action == 'confirm_transfer_invoice' && GETPOST('confirm', 'alpha') == 'yes
|
||||||
$product = new Product($db);
|
$product = new Product($db);
|
||||||
$product->fetch($addProd->fk_product);
|
$product->fetch($addProd->fk_product);
|
||||||
|
|
||||||
|
// Kundenspezifischen Preis holen (falls vorhanden)
|
||||||
|
$customerPriceInfo = getCustomerPrice($db, $addProd->fk_product, $order->socid, $product);
|
||||||
|
$usePrice = $customerPriceInfo['price'];
|
||||||
|
$useTvaTx = $customerPriceInfo['tva_tx'];
|
||||||
|
|
||||||
$productResult = $facture->addline(
|
$productResult = $facture->addline(
|
||||||
$product->label, // 1: desc
|
$product->label, // 1: desc
|
||||||
$product->price, // 2: pu_ht
|
$usePrice, // 2: pu_ht - kundenspezifischer Preis
|
||||||
$addProd->total_qty, // 3: qty
|
$addProd->total_qty, // 3: qty
|
||||||
$product->tva_tx, // 4: txtva
|
$useTvaTx, // 4: txtva - kundenspezifischer MwSt-Satz
|
||||||
0, // 5: txlocaltax1
|
0, // 5: txlocaltax1
|
||||||
0, // 6: txlocaltax2
|
0, // 6: txlocaltax2
|
||||||
$addProd->fk_product, // 7: fk_product
|
$addProd->fk_product, // 7: fk_product
|
||||||
|
|
@ -808,6 +814,137 @@ if ($action == 'confirm_transfer_invoice' && GETPOST('confirm', 'alpha') == 'yes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// ARBEITSZEITEN aus Stundenzetteln hinzufügen
|
||||||
|
// (Gruppiert nach Leistungsposition/Produkt)
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
// Standard-Leistung vom Kunden laden (Fallback wenn keine Leistung gewählt)
|
||||||
|
$societe = new Societe($db);
|
||||||
|
$societe->fetch($order->socid);
|
||||||
|
$societe->fetch_optionals();
|
||||||
|
$defaultServiceId = isset($societe->array_options['options_stundenzettel_default_service']) ? (int)$societe->array_options['options_stundenzettel_default_service'] : 0;
|
||||||
|
|
||||||
|
// Alle Arbeitszeiten nach Leistungsposition gruppiert sammeln
|
||||||
|
$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 .= " 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"; // Nur validierte Stundenzettel
|
||||||
|
$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 .= " ORDER BY p.label";
|
||||||
|
|
||||||
|
$resqlHours = $db->query($sqlHours);
|
||||||
|
$hasWorkHours = ($resqlHours && $db->num_rows($resqlHours) > 0);
|
||||||
|
|
||||||
|
if ($hasWorkHours) {
|
||||||
|
// Arbeitszeit-Section erstellen (nur wenn Sections im Auftrag)
|
||||||
|
$arbeitszeitSectionResult = 0;
|
||||||
|
if ($orderHasSections) {
|
||||||
|
$arbeitszeitSectionResult = $facture->addline(
|
||||||
|
$langs->trans('Leistungen'),
|
||||||
|
0, 0, 0, 0, 0, 0, 0, '', '', 0, 0, '', 'HT', 0,
|
||||||
|
9, // product_type für Titel
|
||||||
|
$rang++,
|
||||||
|
100 // special_code = 100 für Section
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($arbeitszeitSectionResult > 0) {
|
||||||
|
$invoiceManagerLines[] = array(
|
||||||
|
'type' => 'section',
|
||||||
|
'fk_facturedet' => $arbeitszeitSectionResult,
|
||||||
|
'title' => $langs->trans('Leistungen'),
|
||||||
|
'parent' => null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arbeitszeiten hinzufügen (pro Leistungsposition)
|
||||||
|
$arbeitszeitSubtotal = 0;
|
||||||
|
while ($objHours = $db->fetch_object($resqlHours)) {
|
||||||
|
$productId = (int)$objHours->product_id;
|
||||||
|
$hoursWorked = $objHours->total_minutes / 60;
|
||||||
|
|
||||||
|
// Produkt-Preis ermitteln (kundenspezifisch oder Standard)
|
||||||
|
$priceInfo = getCustomerPrice($db, $productId, $order->socid);
|
||||||
|
$hourlyPrice = $priceInfo['price'];
|
||||||
|
$tvaTx = !empty($objHours->tva_tx) ? $objHours->tva_tx : $priceInfo['tva_tx'];
|
||||||
|
|
||||||
|
// Beschreibung
|
||||||
|
$lineDesc = !empty($objHours->product_label) ? $objHours->product_label : $langs->trans('DefaultService');
|
||||||
|
|
||||||
|
// Rechnungszeile hinzufügen
|
||||||
|
$hoursResult = $facture->addline(
|
||||||
|
$lineDesc, // 1: desc
|
||||||
|
$hourlyPrice, // 2: pu_ht
|
||||||
|
$hoursWorked, // 3: qty (Stunden)
|
||||||
|
$tvaTx, // 4: txtva
|
||||||
|
0, // 5: txlocaltax1
|
||||||
|
0, // 6: txlocaltax2
|
||||||
|
$productId, // 7: fk_product
|
||||||
|
0, // 8: remise_percent
|
||||||
|
'', // 9: date_start
|
||||||
|
'', // 10: date_end
|
||||||
|
0, // 11: fk_code_ventilation
|
||||||
|
0, // 12: info_bits
|
||||||
|
0, // 13: fk_remise_except
|
||||||
|
'HT', // 14: price_base_type
|
||||||
|
0, // 15: pu_ttc
|
||||||
|
1, // 16: type (1 = Dienstleistung)
|
||||||
|
$rang++, // 17: rang
|
||||||
|
0, // 18: special_code
|
||||||
|
'', // 19: origin
|
||||||
|
0, // 20: origin_id
|
||||||
|
0, // 21: fk_parent_line
|
||||||
|
null, // 22: fk_fournprice
|
||||||
|
0, // 23: pa_ht
|
||||||
|
'', // 24: label
|
||||||
|
array(), // 25: array_options
|
||||||
|
100, // 26: situation_percent
|
||||||
|
0, // 27: fk_prev_id
|
||||||
|
$objHours->fk_unit // 28: fk_unit
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($hoursResult > 0) {
|
||||||
|
if ($orderHasSections && $arbeitszeitSectionResult > 0) {
|
||||||
|
$invoiceManagerLines[] = array(
|
||||||
|
'type' => 'product',
|
||||||
|
'fk_facturedet' => $hoursResult,
|
||||||
|
'title' => null,
|
||||||
|
'parent' => $arbeitszeitSectionResult
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$arbeitszeitSubtotal += $hourlyPrice * $hoursWorked;
|
||||||
|
} elseif ($hoursResult < 0) {
|
||||||
|
$error++;
|
||||||
|
setEventMessages($facture->error, $facture->errors, 'errors');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zwischensumme für Arbeitszeit
|
||||||
|
if ($arbeitszeitSectionResult > 0 && $arbeitszeitSubtotal > 0) {
|
||||||
|
$subtotalLabel = 'Zwischensumme: '.$langs->trans('Leistungen');
|
||||||
|
$subtotalResult = $facture->addline(
|
||||||
|
$subtotalLabel,
|
||||||
|
0, 1, 0, 0, 0, 0, 0, '', '', 0, 0, '', 'HT', 0,
|
||||||
|
9, $rang++, 102, 0, '', 0, 0
|
||||||
|
);
|
||||||
|
if ($subtotalResult > 0) {
|
||||||
|
$invoiceManagerLines[] = array(
|
||||||
|
'type' => 'subtotal',
|
||||||
|
'fk_facturedet' => $subtotalResult,
|
||||||
|
'title' => $subtotalLabel,
|
||||||
|
'parent' => $arbeitszeitSectionResult
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// llx_facture_lines_manager für Rechnung erstellen
|
// llx_facture_lines_manager für Rechnung erstellen
|
||||||
if (!$error && count($invoiceManagerLines) > 0) {
|
if (!$error && count($invoiceManagerLines) > 0) {
|
||||||
$lineOrder = 1;
|
$lineOrder = 1;
|
||||||
|
|
@ -891,7 +1028,6 @@ $_GET['mainmenu'] = 'stundenzettel';
|
||||||
// Stundenzettel laden wenn ID übergeben wurde
|
// Stundenzettel laden wenn ID übergeben wurde
|
||||||
$stundenzettelObj = null;
|
$stundenzettelObj = null;
|
||||||
if ($stundenzettel_id > 0) {
|
if ($stundenzettel_id > 0) {
|
||||||
dol_include_once('/stundenzettel/lib/stundenzettel.lib.php');
|
|
||||||
$stundenzettelObj = new Stundenzettel($db);
|
$stundenzettelObj = new Stundenzettel($db);
|
||||||
if ($stundenzettelObj->fetch($stundenzettel_id) <= 0) {
|
if ($stundenzettelObj->fetch($stundenzettel_id) <= 0) {
|
||||||
$stundenzettelObj = null;
|
$stundenzettelObj = null;
|
||||||
|
|
@ -947,22 +1083,15 @@ $hasRemainingProducts = (count($remainingProducts) > 0);
|
||||||
$title = $langs->trans("Stundenzettel").' - '.$order->ref;
|
$title = $langs->trans("Stundenzettel").' - '.$order->ref;
|
||||||
llxHeader('', $title, '', '', 0, 0, '', '', '', 'mod-stundenzettel page-commande');
|
llxHeader('', $title, '', '', 0, 0, '', '', '', 'mod-stundenzettel page-commande');
|
||||||
|
|
||||||
// Tabs - Stundenzettel-Tabs wenn aus Stundenzettel aufgerufen, sonst Auftrags-Tabs
|
// Mobile CSS einbinden
|
||||||
// Aktiven Tab-Key basierend auf $tab bestimmen
|
print '<link rel="stylesheet" type="text/css" href="'.dol_buildpath('/stundenzettel/css/stundenzettel-mobile.css', 1).'?v='.filemtime(dol_buildpath('/stundenzettel/css/stundenzettel-mobile.css', 0)).'">';
|
||||||
$activeTabKey = 'productlist'; // Default
|
|
||||||
if ($tab == 'stundenzettel') {
|
|
||||||
$activeTabKey = 'stundenzettel_list';
|
|
||||||
} elseif ($tab == 'tracking') {
|
|
||||||
$activeTabKey = 'tracking';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($stundenzettelObj) {
|
// Tabs - Immer die Stundenzettel-Commande-Tabs verwenden
|
||||||
$head = stundenzettel_prepare_head($stundenzettelObj);
|
// Aktiven Tab-Key basierend auf $tab bestimmen
|
||||||
dol_fiche_head($head, $activeTabKey, $langs->trans("Stundenzettel"), -1, 'clock');
|
$activeTabKey = $tab; // products, stundenzettel, oder tracking
|
||||||
} else {
|
|
||||||
$head = commande_prepare_head($order);
|
$head = stundenzettel_commande_prepare_head($order, $stundenzettel_id);
|
||||||
dol_fiche_head($head, 'stundenzettel', $langs->trans("CustomerOrder"), -1, 'order');
|
dol_fiche_head($head, $activeTabKey, $langs->trans("Stundenzettel"), -1, 'clock');
|
||||||
}
|
|
||||||
|
|
||||||
$form = new Form($db);
|
$form = new Form($db);
|
||||||
|
|
||||||
|
|
@ -1070,8 +1199,17 @@ if ($defaultServiceProduct || $plannedHours > 0) {
|
||||||
print '<td style="padding-right:20px;"><strong>'.img_picto('', 'service', 'class="pictofixedwidth"').$langs->trans("DefaultService").':</strong></td>';
|
print '<td style="padding-right:20px;"><strong>'.img_picto('', 'service', 'class="pictofixedwidth"').$langs->trans("DefaultService").':</strong></td>';
|
||||||
print '<td>';
|
print '<td>';
|
||||||
if ($defaultServiceProduct) {
|
if ($defaultServiceProduct) {
|
||||||
|
// Kundenspezifischen Preis für die Standard-Leistung holen
|
||||||
|
$defaultServicePriceInfo = getCustomerPrice($db, $defaultServiceProduct->id, $order->socid, $defaultServiceProduct);
|
||||||
|
$displayPrice = $defaultServicePriceInfo['price'];
|
||||||
|
$isCustomerPrice = $defaultServicePriceInfo['is_customer_price'];
|
||||||
|
|
||||||
print $defaultServiceProduct->getNomUrl(1).' - '.$defaultServiceProduct->label;
|
print $defaultServiceProduct->getNomUrl(1).' - '.$defaultServiceProduct->label;
|
||||||
print ' <span class="opacitymedium">('.price($defaultServiceProduct->price, 0, $langs, 1, -1, -1, $conf->currency).'/Std.)</span>';
|
print ' <span class="opacitymedium">('.price($displayPrice, 0, $langs, 1, -1, -1, $conf->currency).'/Std.)';
|
||||||
|
if ($isCustomerPrice) {
|
||||||
|
print ' <span class="badge badge-status4" title="'.$langs->trans("CustomerSpecificPrice").'">Kundenpreis</span>';
|
||||||
|
}
|
||||||
|
print '</span>';
|
||||||
} else {
|
} else {
|
||||||
print '<span class="opacitymedium">'.$langs->trans("NoDefaultServiceSet").'</span>';
|
print '<span class="opacitymedium">'.$langs->trans("NoDefaultServiceSet").'</span>';
|
||||||
print ' <a href="'.DOL_URL_ROOT.'/societe/card.php?socid='.$order->socid.'" class="button small">'.$langs->trans("SetDefaultServiceInCustomer").'</a>';
|
print ' <a href="'.DOL_URL_ROOT.'/societe/card.php?socid='.$order->socid.'" class="button small">'.$langs->trans("SetDefaultServiceInCustomer").'</a>';
|
||||||
|
|
@ -2048,7 +2186,15 @@ if ($tab == 'tracking') {
|
||||||
$resqlDetails = $db->query($sqlDetails);
|
$resqlDetails = $db->query($sqlDetails);
|
||||||
if ($resqlDetails) {
|
if ($resqlDetails) {
|
||||||
while ($objD = $db->fetch_object($resqlDetails)) {
|
while ($objD = $db->fetch_object($resqlDetails)) {
|
||||||
$key = $objD->fk_commandedet > 0 ? $objD->fk_commandedet : 'prod_'.$objD->fk_product;
|
// Key-Logik: fk_commandedet > prod_X > freetext_hash
|
||||||
|
if ($objD->fk_commandedet > 0) {
|
||||||
|
$key = $objD->fk_commandedet;
|
||||||
|
} elseif ($objD->fk_product > 0) {
|
||||||
|
$key = 'prod_'.$objD->fk_product;
|
||||||
|
} else {
|
||||||
|
// Freitext: Hash der Beschreibung als Key
|
||||||
|
$key = 'freetext_'.md5(trim($objD->description));
|
||||||
|
}
|
||||||
if (!isset($trackingDetails[$key])) {
|
if (!isset($trackingDetails[$key])) {
|
||||||
$trackingDetails[$key] = array();
|
$trackingDetails[$key] = array();
|
||||||
}
|
}
|
||||||
|
|
@ -2082,14 +2228,15 @@ if ($tab == 'tracking') {
|
||||||
$sql .= " COALESCE((SELECT SUM(sp.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp";
|
$sql .= " COALESCE((SELECT SUM(sp.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp";
|
||||||
$sql .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = sp.fk_stundenzettel";
|
$sql .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = sp.fk_stundenzettel";
|
||||||
$sql .= " WHERE sp.fk_commandedet = cd.rowid AND sp.origin IN ('order', 'added')), 0) as qty_delivered,";
|
$sql .= " WHERE sp.fk_commandedet = cd.rowid AND sp.origin IN ('order', 'added')), 0) as qty_delivered,";
|
||||||
// Mehraufwand
|
// Mehraufwand - nur für Produkte mit fk_product > 0 (Freitext-Mehraufwand wird separat angezeigt)
|
||||||
$sql .= " COALESCE((SELECT SUM(sp2.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp2";
|
$sql .= " COALESCE((SELECT SUM(sp2.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp2";
|
||||||
$sql .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s2 ON s2.rowid = sp2.fk_stundenzettel";
|
$sql .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s2 ON s2.rowid = sp2.fk_stundenzettel";
|
||||||
$sql .= " WHERE sp2.fk_product = cd.fk_product AND sp2.origin = 'additional' AND s2.fk_commande = ".((int)$order->id)."), 0) as qty_additional,";
|
$sql .= " WHERE sp2.fk_product = cd.fk_product AND cd.fk_product > 0 AND sp2.origin = 'additional' AND s2.fk_commande = ".((int)$order->id)."), 0) as qty_additional,";
|
||||||
// Entfällt
|
// Entfällt - für Produkte via fk_product, für Freitext via fk_commandedet
|
||||||
$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_omitted";
|
$sql .= " WHERE sp3.origin = 'omitted' AND s3.fk_commande = ".((int)$order->id);
|
||||||
|
$sql .= " AND ((sp3.fk_product = cd.fk_product AND cd.fk_product > 0) OR sp3.fk_commandedet = cd.rowid)), 0) as qty_omitted";
|
||||||
$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);
|
||||||
|
|
@ -2264,6 +2411,165 @@ if ($tab == 'tracking') {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// ZUSÄTZLICHE PRODUKTE: Mehraufwand die NICHT im Auftrag sind
|
||||||
|
// (Produkte und Freitext die direkt als Mehraufwand hinzugefügt wurden)
|
||||||
|
// =============================================
|
||||||
|
|
||||||
|
// Alle Produkt-IDs aus dem Auftrag sammeln (für Ausschluss)
|
||||||
|
$orderProductIds = array();
|
||||||
|
$sqlOrderProds = "SELECT fk_product FROM ".MAIN_DB_PREFIX."commandedet WHERE fk_commande = ".((int)$order->id)." AND fk_product > 0";
|
||||||
|
$resOrderProds = $db->query($sqlOrderProds);
|
||||||
|
if ($resOrderProds) {
|
||||||
|
while ($objOP = $db->fetch_object($resOrderProds)) {
|
||||||
|
$orderProductIds[] = $objOP->fk_product;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mehraufwand-Produkte laden die NICHT im Auftrag sind
|
||||||
|
$sqlMehraufwand = "SELECT sp.fk_product, sp.description,";
|
||||||
|
$sqlMehraufwand .= " p.ref as product_ref, p.label as product_label,";
|
||||||
|
$sqlMehraufwand .= " SUM(sp.qty) as qty_ordered,";
|
||||||
|
$sqlMehraufwand .= " SUM(sp.qty_done) as qty_delivered";
|
||||||
|
$sqlMehraufwand .= " FROM ".MAIN_DB_PREFIX."stundenzettel_product sp";
|
||||||
|
$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 .= " WHERE s.fk_commande = ".((int)$order->id);
|
||||||
|
$sqlMehraufwand .= " AND sp.origin = 'additional'";
|
||||||
|
if (!empty($orderProductIds)) {
|
||||||
|
$sqlMehraufwand .= " AND (sp.fk_product NOT IN (".implode(',', $orderProductIds).") OR sp.fk_product = 0)";
|
||||||
|
}
|
||||||
|
$sqlMehraufwand .= " GROUP BY sp.fk_product, sp.description, p.ref, p.label";
|
||||||
|
$sqlMehraufwand .= " ORDER BY p.ref, sp.description";
|
||||||
|
|
||||||
|
$resqlMehr = $db->query($sqlMehraufwand);
|
||||||
|
$hasMehraufwandProducts = false;
|
||||||
|
|
||||||
|
if ($resqlMehr && $db->num_rows($resqlMehr) > 0) {
|
||||||
|
// Separator-Zeile für Mehraufwand
|
||||||
|
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 '</tr>';
|
||||||
|
|
||||||
|
while ($objMehr = $db->fetch_object($resqlMehr)) {
|
||||||
|
$hasMehraufwandProducts = true;
|
||||||
|
$detailRowId++;
|
||||||
|
$qty_ordered_mehr = (float)$objMehr->qty_ordered;
|
||||||
|
$qty_delivered_mehr = (float)$objMehr->qty_delivered;
|
||||||
|
$qty_remaining_mehr = $qty_ordered_mehr - $qty_delivered_mehr;
|
||||||
|
|
||||||
|
// Details für Mehraufwand laden
|
||||||
|
$detailsMehr = array();
|
||||||
|
if ($objMehr->fk_product > 0) {
|
||||||
|
$prodKey = 'prod_'.$objMehr->fk_product;
|
||||||
|
if (isset($trackingDetails[$prodKey])) {
|
||||||
|
$detailsMehr = $trackingDetails[$prodKey];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Freitext: Hash der Beschreibung als Key
|
||||||
|
$freetextKey = 'freetext_'.md5(trim($objMehr->description));
|
||||||
|
if (isset($trackingDetails[$freetextKey])) {
|
||||||
|
$detailsMehr = $trackingDetails[$freetextKey];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$hasDetailsMehr = !empty($detailsMehr);
|
||||||
|
|
||||||
|
print '<tr class="oddeven">';
|
||||||
|
|
||||||
|
// Produkt/Beschreibung
|
||||||
|
print '<td>';
|
||||||
|
if ($hasDetailsMehr) {
|
||||||
|
print '<span class="tracking-toggle" onclick="toggleTrackingDetails(\'tdetail_'.$detailRowId.'\')" style="cursor:pointer; margin-right:5px;">';
|
||||||
|
print '<span class="fas fa-chevron-right tracking-arrow" id="tarrow_tdetail_'.$detailRowId.'"></span>';
|
||||||
|
print '</span>';
|
||||||
|
}
|
||||||
|
print '<span class="badge badge-success" style="margin-right:5px;">'.$langs->trans("Mehraufwand").'</span>';
|
||||||
|
if ($objMehr->fk_product > 0) {
|
||||||
|
print '<a href="'.DOL_URL_ROOT.'/product/card.php?id='.$objMehr->fk_product.'">';
|
||||||
|
print img_picto('', 'product', 'class="pictofixedwidth"');
|
||||||
|
print $objMehr->product_ref.' - '.$objMehr->product_label;
|
||||||
|
print '</a>';
|
||||||
|
} else {
|
||||||
|
// Freitext
|
||||||
|
print img_picto('', 'generic', 'class="pictofixedwidth"');
|
||||||
|
$desc = !empty($objMehr->description) ? strip_tags($objMehr->description) : '-';
|
||||||
|
if (strlen($desc) > 50) $desc = substr($desc, 0, 47).'...';
|
||||||
|
print '<span class="opacitymedium">'.$desc.'</span>';
|
||||||
|
}
|
||||||
|
print '</td>';
|
||||||
|
|
||||||
|
// Bestellt (Mehraufwand-Menge)
|
||||||
|
print '<td class="right"><strong>'.formatQty($qty_ordered_mehr).'</strong></td>';
|
||||||
|
$total_ordered += $qty_ordered_mehr;
|
||||||
|
|
||||||
|
// Geliefert
|
||||||
|
print '<td class="right">'.formatQty($qty_delivered_mehr).'</td>';
|
||||||
|
$total_delivered += $qty_delivered_mehr;
|
||||||
|
|
||||||
|
// Verbleibend
|
||||||
|
print '<td class="right">';
|
||||||
|
if ($qty_remaining_mehr > 0) {
|
||||||
|
print '<span class="badge badge-warning">'.formatQty($qty_remaining_mehr).'</span>';
|
||||||
|
} elseif ($qty_remaining_mehr == 0) {
|
||||||
|
print '<span class="badge badge-success">0</span>';
|
||||||
|
} else {
|
||||||
|
print '<span class="badge badge-info">'.formatQty($qty_remaining_mehr).'</span>';
|
||||||
|
}
|
||||||
|
print '</td>';
|
||||||
|
$total_remaining += $qty_remaining_mehr;
|
||||||
|
|
||||||
|
// Status
|
||||||
|
print '<td>';
|
||||||
|
if ($qty_remaining_mehr <= 0) {
|
||||||
|
print '<span class="badge badge-success">'.$langs->trans("TrackingDone").'</span>';
|
||||||
|
} elseif ($qty_delivered_mehr > 0) {
|
||||||
|
print '<span class="badge badge-warning">'.$langs->trans("TrackingPartial").'</span>';
|
||||||
|
} else {
|
||||||
|
print '<span class="badge badge-secondary">'.$langs->trans("TrackingOpen").'</span>';
|
||||||
|
}
|
||||||
|
print '</td>';
|
||||||
|
|
||||||
|
print '</tr>';
|
||||||
|
|
||||||
|
// Detail-Zeile für Mehraufwand
|
||||||
|
if ($hasDetailsMehr) {
|
||||||
|
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 '<table class="noborder" style="width:100%; margin: 5px 0;">';
|
||||||
|
print '<tr class="liste_titre" style="background-color: #f0f0f0;">';
|
||||||
|
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("Type").'</th>';
|
||||||
|
print '<th class="right" style="width:80px;">'.$langs->trans("Qty").'</th>';
|
||||||
|
print '<th>'.$langs->trans("Reason").'</th>';
|
||||||
|
print '</tr>';
|
||||||
|
|
||||||
|
foreach ($detailsMehr as $det) {
|
||||||
|
print '<tr class="oddeven">';
|
||||||
|
print '<td><a href="'.dol_buildpath('/stundenzettel/card.php', 1).'?id='.$det->fk_stundenzettel.'">'.$det->stz_ref.'</a></td>';
|
||||||
|
print '<td>'.dol_print_date($db->jdate($det->date_stundenzettel), 'day').'</td>';
|
||||||
|
print '<td><span class="badge badge-success">'.$langs->trans("Mehraufwand").'</span></td>';
|
||||||
|
print '<td class="right">'.formatQty($det->qty_done).'</td>';
|
||||||
|
print '<td>'.((!empty($det->description)) ? dol_escape_htmltag($det->description) : '<span class="opacitymedium">-</span>').'</td>';
|
||||||
|
print '</tr>';
|
||||||
|
}
|
||||||
|
|
||||||
|
print '</table>';
|
||||||
|
print '</td>';
|
||||||
|
print '</tr>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neue Summenzeile mit Mehraufwand
|
||||||
|
print '<tr class="liste_total">';
|
||||||
|
print '<td><strong>'.$langs->trans("Total").' ('.$langs->trans("Mehraufwand").' '.$langs->trans("incl").')</strong></td>';
|
||||||
|
print '<td class="right"><strong>'.formatQty($total_ordered).'</strong></td>';
|
||||||
|
print '<td class="right"><strong>'.formatQty($total_delivered).'</strong></td>';
|
||||||
|
print '<td class="right"><strong>'.formatQty($total_remaining).'</strong></td>';
|
||||||
|
print '<td></td>';
|
||||||
|
print '</tr>';
|
||||||
|
}
|
||||||
|
|
||||||
print '</table>';
|
print '</table>';
|
||||||
print '</div>';
|
print '</div>';
|
||||||
|
|
||||||
|
|
@ -2307,9 +2613,176 @@ if ($tab == 'tracking') {
|
||||||
}
|
}
|
||||||
</script>';
|
</script>';
|
||||||
|
|
||||||
|
// =============================================
|
||||||
|
// BEREICH: LEISTUNGEN / ARBEITSZEITEN
|
||||||
|
// =============================================
|
||||||
|
print '<div class="fichehalfleft" style="margin-top: 20px;">';
|
||||||
|
print '<div class="titre inline-block">'.$langs->trans("Leistungen").' / '.$langs->trans("TotalHours").'</div>';
|
||||||
|
|
||||||
|
// Leistungen nach Leistungsposition gruppiert laden
|
||||||
|
$sqlLeistungen = "SELECT ";
|
||||||
|
$sqlLeistungen .= " COALESCE(l.fk_product, 0) as service_id,";
|
||||||
|
$sqlLeistungen .= " p.ref as service_ref, p.label as service_label,";
|
||||||
|
$sqlLeistungen .= " SUM(l.duration) as total_minutes,";
|
||||||
|
$sqlLeistungen .= " COUNT(l.rowid) as entry_count";
|
||||||
|
$sqlLeistungen .= " FROM ".MAIN_DB_PREFIX."stundenzettel_leistung l";
|
||||||
|
$sqlLeistungen .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = l.fk_stundenzettel";
|
||||||
|
$sqlLeistungen .= " LEFT JOIN ".MAIN_DB_PREFIX."product p ON p.rowid = l.fk_product";
|
||||||
|
$sqlLeistungen .= " WHERE s.fk_commande = ".((int)$order->id);
|
||||||
|
$sqlLeistungen .= " GROUP BY COALESCE(l.fk_product, 0), p.ref, p.label";
|
||||||
|
$sqlLeistungen .= " ORDER BY p.ref, p.label";
|
||||||
|
|
||||||
|
$resqlLeistungen = $db->query($sqlLeistungen);
|
||||||
|
$totalMinutesAll = 0;
|
||||||
|
|
||||||
|
print '<div class="div-table-responsive-no-min">';
|
||||||
|
print '<table class="noborder centpercent">';
|
||||||
|
print '<tr class="liste_titre">';
|
||||||
|
print '<th>'.$langs->trans("DefaultService").'</th>';
|
||||||
|
print '<th class="right">'.$langs->trans("TotalHours").'</th>';
|
||||||
|
print '<th class="right">'.$langs->trans("Entries").'</th>';
|
||||||
|
print '</tr>';
|
||||||
|
|
||||||
|
if ($resqlLeistungen) {
|
||||||
|
$numLeistungen = $db->num_rows($resqlLeistungen);
|
||||||
|
if ($numLeistungen > 0) {
|
||||||
|
while ($objL = $db->fetch_object($resqlLeistungen)) {
|
||||||
|
$totalMinutesAll += $objL->total_minutes;
|
||||||
|
$hours = floor($objL->total_minutes / 60);
|
||||||
|
$mins = $objL->total_minutes % 60;
|
||||||
|
|
||||||
|
print '<tr class="oddeven">';
|
||||||
|
|
||||||
|
// Leistungsposition
|
||||||
|
print '<td>';
|
||||||
|
if ($objL->service_id > 0) {
|
||||||
|
print '<a href="'.DOL_URL_ROOT.'/product/card.php?id='.$objL->service_id.'">';
|
||||||
|
print img_picto('', 'service', 'class="pictofixedwidth"');
|
||||||
|
print $objL->service_ref.' - '.$objL->service_label;
|
||||||
|
print '</a>';
|
||||||
|
} else {
|
||||||
|
print '<span class="opacitymedium">'.img_picto('', 'service', 'class="pictofixedwidth"').$langs->trans("NotSet").'</span>';
|
||||||
|
}
|
||||||
|
print '</td>';
|
||||||
|
|
||||||
|
// Stunden
|
||||||
|
print '<td class="right"><strong>'.sprintf('%d:%02d h', $hours, $mins).'</strong></td>';
|
||||||
|
|
||||||
|
// Anzahl Einträge
|
||||||
|
print '<td class="right">'.$objL->entry_count.'</td>';
|
||||||
|
|
||||||
|
print '</tr>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Summenzeile
|
||||||
|
$totalHours = floor($totalMinutesAll / 60);
|
||||||
|
$totalMins = $totalMinutesAll % 60;
|
||||||
|
print '<tr class="liste_total">';
|
||||||
|
print '<td><strong>'.$langs->trans("Total").'</strong></td>';
|
||||||
|
print '<td class="right"><strong>'.sprintf('%d:%02d h', $totalHours, $totalMins).'</strong></td>';
|
||||||
|
print '<td class="right"></td>';
|
||||||
|
print '</tr>';
|
||||||
|
|
||||||
|
} else {
|
||||||
|
print '<tr class="oddeven"><td colspan="3" class="opacitymedium">'.$langs->trans("NoRecordFound").'</td></tr>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print '</table>';
|
||||||
|
print '</div>';
|
||||||
|
|
||||||
|
// Details pro Stundenzettel
|
||||||
|
print '<br>';
|
||||||
|
print '<div class="titre inline-block">'.$langs->trans("Leistungen").' '.$langs->trans("perStundenzettel").'</div>';
|
||||||
|
|
||||||
|
$sqlLeistDetail = "SELECT l.rowid, l.fk_stundenzettel, l.fk_product, l.date_leistung,";
|
||||||
|
$sqlLeistDetail .= " l.time_start, l.time_end, l.duration, l.description,";
|
||||||
|
$sqlLeistDetail .= " s.ref as stz_ref, s.date_stundenzettel,";
|
||||||
|
$sqlLeistDetail .= " p.ref as service_ref, p.label as service_label";
|
||||||
|
$sqlLeistDetail .= " FROM ".MAIN_DB_PREFIX."stundenzettel_leistung l";
|
||||||
|
$sqlLeistDetail .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = l.fk_stundenzettel";
|
||||||
|
$sqlLeistDetail .= " LEFT JOIN ".MAIN_DB_PREFIX."product p ON p.rowid = l.fk_product";
|
||||||
|
$sqlLeistDetail .= " WHERE s.fk_commande = ".((int)$order->id);
|
||||||
|
$sqlLeistDetail .= " ORDER BY s.date_stundenzettel DESC, l.date_leistung, l.time_start";
|
||||||
|
|
||||||
|
$resqlLeistDetail = $db->query($sqlLeistDetail);
|
||||||
|
|
||||||
|
print '<div class="div-table-responsive-no-min">';
|
||||||
|
print '<table class="noborder centpercent">';
|
||||||
|
print '<tr class="liste_titre">';
|
||||||
|
print '<th>'.$langs->trans("Stundenzettel").'</th>';
|
||||||
|
print '<th>'.$langs->trans("Date").'</th>';
|
||||||
|
print '<th>'.$langs->trans("LeistungTimeStart").' - '.$langs->trans("LeistungTimeEnd").'</th>';
|
||||||
|
print '<th class="right">'.$langs->trans("LeistungDuration").'</th>';
|
||||||
|
print '<th>'.$langs->trans("DefaultService").'</th>';
|
||||||
|
print '<th>'.$langs->trans("Description").'</th>';
|
||||||
|
print '</tr>';
|
||||||
|
|
||||||
|
if ($resqlLeistDetail) {
|
||||||
|
$numDetail = $db->num_rows($resqlLeistDetail);
|
||||||
|
if ($numDetail > 0) {
|
||||||
|
while ($objLD = $db->fetch_object($resqlLeistDetail)) {
|
||||||
|
$hours = floor($objLD->duration / 60);
|
||||||
|
$mins = $objLD->duration % 60;
|
||||||
|
|
||||||
|
print '<tr class="oddeven">';
|
||||||
|
|
||||||
|
// Stundenzettel
|
||||||
|
print '<td>';
|
||||||
|
print '<a href="'.dol_buildpath('/stundenzettel/card.php', 1).'?id='.$objLD->fk_stundenzettel.'">';
|
||||||
|
print $objLD->stz_ref;
|
||||||
|
print '</a>';
|
||||||
|
print '</td>';
|
||||||
|
|
||||||
|
// Datum
|
||||||
|
print '<td>'.dol_print_date($db->jdate($objLD->date_leistung), 'day').'</td>';
|
||||||
|
|
||||||
|
// Zeit
|
||||||
|
print '<td>';
|
||||||
|
if ($objLD->time_start && $objLD->time_end) {
|
||||||
|
print substr($objLD->time_start, 0, 5).' - '.substr($objLD->time_end, 0, 5);
|
||||||
|
} else {
|
||||||
|
print '<span class="opacitymedium">-</span>';
|
||||||
|
}
|
||||||
|
print '</td>';
|
||||||
|
|
||||||
|
// Dauer
|
||||||
|
print '<td class="right">'.sprintf('%d:%02d h', $hours, $mins).'</td>';
|
||||||
|
|
||||||
|
// Leistungsposition
|
||||||
|
print '<td>';
|
||||||
|
if ($objLD->fk_product > 0) {
|
||||||
|
print '<span class="badge badge-primary">'.$objLD->service_ref.'</span>';
|
||||||
|
} else {
|
||||||
|
print '<span class="opacitymedium">-</span>';
|
||||||
|
}
|
||||||
|
print '</td>';
|
||||||
|
|
||||||
|
// Beschreibung
|
||||||
|
print '<td>';
|
||||||
|
if (!empty($objLD->description)) {
|
||||||
|
$desc = strip_tags($objLD->description);
|
||||||
|
if (strlen($desc) > 50) $desc = substr($desc, 0, 47).'...';
|
||||||
|
print dol_escape_htmltag($desc);
|
||||||
|
} else {
|
||||||
|
print '<span class="opacitymedium">-</span>';
|
||||||
|
}
|
||||||
|
print '</td>';
|
||||||
|
|
||||||
|
print '</tr>';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print '<tr class="oddeven"><td colspan="6" class="opacitymedium">'.$langs->trans("NoRecordFound").'</td></tr>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print '</table>';
|
||||||
|
print '</div>';
|
||||||
|
print '</div>'; // fichehalfleft
|
||||||
|
|
||||||
// Button: In Rechnung übertragen (nur wenn alles erledigt)
|
// Button: In Rechnung übertragen (nur wenn alles erledigt)
|
||||||
if ($total_remaining <= 0 && $total_delivered > 0) {
|
if ($total_remaining <= 0 && $total_delivered > 0) {
|
||||||
print '<div class="center" style="margin-top:20px;">';
|
print '<div class="center clearboth" style="margin-top:20px;">';
|
||||||
print '<a class="butAction" href="?id='.$order->id.'&action=transfer_to_invoice">';
|
print '<a class="butAction" href="?id='.$order->id.'&action=transfer_to_invoice">';
|
||||||
print img_picto('', 'bill', 'class="pictofixedwidth"').$langs->trans("TransferToInvoice");
|
print img_picto('', 'bill', 'class="pictofixedwidth"').$langs->trans("TransferToInvoice");
|
||||||
print '</a>';
|
print '</a>';
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue