* * Stundenzettel - Hauptformular (Card) */ // Load Dolibarr environment $res = 0; if (!$res && file_exists("../main.inc.php")) $res = @include "../main.inc.php"; if (!$res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php"; if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php"; if (!$res) die("Include of main fails"); 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.'/societe/class/societe.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php'; dol_include_once('/stundenzettel/class/stundenzettel.class.php'); dol_include_once('/stundenzettel/lib/stundenzettel.lib.php'); // Load translation files $langs->loadLangs(array("stundenzettel@stundenzettel", "orders", "products")); /** * Generiert Zeitoptionen im 15-Minuten-Takt * @param string $selected Ausgewählte Zeit (HH:MM) * @param string $defaultHour Standard-Stunde * @return string HTML-Options */ function getTimeOptions($selected = '', $defaultHour = '08') { $options = ''; for ($h = 6; $h <= 22; $h++) { for ($m = 0; $m < 60; $m += 15) { $time = sprintf('%02d:%02d', $h, $m); $isSelected = ($selected == $time) || (empty($selected) && $h == intval($defaultHour) && $m == 0); $options .= ''; } } return $options; } /** * Formatiert Mengen: Ganzzahlen ohne Dezimalstellen, sonst max. 2 Stellen * @param float $qty Menge * @return string Formatierte Menge */ function formatQty($qty) { $qty = (float)$qty; if ($qty == floor($qty)) { return number_format($qty, 0, ',', '.'); } $formatted = rtrim(rtrim(number_format($qty, 2, ',', '.'), '0'), ','); return $formatted; } // Get parameters $id = GETPOST('id', 'int'); $ref = GETPOST('ref', 'alpha'); $action = GETPOST('action', 'aZ09'); $confirm = GETPOST('confirm', 'alpha'); $fk_commande = GETPOST('fk_commande', 'int'); $subtab = GETPOST('tab', 'aZ09') ?: 'leistungen'; // Interner Sub-Tab auf dieser Seite // Security check if (!$user->hasRight('stundenzettel', 'read')) { accessforbidden(); } $object = new Stundenzettel($db); // Load object if ($id > 0 || !empty($ref)) { $result = $object->fetch($id, $ref); if ($result <= 0) { dol_print_error($db, $object->error); exit; } } // Permissions - Basis-Berechtigungen $permissiontoread = $user->hasRight('stundenzettel', 'read'); $permissiontoreadall = $user->hasRight('stundenzettel', 'read', 'all') || $user->admin; $permissiontowrite = $user->hasRight('stundenzettel', 'write'); $permissiontowriteall = $user->hasRight('stundenzettel', 'write', 'all') || $user->admin; $permissiontodelete = $user->hasRight('stundenzettel', 'delete'); $permissiontodeleteall = $user->hasRight('stundenzettel', 'delete', 'all') || $user->admin; $permissiontovalidate = $user->hasRight('stundenzettel', 'validate'); // Prüfen ob der aktuelle Stundenzettel dem Benutzer gehört $isOwner = ($object->id > 0 && $object->fk_user_author == $user->id); // Effektive Berechtigungen für diesen Stundenzettel $permissiontoadd = $permissiontowrite && ($isOwner || $permissiontowriteall || $action == 'create'); $permissiontodeleteobj = $permissiontodelete && ($isOwner || $permissiontodeleteall); // Zugriffskontrolle: Wenn geladen und nicht berechtigt -> Zugriff verweigern if ($object->id > 0 && !$isOwner && !$permissiontoreadall) { accessforbidden('Sie haben keine Berechtigung, diesen Stundenzettel anzuzeigen.'); } /* * Actions */ // Create if ($action == 'add' && $permissiontoadd) { $object->fk_commande = GETPOST('fk_commande', 'int'); // Datum aus Formular holen - selectDate generiert: {prefix}day, {prefix}month, {prefix}year $dateYear = GETPOST('date_stundenzetteljahr', 'int'); // DE locale if (empty($dateYear)) $dateYear = GETPOST('date_stundenzettelselectyear', 'int'); // select version if (empty($dateYear)) $dateYear = GETPOST('date_stundenzetteyear', 'int'); // typo fallback if (empty($dateYear)) $dateYear = GETPOST('date_stundenzettelyear', 'int'); // correct year suffix $dateMonth = GETPOST('date_stundenzettelmonth', 'int'); if (empty($dateMonth)) $dateMonth = GETPOST('date_stundenzettelselectmonth', 'int'); $dateDay = GETPOST('date_stundenzettelday', 'int'); if (empty($dateDay)) $dateDay = GETPOST('date_stundenzettelselectday', 'int'); // Debug: Log welche Werte ankommen dol_syslog("Stundenzettel create: day=$dateDay, month=$dateMonth, year=$dateYear", LOG_DEBUG); if ($dateYear > 0 && $dateMonth > 0 && $dateDay > 0) { $object->date_stundenzettel = dol_mktime(0, 0, 0, $dateMonth, $dateDay, $dateYear); } else { // Fallback: Wenn kein gültiges Datum, nehme heute $object->date_stundenzettel = dol_now(); } $object->note_private = GETPOST('note_private', 'restricthtml'); $object->note_public = GETPOST('note_public', 'restricthtml'); // Get socid from order and check if released $order = new Commande($db); $orderReleased = false; if ($order->fetch($object->fk_commande) > 0) { $object->fk_soc = $order->socid; // Prüfe Stundenzettel-Status des Auftrags $order->fetch_optionals(); $stzStatus = isset($order->array_options['options_stundenzettel_status']) ? (int)$order->array_options['options_stundenzettel_status'] : 0; if ($stzStatus >= 1) { $orderReleased = true; } } if ($orderReleased) { setEventMessages($langs->trans('ErrorStundenzettelReleased'), null, 'errors'); header('Location: '.dol_buildpath('/stundenzettel/stundenzettel_commande.php?id='.$object->fk_commande.'&tab=stundenzettel&noredirect=1', 1)); exit; } elseif (empty($object->fk_commande)) { setEventMessages($langs->trans('ErrorNoOrder'), null, 'errors'); $action = 'create'; } else { // Prüfen ob für diesen Auftrag und dieses Datum bereits ein Stundenzettel existiert $dateStr = dol_print_date($object->date_stundenzettel, '%Y-%m-%d'); $sqlCheck = "SELECT rowid FROM ".MAIN_DB_PREFIX."stundenzettel"; $sqlCheck .= " WHERE fk_commande = ".((int)$object->fk_commande); $sqlCheck .= " AND DATE(date_stundenzettel) = '".$db->escape($dateStr)."'"; $sqlCheck .= " AND status = 0"; // Nur Entwürfe $resqlCheck = $db->query($sqlCheck); if ($resqlCheck && $db->num_rows($resqlCheck) > 0) { // Es existiert bereits ein Stundenzettel für diesen Tag - dorthin weiterleiten $objExisting = $db->fetch_object($resqlCheck); setEventMessages($langs->trans('StundenzettelExistsForDate'), null, 'warnings'); header('Location: '.$_SERVER['PHP_SELF'].'?id='.$objExisting->rowid); exit; } $result = $object->create($user); if ($result > 0) { setEventMessages($langs->trans('StundenzettelCreated'), null, 'mesgs'); header('Location: '.$_SERVER['PHP_SELF'].'?id='.$result); exit; } else { setEventMessages($object->error, $object->errors, 'errors'); $action = 'create'; } } } // Update if ($action == 'update' && $permissiontoadd) { $object->date_stundenzettel = dol_mktime(0, 0, 0, GETPOST('date_stundenzettelmonth', 'int'), GETPOST('date_stundenzettelday', 'int'), GETPOST('date_stundenzettleyear', 'int')); $object->note_private = GETPOST('note_private', 'restricthtml'); $object->note_public = GETPOST('note_public', 'restricthtml'); $result = $object->update($user); if ($result > 0) { setEventMessages($langs->trans('RecordModified'), null, 'mesgs'); header('Location: '.$_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=notes'); exit; } else { setEventMessages($object->error, $object->errors, 'errors'); } } // Validate if ($action == 'confirm_validate' && $confirm == 'yes' && $permissiontovalidate) { $result = $object->validate($user); if ($result > 0) { setEventMessages($langs->trans('StundenzettelValidated'), null, 'mesgs'); // Netto-Wert aller Stundenzettel des Auftrags neu berechnen updateOrderNettoSTZ($db, $object->fk_commande); } else { setEventMessages($object->error, $object->errors, 'errors'); } } // Set to draft if ($action == 'confirm_setdraft' && $confirm == 'yes' && $permissiontoadd) { $result = $object->setDraft($user); if ($result > 0) { setEventMessages($langs->trans('RecordModified'), null, 'mesgs'); // Netto-Wert neu berechnen (da dieser Stundenzettel nicht mehr freigegeben ist) updateOrderNettoSTZ($db, $object->fk_commande); } else { setEventMessages($object->error, $object->errors, 'errors'); } } // Delete if ($action == 'confirm_delete' && $confirm == 'yes' && $permissiontodeleteobj) { $fk_commande = $object->fk_commande; // Speichern vor dem Löschen $result = $object->delete($user); if ($result > 0) { setEventMessages($langs->trans('StundenzettelDeleted'), null, 'mesgs'); // Netto-Wert neu berechnen if ($fk_commande > 0) { updateOrderNettoSTZ($db, $fk_commande); } // Weiterleitung zur Stundenzettel-Liste des Auftrags if ($fk_commande > 0) { header('Location: '.dol_buildpath('/stundenzettel/stundenzettel_commande.php?id='.$fk_commande.'&tab=stundenzettel&noredirect=1', 1)); } else { header('Location: '.dol_buildpath('/stundenzettel/list.php', 1)); } exit; } else { setEventMessages($object->error, $object->errors, 'errors'); } } // Add Leistung if ($action == 'add_leistung' && $permissiontoadd) { // Datum kann als einzelnes Feld (leistung_date) oder als separate Felder kommen $leistung_date_str = GETPOST('leistung_date', 'alpha'); if (!empty($leistung_date_str)) { // Format: Y-m-d $date = strtotime($leistung_date_str); } else { $date = dol_mktime(0, 0, 0, GETPOST('leistung_datemonth', 'int'), GETPOST('leistung_dateday', 'int'), GETPOST('leistung_dateyear', 'int')); } $time_start = GETPOST('time_start', 'alpha'); $time_end = GETPOST('time_end', 'alpha'); $description = GETPOST('leistung_description', 'restricthtml'); $fk_product = GETPOST('fk_product', 'int'); $result = $object->addLeistung($user, $date, $time_start, $time_end, $description, $fk_product); if ($result > 0) { setEventMessages($langs->trans('RecordSaved'), null, 'mesgs'); } else { setEventMessages($object->error, null, 'errors'); } header('Location: '.$_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=leistungen'); exit; } // Update Leistung if ($action == 'update_leistung' && $permissiontoadd) { $leistung_id = GETPOST('leistung_id', 'int'); // Datum kann als einzelnes Feld (leistung_date) oder als separate Felder kommen $leistung_date_str = GETPOST('leistung_date', 'alpha'); if (!empty($leistung_date_str)) { $date = strtotime($leistung_date_str); } else { $date = dol_mktime(0, 0, 0, GETPOST('leistung_datemonth', 'int'), GETPOST('leistung_dateday', 'int'), GETPOST('leistung_dateyear', 'int')); } $time_start = GETPOST('time_start', 'alpha'); $time_end = GETPOST('time_end', 'alpha'); $description = GETPOST('leistung_description', 'restricthtml'); $fk_product = GETPOST('fk_product', 'int'); $result = $object->updateLeistung($leistung_id, $date, $time_start, $time_end, $description, $fk_product); if ($result > 0) { setEventMessages($langs->trans('RecordModified'), null, 'mesgs'); } else { setEventMessages($object->error, null, 'errors'); } header('Location: '.$_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=leistungen'); exit; } // Delete Leistung if ($action == 'confirm_delete_leistung' && $confirm == 'yes' && $permissiontoadd) { $leistung_id = GETPOST('leistung_id', 'int'); $result = $object->deleteLeistung($leistung_id); if ($result > 0) { setEventMessages($langs->trans('RecordDeleted'), null, 'mesgs'); } else { setEventMessages($object->error, null, 'errors'); } header('Location: '.$_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=leistungen'); exit; } // Update product qty if ($action == 'update_qty' && $permissiontoadd) { $line_id = GETPOST('line_id', 'int'); $qty_done = (float)price2num(GETPOST('qty_done', 'alpha')); $result = $object->updateProductQty($line_id, $qty_done); if ($result > 0) { setEventMessages($langs->trans('RecordModified'), null, 'mesgs'); } header('Location: '.$_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=products'); exit; } // Produkt hinzufügen (aus Katalog oder Freitext) // Logik: Wenn Produkt im Auftrag existiert → als Verbrauch zur Auftragszeile // Wenn Produkt NICHT im Auftrag → als Mehraufwand hinzufügen if ($action == 'add_product' && $permissiontoadd) { $fk_product = GETPOST('add_product_id', 'int'); $qty = (float)price2num(GETPOST('add_product_qty', 'alpha')); $description = GETPOST('add_product_description', 'restricthtml'); if ($fk_product > 0 || !empty($description)) { $productHandled = false; // Bei Katalog-Produkten: Prüfen ob das Produkt im Auftrag existiert if ($fk_product > 0) { $order = new Commande($db); if ($order->fetch($object->fk_commande) > 0) { // Prüfen ob dieses Produkt in einer Auftragszeile existiert $sqlOrderLine = "SELECT cd.rowid as commandedet_id FROM ".MAIN_DB_PREFIX."commandedet cd"; $sqlOrderLine .= " WHERE cd.fk_commande = ".((int)$order->id); $sqlOrderLine .= " AND cd.fk_product = ".((int)$fk_product); $sqlOrderLine .= " LIMIT 1"; $resqlOrderLine = $db->query($sqlOrderLine); if ($resqlOrderLine && ($objOrderLine = $db->fetch_object($resqlOrderLine))) { // Produkt existiert im Auftrag - als Verbrauch hinzufügen $commandedet_id = $objOrderLine->commandedet_id; // Prüfen ob schon eine Zeile für diesen Stundenzettel existiert $sqlExisting = "SELECT rowid, qty_done FROM ".MAIN_DB_PREFIX."stundenzettel_product"; $sqlExisting .= " WHERE fk_stundenzettel = ".((int)$object->id); $sqlExisting .= " AND fk_commandedet = ".((int)$commandedet_id); $sqlExisting .= " AND origin IN ('order', 'added')"; $sqlExisting .= " LIMIT 1"; $resqlExisting = $db->query($sqlExisting); if ($resqlExisting && ($objExisting = $db->fetch_object($resqlExisting))) { // Zeile existiert - Menge erhöhen $newQty = (float)$objExisting->qty_done + $qty; $result = $object->updateProductQty($objExisting->rowid, $newQty); if ($result > 0) { setEventMessages($langs->trans('RecordModified'), null, 'mesgs'); } else { setEventMessages($object->error, null, 'errors'); } } else { // Neue Zeile als Verbrauch hinzufügen $product = new Product($db); $product->fetch($fk_product); $result = $object->addProduct( $fk_product, $commandedet_id, // fk_commandedet 0, // fk_manager_line 0, // qty_original $qty, // qty_done 'added', // origin = Verbrauch $description ); if ($result > 0) { setEventMessages($langs->trans('RecordSaved'), null, 'mesgs'); } else { setEventMessages($object->error, null, 'errors'); } } $productHandled = true; } } } // Wenn Produkt nicht im Auftrag - als Verbaut hinzufügen (erscheint in Produktliste) if (!$productHandled) { // Prüfen ob bereits auf diesem Stundenzettel existiert $existingLineId = 0; $existingQty = 0; if ($fk_product > 0) { $sqlCheck = "SELECT rowid, qty_done FROM ".MAIN_DB_PREFIX."stundenzettel_product"; $sqlCheck .= " WHERE fk_stundenzettel = ".((int)$object->id); $sqlCheck .= " AND fk_product = ".((int)$fk_product); $sqlCheck .= " AND origin = 'added'"; $sqlCheck .= " LIMIT 1"; } else { $sqlCheck = "SELECT rowid, qty_done FROM ".MAIN_DB_PREFIX."stundenzettel_product"; $sqlCheck .= " WHERE fk_stundenzettel = ".((int)$object->id); $sqlCheck .= " AND (fk_product IS NULL OR fk_product = 0)"; $sqlCheck .= " AND origin = 'added'"; $sqlCheck .= " AND description = '".$db->escape($description)."'"; $sqlCheck .= " LIMIT 1"; } $resqlCheck = $db->query($sqlCheck); if ($resqlCheck && ($objCheck = $db->fetch_object($resqlCheck))) { $existingLineId = $objCheck->rowid; $existingQty = (float)$objCheck->qty_done; } if ($existingLineId > 0) { // Existiert bereits - Menge erhöhen $newQty = $existingQty + $qty; $result = $object->updateProductQty($existingLineId, $newQty); if ($result > 0) { setEventMessages($langs->trans('RecordModified'), null, 'mesgs'); } else { setEventMessages($object->error, null, 'errors'); } } else { // Neu als Verbaut anlegen $result = $object->addProduct( $fk_product, 0, // fk_commandedet = NULL 0, // fk_manager_line 0, // qty_original = 0 $qty, // qty_done = Verbaut 'added', // origin = manuell hinzugefügt (Produktliste) $description ); if ($result > 0) { setEventMessages($langs->trans('RecordSaved'), null, 'mesgs'); } else { setEventMessages($object->error, null, 'errors'); } } } } else { setEventMessages($langs->trans('ErrorNoProductSelected'), null, 'errors'); } header('Location: '.$_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=products'); exit; } // Entfällt hinzufügen (Produkt aus Auftrag oder Mehraufwand das nicht verbaut wird) if ($action == 'add_entfaellt' && $permissiontoadd) { $entfaellt_product_raw = GETPOST('entfaellt_product', 'alpha'); $qty = (float)price2num(GETPOST('entfaellt_qty', 'alpha')); $reason = GETPOST('entfaellt_description', 'restricthtml'); // Prüfen ob es ein Freitext-Produkt, Mehraufwand oder normales Produkt ist $fk_product = 0; $freetext_description = ''; $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 $commandedet_id = (int)substr($entfaellt_product_raw, 9); // Beschreibung aus commandedet laden $sqlDesc = "SELECT description FROM ".MAIN_DB_PREFIX."commandedet WHERE rowid = ".((int)$commandedet_id); $resqlDesc = $db->query($sqlDesc); if ($resqlDesc && ($objDesc = $db->fetch_object($resqlDesc))) { $freetext_description = $objDesc->description; } } else { $fk_product = (int)$entfaellt_product_raw; } // Beschreibung: Freitext-Beschreibung + Grund $description = $reason; if (!empty($freetext_description)) { $description = strip_tags($freetext_description) . (!empty($reason) ? ' - ' . $reason : ''); } $error = 0; if ($fk_product > 0 || !empty($freetext_description)) { // Server-seitige Validierung: Prüfen ob Menge noch verfügbar ist 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) { // Produkt-Validierung $sqlCheck = "SELECT cd.qty,"; $sqlCheck .= " COALESCE((SELECT SUM(sp.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp"; $sqlCheck .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = sp.fk_stundenzettel"; $sqlCheck .= " WHERE sp.fk_commandedet = cd.rowid AND sp.origin IN ('order', 'added')), 0) as qty_used,"; $sqlCheck .= " COALESCE((SELECT SUM(sp2.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp2"; $sqlCheck .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s2 ON s2.rowid = sp2.fk_stundenzettel"; $sqlCheck .= " WHERE (sp2.fk_commandedet = cd.rowid OR (sp2.fk_product = cd.fk_product AND cd.fk_product > 0)) AND sp2.origin = 'omitted' AND s2.fk_commande = ".((int)$object->fk_commande)."), 0) as qty_omitted"; $sqlCheck .= " FROM ".MAIN_DB_PREFIX."commandedet as cd"; $sqlCheck .= " WHERE cd.fk_commande = ".((int)$object->fk_commande); $sqlCheck .= " AND cd.fk_product = ".((int)$fk_product); $sqlCheck .= " LIMIT 1"; $resqlCheck = $db->query($sqlCheck); if ($resqlCheck && ($objCheck = $db->fetch_object($resqlCheck))) { $qty_available = $objCheck->qty - $objCheck->qty_used - $objCheck->qty_omitted; if ($qty > $qty_available) { setEventMessages($langs->trans('ErrorQtyExceedsAvailable', $qty_available), null, 'errors'); $error++; } } } elseif (!empty($commandedet_id)) { // Freitext-Produkt Validierung $sqlCheck = "SELECT cd.qty,"; $sqlCheck .= " COALESCE((SELECT SUM(sp.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp"; $sqlCheck .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = sp.fk_stundenzettel"; $sqlCheck .= " WHERE sp.fk_commandedet = cd.rowid AND sp.origin IN ('order', 'added')), 0) as qty_used,"; $sqlCheck .= " COALESCE((SELECT SUM(sp2.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp2"; $sqlCheck .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s2 ON s2.rowid = sp2.fk_stundenzettel"; $sqlCheck .= " WHERE sp2.fk_commandedet = cd.rowid AND sp2.origin = 'omitted' AND s2.fk_commande = ".((int)$object->fk_commande)."), 0) as qty_omitted"; $sqlCheck .= " FROM ".MAIN_DB_PREFIX."commandedet as cd"; $sqlCheck .= " WHERE cd.rowid = ".((int)$commandedet_id); $sqlCheck .= " LIMIT 1"; $resqlCheck = $db->query($sqlCheck); if ($resqlCheck && ($objCheck = $db->fetch_object($resqlCheck))) { $qty_available = $objCheck->qty - $objCheck->qty_used - $objCheck->qty_omitted; if ($qty > $qty_available) { setEventMessages($langs->trans('ErrorQtyExceedsAvailable', $qty_available), null, 'errors'); $error++; } } } } if (!$error) { if ($mehraufwand_id > 0) { // Mehraufwand: Menge vom qty_done erhöhen statt neuen Eintrag erstellen // Oder: Menge vom Mehraufwand reduzieren und als Entfällt anlegen // Wir reduzieren qty des Mehraufwands und legen einen neuen Entfällt-Eintrag an $sqlUpdateMehr = "UPDATE ".MAIN_DB_PREFIX."stundenzettel_product SET qty = qty - ".((int)$qty); $sqlUpdateMehr .= " WHERE rowid = ".((int)$mehraufwand_id); $db->query($sqlUpdateMehr); // Entfällt-Eintrag mit Hinweis auf Mehraufwand anlegen $entfaelltDesc = $langs->trans("Mehraufwand").': '.$description; if (!empty($reason)) { $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 { // Produkt zum Stundenzettel hinzufügen mit origin='omitted' $result = $object->addProduct( $fk_product, $commandedet_id, // fk_commandedet (Verknüpfung zur Auftragszeile) 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 { setEventMessages($langs->trans('ErrorNoProductSelected'), null, 'errors'); } header('Location: '.$_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=products'); exit; } // Entfällt-Produkt löschen if ($action == 'confirm_delete_entfaellt' && $confirm == 'yes' && $permissiontoadd) { $line_id = GETPOST('line_id', 'int'); $result = $object->deleteProduct($line_id); if ($result > 0) { setEventMessages($langs->trans('RecordDeleted'), null, 'mesgs'); } else { setEventMessages($object->error, null, 'errors'); } header('Location: '.$_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=products'); exit; } // Rücknahme hinzufügen (bereits verbautes Produkt wird zurückgenommen) if ($action == 'add_ruecknahme' && $permissiontoadd) { $ruecknahme_product_raw = GETPOST('ruecknahme_product', 'alpha'); $qty = (float)price2num(GETPOST('ruecknahme_qty', 'alpha')); $reason = GETPOST('ruecknahme_description', 'restricthtml'); // Prüfen ob es ein Freitext-Produkt oder normales Produkt ist $fk_product = 0; $freetext_description = ''; $commandedet_id = 0; if (strpos($ruecknahme_product_raw, 'freetext_') === 0) { // Freitext-Produkt aus dem Auftrag $commandedet_id = (int)substr($ruecknahme_product_raw, 9); // Beschreibung aus commandedet laden $sqlDesc = "SELECT description FROM ".MAIN_DB_PREFIX."commandedet WHERE rowid = ".((int)$commandedet_id); $resqlDesc = $db->query($sqlDesc); if ($resqlDesc && ($objDesc = $db->fetch_object($resqlDesc))) { $freetext_description = $objDesc->description; } } elseif (strpos($ruecknahme_product_raw, 'mehraufwand_') === 0) { // Freitext-Mehraufwand aus stundenzettel_product $sp_rowid = (int)substr($ruecknahme_product_raw, 12); $sqlDesc = "SELECT description FROM ".MAIN_DB_PREFIX."stundenzettel_product WHERE rowid = ".((int)$sp_rowid); $resqlDesc = $db->query($sqlDesc); if ($resqlDesc && ($objDesc = $db->fetch_object($resqlDesc))) { $freetext_description = $objDesc->description; } } else { $fk_product = (int)$ruecknahme_product_raw; } // Beschreibung: Freitext-Beschreibung + Grund $description = $reason; if (!empty($freetext_description)) { $description = strip_tags($freetext_description) . (!empty($reason) ? ' - ' . $reason : ''); } $error = 0; if ($fk_product > 0 || !empty($freetext_description)) { // Server-seitige Validierung: Prüfen ob Menge bereits verbaut wurde if ($object->fk_commande > 0) { if ($fk_product > 0) { // Produkt-Validierung: Prüfe bereits verbaute Menge $sqlCheck = "SELECT COALESCE(SUM(sp.qty_done), 0) as qty_delivered,"; $sqlCheck .= " COALESCE((SELECT SUM(sp2.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp2"; $sqlCheck .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s2 ON s2.rowid = sp2.fk_stundenzettel"; $sqlCheck .= " WHERE sp2.fk_product = ".((int)$fk_product)." AND sp2.origin = 'returned' AND s2.fk_commande = ".((int)$object->fk_commande)."), 0) as qty_already_returned"; $sqlCheck .= " FROM ".MAIN_DB_PREFIX."stundenzettel_product sp"; $sqlCheck .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = sp.fk_stundenzettel"; $sqlCheck .= " WHERE sp.fk_product = ".((int)$fk_product); $sqlCheck .= " AND sp.origin IN ('order', 'added')"; $sqlCheck .= " AND s.fk_commande = ".((int)$object->fk_commande); $resqlCheck = $db->query($sqlCheck); if ($resqlCheck && ($objCheck = $db->fetch_object($resqlCheck))) { $qty_available = $objCheck->qty_delivered - $objCheck->qty_already_returned; if ($qty > $qty_available) { setEventMessages($langs->trans('ErrorQtyExceedsAvailable', $qty_available), null, 'errors'); $error++; } } } elseif (!empty($commandedet_id)) { // Freitext-Produkt Validierung $sqlCheck = "SELECT COALESCE(SUM(sp.qty_done), 0) as qty_delivered,"; $sqlCheck .= " COALESCE((SELECT SUM(sp2.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp2"; $sqlCheck .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s2 ON s2.rowid = sp2.fk_stundenzettel"; $sqlCheck .= " WHERE sp2.fk_commandedet = ".((int)$commandedet_id)." AND sp2.origin = 'returned' AND s2.fk_commande = ".((int)$object->fk_commande)."), 0) as qty_already_returned"; $sqlCheck .= " FROM ".MAIN_DB_PREFIX."stundenzettel_product sp"; $sqlCheck .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = sp.fk_stundenzettel"; $sqlCheck .= " WHERE sp.fk_commandedet = ".((int)$commandedet_id); $sqlCheck .= " AND sp.origin IN ('order', 'added')"; $sqlCheck .= " AND s.fk_commande = ".((int)$object->fk_commande); $resqlCheck = $db->query($sqlCheck); if ($resqlCheck && ($objCheck = $db->fetch_object($resqlCheck))) { $qty_available = $objCheck->qty_delivered - $objCheck->qty_already_returned; if ($qty > $qty_available) { setEventMessages($langs->trans('ErrorQtyExceedsAvailable', $qty_available), null, 'errors'); $error++; } } } } if (!$error) { // Produkt zum Stundenzettel hinzufügen mit origin='returned' $result = $object->addProduct( $fk_product, $commandedet_id, // fk_commandedet 0, // fk_manager_line 0, // qty_original $qty, // qty_done (Menge die zurückgenommen wird) 'returned', // origin (rücknahme) $description // description (Grund) ); if ($result > 0) { setEventMessages($langs->trans('RecordSaved'), null, 'mesgs'); } else { setEventMessages($object->error, null, 'errors'); } } } else { setEventMessages($langs->trans('ErrorNoProductSelected'), null, 'errors'); } header('Location: '.$_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=products'); exit; } // Rücknahme-Produkt löschen if ($action == 'confirm_delete_ruecknahme' && $confirm == 'yes' && $permissiontoadd) { $line_id = GETPOST('line_id', 'int'); $result = $object->deleteProduct($line_id); if ($result > 0) { setEventMessages($langs->trans('RecordDeleted'), null, 'mesgs'); } else { setEventMessages($object->error, null, 'errors'); } header('Location: '.$_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=products'); exit; } // Mehraufwand hinzufügen (zusätzliches Produkt nicht aus Auftrag) if ($action == 'add_mehraufwand' && $permissiontoadd) { $fk_product = GETPOST('mehraufwand_product', 'int'); $qty = (float)price2num(GETPOST('mehraufwand_qty', 'alpha')); $freetext_description = GETPOST('mehraufwand_description', 'restricthtml'); $reason = GETPOST('mehraufwand_reason', 'restricthtml'); // Beschreibung: // - Bei Katalog-Produkt: Nur der Grund (Produktname kommt aus der Produkt-Tabelle) // - Bei Freitext: Freitext ist die Beschreibung (Grund wird nicht unterstützt) $description = ''; if ($fk_product > 0) { // Katalog-Produkt: Grund als Beschreibung $description = $reason; } else { // Freitext-Produkt: Freitext als Beschreibung $description = $freetext_description; } if ($fk_product > 0 || !empty($description)) { // Mehraufwand wird IMMER als origin='additional' gespeichert // (unabhängig davon, ob das Produkt im Auftrag existiert oder nicht) // Prüfen ob bereits als Mehraufwand auf diesem Stundenzettel existiert $existingLineId = 0; $existingQty = 0; if ($fk_product > 0) { $sqlCheck = "SELECT rowid, qty_done FROM ".MAIN_DB_PREFIX."stundenzettel_product"; $sqlCheck .= " WHERE fk_stundenzettel = ".((int)$object->id); $sqlCheck .= " AND fk_product = ".((int)$fk_product); $sqlCheck .= " AND origin = 'additional'"; $sqlCheck .= " LIMIT 1"; } else { $sqlCheck = "SELECT rowid, qty_done FROM ".MAIN_DB_PREFIX."stundenzettel_product"; $sqlCheck .= " WHERE fk_stundenzettel = ".((int)$object->id); $sqlCheck .= " AND (fk_product IS NULL OR fk_product = 0)"; $sqlCheck .= " AND origin = 'additional'"; $sqlCheck .= " AND description = '".$db->escape($description)."'"; $sqlCheck .= " LIMIT 1"; } $resqlCheck = $db->query($sqlCheck); if ($resqlCheck && ($objCheck = $db->fetch_object($resqlCheck))) { $existingLineId = $objCheck->rowid; $existingQty = (float)$objCheck->qty_done; } if ($existingLineId > 0) { // Existiert bereits - Menge erhöhen $newQty = $existingQty + $qty; $result = $object->updateProductQty($existingLineId, $newQty); if ($result > 0) { setEventMessages($langs->trans('RecordModified'), null, 'mesgs'); } else { setEventMessages($object->error, null, 'errors'); } } else { // Neu als Mehraufwand anlegen $result = $object->addProduct( $fk_product, 0, // fk_commandedet 0, // fk_manager_line 0, // qty_original $qty, // qty_done = Menge 'additional', // origin = Mehraufwand $description ); if ($result > 0) { setEventMessages($langs->trans('RecordSaved'), null, 'mesgs'); } else { setEventMessages($object->error, null, 'errors'); } } } else { setEventMessages($langs->trans('ErrorNoProductSelected'), null, 'errors'); } header('Location: '.$_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=products'); exit; } // Mehraufwand-Produkt aktualisieren (Menge und Grund) if ($action == 'update_mehraufwand' && $permissiontoadd) { $line_id = GETPOST('line_id', 'int'); $qty_done = (float)price2num(GETPOST('qty_done', 'alpha')); $reason = GETPOST('reason', 'restricthtml'); if ($line_id > 0 && $qty_done > 0) { // Menge und Grund aktualisieren $sql = "UPDATE ".MAIN_DB_PREFIX."stundenzettel_product"; $sql .= " SET qty_done = ".((float)$qty_done); $sql .= ", description = ".($reason !== '' ? "'".$db->escape($reason)."'" : "NULL"); $sql .= " WHERE rowid = ".((int)$line_id); $sql .= " AND fk_stundenzettel = ".((int)$object->id); $result = $db->query($sql); if ($result) { setEventMessages($langs->trans('RecordModified'), null, 'mesgs'); } else { setEventMessages($db->lasterror(), null, 'errors'); } } header('Location: '.$_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=products'); exit; } // Mehraufwand-Produkt löschen if ($action == 'confirm_delete_mehraufwand' && $confirm == 'yes' && $permissiontoadd) { $line_id = GETPOST('line_id', 'int'); $result = $object->deleteProduct($line_id); if ($result > 0) { setEventMessages($langs->trans('RecordDeleted'), null, 'mesgs'); } else { setEventMessages($object->error, null, 'errors'); } header('Location: '.$_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=products'); exit; } // Produkt aus Produktliste löschen if ($action == 'confirm_delete_product' && $confirm == 'yes' && $permissiontoadd) { $line_id = GETPOST('line_id', 'int'); $result = $object->deleteProduct($line_id); if ($result > 0) { setEventMessages($langs->trans('RecordDeleted'), null, 'mesgs'); } else { setEventMessages($object->error, null, 'errors'); } header('Location: '.$_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=products'); exit; } // Notiz hinzufügen if ($action == 'add_note' && $permissiontoadd) { $note_text = GETPOST('note_text', 'restricthtml'); if (!empty($note_text)) { $result = $object->addNote($user, $note_text); if ($result > 0) { setEventMessages($langs->trans('RecordSaved'), null, 'mesgs'); } else { setEventMessages($object->error, null, 'errors'); } } header('Location: '.$_SERVER['PHP_SELF'].'?id='.$object->id); exit; } // Notiz-Status umschalten (abhaken/öffnen) if ($action == 'toggle_note' && $permissiontoadd) { $note_id = GETPOST('note_id', 'int'); $checked = GETPOST('checked', 'int'); $result = $object->updateNoteStatus($note_id, $checked); if ($result > 0) { setEventMessages($langs->trans('RecordModified'), null, 'mesgs'); } header('Location: '.$_SERVER['PHP_SELF'].'?id='.$object->id); exit; } // Notiz löschen if ($action == 'confirm_delete_note' && $confirm == 'yes' && $permissiontoadd) { $note_id = GETPOST('note_id', 'int'); $result = $object->deleteNote($note_id); if ($result > 0) { setEventMessages($langs->trans('RecordDeleted'), null, 'mesgs'); } else { setEventMessages($object->error, null, 'errors'); } header('Location: '.$_SERVER['PHP_SELF'].'?id='.$object->id); exit; } /* * View */ $form = new Form($db); $title = $langs->trans("Stundenzettel"); if ($object->id > 0) { $title .= ' - '.$object->ref; } // Linkes Menü aktivieren $_GET['mainmenu'] = 'stundenzettel'; llxHeader('', $title, '', '', 0, 0, '', '', '', 'mod-stundenzettel page-card'); // Mobile CSS einbinden $mobileCssFile = dol_buildpath('/stundenzettel/css/stundenzettel-mobile.css', 0); if (file_exists($mobileCssFile)) { print ''; } // JavaScript für Mengenprüfung print ''; // Create mode if ($action == 'create') { // Prüfe ob Auftrag freigegeben ist if ($fk_commande > 0) { $orderCheck = new Commande($db); if ($orderCheck->fetch($fk_commande) > 0) { $orderCheck->fetch_optionals(); $stzStatus = isset($orderCheck->array_options['options_stundenzettel_status']) ? (int)$orderCheck->array_options['options_stundenzettel_status'] : 0; if ($stzStatus >= 1) { setEventMessages($langs->trans('ErrorStundenzettelReleased'), null, 'errors'); header('Location: '.dol_buildpath('/stundenzettel/stundenzettel_commande.php?id='.$fk_commande.'&tab=stundenzettel&noredirect=1', 1)); exit; } } } print load_fiche_titre($langs->trans("CreateStundenzettel"), '', 'clock'); print '
'; print ''; print ''; print dol_get_fiche_head(array(), ''); print ''; // Auftrag print ''; // Datum print ''; // Notiz öffentlich print ''; // Notiz privat print ''; print '
'.$langs->trans("Order").''; if ($fk_commande > 0) { $order = new Commande($db); $order->fetch($fk_commande); print $order->getNomUrl(1); print ''; } else { // Auftrag auswählen $sql = "SELECT c.rowid, c.ref, s.nom as soc_name"; $sql .= " FROM ".MAIN_DB_PREFIX."commande as c"; $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid = c.fk_soc"; $sql .= " WHERE c.entity = ".((int)$conf->entity); $sql .= " AND c.fk_statut IN (1, 2)"; $sql .= " ORDER BY c.date_commande DESC"; $sql .= " LIMIT 100"; print ''; } print '
'.$langs->trans("Date").''; print $form->selectDate(dol_now(), 'date_stundenzettel', 0, 0, 0, '', 1, 1); print '
'.$langs->trans("NotePublic").''; $doleditor = new DolEditor('note_public', '', '', 150, 'dolibarr_notes', '', false, true, getDolGlobalString('FCKEDITOR_ENABLE_NOTE_PUBLIC'), ROWS_5, '90%'); print $doleditor->Create(1); print '
'.$langs->trans("NotePrivate").''; $doleditor = new DolEditor('note_private', '', '', 150, 'dolibarr_notes', '', false, true, getDolGlobalString('FCKEDITOR_ENABLE_NOTE_PRIVATE'), ROWS_5, '90%'); print $doleditor->Create(1); print '
'; print dol_get_fiche_end(); print '
'; print ''; print '   '; print ''.$langs->trans("Cancel").''; print '
'; print '
'; } // View/Edit mode elseif ($object->id > 0) { // Confirmations if ($action == 'validate') { print $form->formconfirm($_SERVER['PHP_SELF'].'?id='.$object->id, $langs->trans('ValidateStundenzettel'), $langs->trans('ConfirmValidate'), 'confirm_validate', '', 0, 1); } if ($action == 'setdraft') { print $form->formconfirm($_SERVER['PHP_SELF'].'?id='.$object->id, $langs->trans('ReopenStundenzettel'), $langs->trans('ConfirmSetDraft'), 'confirm_setdraft', '', 0, 1); } if ($action == 'delete') { print $form->formconfirm($_SERVER['PHP_SELF'].'?id='.$object->id, $langs->trans('DeleteStundenzettel'), $langs->trans('ConfirmDelete'), 'confirm_delete', '', 0, 1); } // Tabs $head = stundenzettel_prepare_head($object); print dol_get_fiche_head($head, 'card', $langs->trans("Stundenzettel"), -1, 'clock'); // Banner $linkback = ''.$langs->trans("BackToList").''; dol_banner_tab($object, 'id', $linkback, 1, 'rowid', 'ref', '', '', 0, '', '', 1); print '
'; print '
'; // ============================================= // BEREICH: LEISTUNGEN (immer anzeigen außer bei Notizen) // ============================================= 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 $editLeistungId = GETPOST('edit_leistung', 'int'); // Bestätigung für Löschen if ($action == 'delete_leistung') { $leistung_id = GETPOST('leistung_id', 'int'); print $form->formconfirm($_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=leistungen&leistung_id='.$leistung_id, $langs->trans('Delete'), $langs->trans('ConfirmDeleteLeistung'), 'confirm_delete_leistung', '', 0, 1); } print '

'.$langs->trans("Leistungen").'

'; print '
'; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) { print ''; // Edit print ''; // Delete } print ''; if (count($object->leistungen) > 0) { foreach ($object->leistungen as $leistung) { // Bearbeitungsmodus für diese Zeile? if ($editLeistungId == $leistung->rowid && $object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) { // Bearbeitungszeile print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; // Service/Product Selection print ''; print ''; // Save Button print ''; // Cancel Button print ''; print ''; print ''; } else { // Normale Anzeige print ''; print ''; print ''; print ''; print ''; // Leistungsposition anzeigen print ''; // Beschreibung (auf Mobile ausgeblendet) print ''; if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) { // Edit Button print ''; // Delete Button print ''; } print ''; } } } else { $colspan = ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) ? 8 : 6; print ''; } // Neue Leistung hinzufügen (nur im Entwurf) if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd && empty($editLeistungId)) { // Zeit-Eingabemodus aus Konfiguration laden $timeInputMode = getDolGlobalString('STUNDENZETTEL_TIME_INPUT_MODE', 'dropdown'); $usePreciseTime = GETPOST('precise_time', 'int') ? true : false; $showTextInput = ($timeInputMode == 'text') || $usePreciseTime; print ''; print ''; print ''; print ''; // Datum ist fix (Stundenzettel gilt für einen Tag) print ''; print ''; // Beginn-Zeit print ''; // Ende-Zeit print ''; // Dauer-Spalte - leer lassen (wird nach Speichern berechnet) print ''; // Leistungsposition (Service/Dienstleistung) print ''; // Beschreibung (Desktop: in Zeile, Mobile: ausgeblendet) print ''; // Action (colspan=2 für beide Button-Spalten) print ''; print ''; print ''; // Mobile: Beschreibung in separater Zeile print ''; print ''; print ''; } // Summenzeile anzeigen if (count($object->leistungen) > 0) { $totalDuration = 0; foreach ($object->leistungen as $l) { $totalDuration += $l->duration; } if ($totalDuration > 0) { $hours = floor($totalDuration / 60); $mins = $totalDuration % 60; $colspan = ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) ? 8 : 6; print ''; print ''; print ''; print ''; print ''; } } print '
'.$langs->trans("Date").''.$langs->trans("LeistungTimeStart").''.$langs->trans("LeistungTimeEnd").''.$langs->trans("Duration").''.$langs->trans("DefaultService").''.$langs->trans("Description").'
'.$form->selectDate($db->jdate($leistung->date_leistung), 'leistung_date', 0, 0, 0, '', 1, 0).''; if ($leistung->duration > 0) { $hours = floor($leistung->duration / 60); $mins = $leistung->duration % 60; print $hours.'h '.sprintf('%02d', $mins).'min'; } print ''; $form->select_produits($leistung->fk_product, 'fk_product', 1, 0, 0, -1, 2, '', 0, array(), 0, '1', 0, 'maxwidth200'); print ''; print ''; print ''; print ''; print '
'.dol_print_date($db->jdate($leistung->date_leistung), 'day').''.$leistung->time_start.''.$leistung->time_end.''; if ($leistung->duration > 0) { $hours = floor($leistung->duration / 60); $mins = $leistung->duration % 60; print $hours.'h '.sprintf('%02d', $mins).'min'; } print ''; if (!empty($leistung->fk_product)) { print dol_escape_htmltag($leistung->product_label); } else { print ''.$langs->trans("NotSet").''; } // Mobile: Beschreibung unter Leistungsposition anzeigen if (!empty($leistung->description)) { print '
'.dol_trunc(strip_tags($leistung->description), 60).'
'; } print '
'.dol_htmlentitiesbr($leistung->description).''; print ''; print ''; print ''; print '
'.$langs->trans("NoRecordFound").'
'.dol_print_date($object->date_stundenzettel, 'day').''; if ($showTextInput) { print ''; } else { print ''; } print ''; if ($showTextInput) { print ''; } else { print ''; } print ''; print ''; // 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 ''; print ''; print ''; print ''; print '
'; print ''; print '
'.$langs->trans("Total").''.$hours.'h '.sprintf('%02d', $mins).'min
'; print '
'; } // ============================================= // BEREICH: PRODUKTE (immer anzeigen außer bei Notizen) // ============================================= if ($subtab != 'notes') { // Bestätigung für Löschen von Produkten if ($action == 'delete_product') { $line_id = GETPOST('line_id', 'int'); print $form->formconfirm($_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=products&line_id='.$line_id, $langs->trans('Delete'), $langs->trans('ConfirmDeleteLine'), 'confirm_delete_product', '', 0, 1); } print '
'; print ''; $colspanProducts = ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) ? 8 : 4; print ''; print ''; print ''; print ''; print ''; if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) { print ''; // Minus print ''; // Plus print ''; // Save print ''; // Delete } print ''; print ''; // Nur Produkte aus Auftrag und Verbaut anzeigen (nicht Mehraufwand/Entfällt - die werden unten separat angezeigt) $orderProducts = array(); foreach ($object->products as $prod) { if ($prod->origin == 'order' || $prod->origin == 'added') { $orderProducts[] = $prod; } } if (count($orderProducts) > 0) { foreach ($orderProducts as $prod) { // Gesamtmenge über alle Stundenzettel für dieses Produkt berechnen $totalQtyAllStz = 0; if ($prod->fk_commandedet > 0) { $sqlTotal = "SELECT SUM(sp.qty_done) as total FROM ".MAIN_DB_PREFIX."stundenzettel_product sp"; $sqlTotal .= " INNER JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = sp.fk_stundenzettel"; $sqlTotal .= " WHERE sp.fk_commandedet = ".((int)$prod->fk_commandedet); $sqlTotal .= " AND s.fk_commande = ".((int)$object->fk_commande); $resqlTotal = $db->query($sqlTotal); if ($resqlTotal && ($objTotal = $db->fetch_object($resqlTotal))) { $totalQtyAllStz = (float)$objTotal->total; } } else { $totalQtyAllStz = (float)$prod->qty_done; } $isExceeded = ($prod->qty_original > 0 && $totalQtyAllStz > $prod->qty_original); $exceededQty = $totalQtyAllStz - $prod->qty_original; print ''; // Produkt print ''; // Menge aus Auftrag print ''; // Menge erledigt print ''; if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) { // Minus Button print ''; // Plus Button - mit Warnung bei Überschreitung der Auftragsmenge // $totalQtyAllStz und $qtyOriginal wurden am Anfang der Schleife berechnet $qtyOriginal = (float)$prod->qty_original; print ''; // Save Button print ''; // Delete Button print ''; } // Herkunft print ''; print ''; } } else { $colspan = ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) ? 8 : 4; print ''; } // Formular zum Hinzufügen von Produkten (nur im Entwurf) if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) { print ''; print ''; print ''; print ''; print ''; print ''; print ''; // Produkt-Auswahl (colspan für Produkt + QtyFromOrder) print ''; // Menge print ''; // Hinzufügen-Button (colspan für Minus, Plus, Save, Delete) print ''; // Origin - leer print ''; print ''; print ''; } print '
'.$langs->trans("Products").' - '.$langs->trans("ProductsDesc").'
'.$langs->trans("Product").''.$langs->trans("QtyFromOrder").''.$langs->trans("QtyUsed").''.$langs->trans("Origin").'
'; if ($prod->fk_product > 0) { print ''; print img_picto('', 'product', 'class="pictofixedwidth"'); print $prod->product_ref.' - '.$prod->product_label; print ''; } else { // Freitext-Produkt: Beschreibung anzeigen print img_picto('', 'generic', 'class="pictofixedwidth"'); $displayText = ''; if (!empty($prod->description)) { $displayText = strip_tags($prod->description); } elseif (!empty($prod->product_label)) { $displayText = $prod->product_label; } if (empty($displayText)) { $displayText = $langs->trans("FreeText"); } if (strlen($displayText) > 80) { $displayText = substr($displayText, 0, 77).'...'; } print ''.$displayText.''; } // Mehraufwand-Warnung anzeigen wenn überschritten if ($isExceeded) { print ' +'.formatQty($exceededQty).' '.$langs->trans("Mehraufwand").''; } print ''.formatQty($prod->qty_original).''; // Wert formatieren (max 2 Dezimalstellen) $qtyDoneValue = (float)$prod->qty_done; if ($qtyDoneValue == floor($qtyDoneValue)) { $qtyDoneFormatted = (int)$qtyDoneValue; } else { $qtyDoneFormatted = round($qtyDoneValue, 2); } if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) { print '
'; print ''; print ''; print ''; print ''; print '
'; } else { print formatQty($prod->qty_done); } print '
'; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; if ($prod->origin == 'order') { print ''.$langs->trans("FromOrder").''; } else { print ''.$langs->trans("Added").''; } print '
'.$langs->trans("NoRecordFound").'
'.$langs->trans("AddProduct").'
'; print $form->select_produits('', 'add_product_id', '', 0, 0, -1, 2, '', 0, array(), 0, '1', 0, 'minwidth300', 0, '', null, 1); print '
'.$langs->trans("OrFreeText").':
'; print ''; print '
'; print ''; print ''; print ''; print '
'; print '
'; // ============================================= // BEREICH: ENTFÄLLT (Produkte die nicht verbaut werden) // ============================================= if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) { // Bestätigung für Löschen if ($action == 'delete_entfaellt') { $line_id = GETPOST('line_id', 'int'); print $form->formconfirm($_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=products&line_id='.$line_id, $langs->trans('Delete'), $langs->trans('ConfirmDeleteEntfaellt'), 'confirm_delete_entfaellt', '', 0, 1); } // Zuerst bereits erfasste Entfällt-Produkte zählen $entfaelltProducts = array(); foreach ($object->products as $prod) { if ($prod->origin == 'omitted') { $entfaelltProducts[] = $prod; } } $hasEntfaelltProducts = (count($entfaelltProducts) > 0); // Bereich nur anzeigen wenn Einträge vorhanden ODER Checkbox aktiviert ODER Admin-Standard $defaultSections = explode(',', getDolGlobalString('STUNDENZETTEL_DEFAULT_SECTIONS', '')); $showEntfaellt = $hasEntfaelltProducts || GETPOST('show_entfaellt', 'int') || in_array('entfaellt', $defaultSections); // Checkbox zum Ein-/Ausblenden print '
'; print ''; print '
'; print '
'; print ''; print ''; print ''; print ''; print ''; print ''; print ''; // Save print ''; // Delete print ''; if ($hasEntfaelltProducts) { foreach ($entfaelltProducts as $prod) { print ''; // Produkt print ''; // Menge print ''; // Grund/Beschreibung (auf Mobile ausgeblendet) print ''; // Save print ''; // Delete print ''; print ''; } } // Formular zum Hinzufügen - immer am Ende // Produkte aus dem Auftrag laden für das Dropdown MIT verfügbarer Menge $orderProducts = array(); if ($object->fk_commande > 0) { $sqlOrderProducts = "SELECT cd.rowid, cd.fk_product, cd.qty, cd.description,"; $sqlOrderProducts .= " p.ref as product_ref, p.label as product_label,"; // Bereits verbaut (auf allen Stundenzetteln dieses Auftrags) $sqlOrderProducts .= " COALESCE((SELECT SUM(sp.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp"; $sqlOrderProducts .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = sp.fk_stundenzettel"; $sqlOrderProducts .= " WHERE sp.fk_commandedet = cd.rowid AND sp.origin IN ('order', 'added')), 0) as qty_used,"; // Bereits als entfällt markiert (auf allen Stundenzetteln dieses Auftrags) $sqlOrderProducts .= " COALESCE((SELECT SUM(sp2.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp2"; $sqlOrderProducts .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s2 ON s2.rowid = sp2.fk_stundenzettel"; $sqlOrderProducts .= " WHERE (sp2.fk_commandedet = cd.rowid OR (sp2.fk_product = cd.fk_product AND cd.fk_product > 0)) AND sp2.origin = 'omitted' AND s2.fk_commande = ".((int)$object->fk_commande)."), 0) as qty_omitted"; $sqlOrderProducts .= " FROM ".MAIN_DB_PREFIX."commandedet as cd"; $sqlOrderProducts .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product"; $sqlOrderProducts .= " WHERE cd.fk_commande = ".((int)$object->fk_commande); $sqlOrderProducts .= " AND (cd.fk_product > 0 OR ((cd.fk_product IS NULL OR cd.fk_product = 0) AND cd.description IS NOT NULL AND cd.description != ''))"; $sqlOrderProducts .= " AND (cd.special_code IS NULL OR cd.special_code = 0)"; $sqlOrderProducts .= " ORDER BY cd.rang"; $resqlOrderProducts = $db->query($sqlOrderProducts); if ($resqlOrderProducts) { while ($objProd = $db->fetch_object($resqlOrderProducts)) { // Verfügbare Menge = Auftragsmenge - verbaut - bereits entfallen $objProd->qty_available = $objProd->qty - $objProd->qty_used - $objProd->qty_omitted; $orderProducts[] = $objProd; } } } // Nur anzeigen wenn Produkte mit verfügbarer Menge existieren $hasAvailableProducts = false; foreach ($orderProducts as $op) { if ($op->qty_available > 0) { $hasAvailableProducts = true; break; } } print ''; print ''; print ''; print ''; print ''; print ''; print ''; // Produkt-Auswahl - NUR Produkte aus dem Auftrag mit verfügbarer Menge // Mehraufwand-Produkte mit verbleibender Menge laden (aus allen Stundenzetteln des Auftrags) $mehraufwandAvailable = array(); if ($object->fk_commande > 0) { $sqlMehraufwand = "SELECT sp.rowid, sp.fk_product, sp.qty, sp.qty_done, sp.description,"; $sqlMehraufwand .= " p.ref as product_ref, p.label as product_label"; $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)$object->fk_commande); $sqlMehraufwand .= " AND sp.origin = 'additional'"; $sqlMehraufwand .= " AND (sp.qty - sp.qty_done) > 0"; // Nur wo noch etwas verfügbar ist $sqlMehraufwand .= " ORDER BY p.label, sp.description"; $resqlMehraufwand = $db->query($sqlMehraufwand); if ($resqlMehraufwand) { while ($objM = $db->fetch_object($resqlMehraufwand)) { $objM->qty_available = $objM->qty - $objM->qty_done; $mehraufwandAvailable[] = $objM; } } } $hasMehraufwandProducts = (count($mehraufwandAvailable) > 0); print ''; // Menge (mit dynamischem max basierend auf Produktauswahl) print ''; // Grund (Desktop: in Zeile, Mobile: ausgeblendet) print ''; // Hinzufügen-Button (colspan für beide Button-Spalten) print ''; print ''; print ''; // Mobile: Grund in separater Zeile print ''; print ''; print ''; // JavaScript für dynamische Max-Menge und Bereichs-Toggle print ''; print '
'.$langs->trans("Entfaellt").' - '.$langs->trans("EntfaelltDesc").'
'.$langs->trans("Product").''.$langs->trans("Qty").''.$langs->trans("Reason").'
'; if ($prod->fk_product > 0) { print ''; print img_picto('', 'product', 'class="pictofixedwidth"'); print $prod->product_ref.' - '.$prod->product_label; print ''; } else { print img_picto('', 'generic', 'class="pictofixedwidth"'); $displayText = !empty($prod->description) ? strip_tags($prod->description) : $langs->trans("FreeText"); if (strlen($displayText) > 80) { $displayText = substr($displayText, 0, 77).'...'; } print ''.$displayText.''; } print ' '.$langs->trans("Entfaellt").''; // Mobile: Grund unter Produktname anzeigen if (!empty($prod->description)) { print '
'.dol_trunc(strip_tags($prod->description), 50).'
'; } print '
'; print '
'; print ''; print ''; print ''; print ''; print '
'; print '
'; if (!empty($prod->description)) { print dol_htmlentitiesbr($prod->description); } else { print '-'; } print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print '
'.$langs->trans("AddEntfaellt").'
'; if ($hasAvailableProducts || $hasMehraufwandProducts) { print ''; } else { print ''.$langs->trans("AllProductsUsedOrOmitted").''; } print ''; print ''; print ''; print ''; print ''; print ''; print '
'; print ''; print '
'; print '
'; } // ============================================= // BEREICH: MEHRAUFWAND (zusätzliche Produkte nicht aus Auftrag) // ============================================= if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) { // Bestätigung für Löschen if ($action == 'delete_mehraufwand') { $line_id = GETPOST('line_id', 'int'); print $form->formconfirm($_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=products&line_id='.$line_id, $langs->trans('Delete'), $langs->trans('ConfirmDeleteMehraufwand'), 'confirm_delete_mehraufwand', '', 0, 1); } // Zuerst bereits erfasste Mehraufwand-Produkte zählen $mehraufwandProducts = array(); foreach ($object->products as $prod) { if ($prod->origin == 'additional') { $mehraufwandProducts[] = $prod; } } $hasMehraufwandProducts = (count($mehraufwandProducts) > 0); // Bereich nur anzeigen wenn Einträge vorhanden ODER Checkbox aktiviert ODER Admin-Standard $defaultSectionsMehr = explode(',', getDolGlobalString('STUNDENZETTEL_DEFAULT_SECTIONS', '')); $showMehraufwand = $hasMehraufwandProducts || GETPOST('show_mehraufwand', 'int') || in_array('mehraufwand', $defaultSectionsMehr); // Checkbox zum Ein-/Ausblenden print '
'; print ''; print '
'; print '
'; print ''; print ''; print ''; print ''; print ''; print ''; print ''; // Save print ''; // Delete print ''; if ($hasMehraufwandProducts) { foreach ($mehraufwandProducts as $prod) { $qty = (float)$prod->qty_done; print ''; // Produkt print ''; // Menge (qty_done) - editierbar print ''; // Grund (auf Mobile ausgeblendet, nur wenn Produkt gesetzt) print ''; // Save print ''; // Delete print ''; print ''; } } // Formular zum Hinzufügen - immer am Ende (unter den hinzugefügten) if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) { print ''; print ''; print ''; print ''; print ''; print ''; print ''; // Produkt-Auswahl print ''; // Menge print ''; // Grund (Desktop: in Zeile, Mobile: ausgeblendet) print ''; // Hinzufügen-Button (colspan für Save, Delete) print ''; print ''; print ''; // Mobile: Grund in separater Zeile print ''; print ''; print ''; } print '
'.$langs->trans("Mehraufwand").' - '.$langs->trans("MehraufwandDesc").'
'.$langs->trans("Product").''.$langs->trans("Qty").''.$langs->trans("Reason").'
'; if ($prod->fk_product > 0) { print ''; print img_picto('', 'product', 'class="pictofixedwidth"'); print $prod->product_ref.' - '.$prod->product_label; print ''; } else { print img_picto('', 'generic', 'class="pictofixedwidth"'); $displayText = !empty($prod->description) ? strip_tags($prod->description) : $langs->trans("FreeText"); if (strlen($displayText) > 80) { $displayText = substr($displayText, 0, 77).'...'; } print ''.$displayText.''; } print ' '.$langs->trans("Mehraufwand").''; // Mobile: Grund unter Produktname anzeigen $reason = ($prod->fk_product > 0) ? $prod->description : ''; if (!empty($reason)) { print '
'.dol_trunc(strip_tags($reason), 50).'
'; } print '
'; if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) { print '
'; print ''; print ''; print ''; print ''; } else { print formatQty($qty); } print '
'; if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) { // Grund nur anzeigen/editieren wenn ein Produkt gewählt wurde $reason = ($prod->fk_product > 0) ? $prod->description : ''; print ''; print ''; } else { if ($prod->fk_product > 0 && !empty($prod->description)) { print dol_escape_htmltag($prod->description); } else { print '-'; } } print ''; if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) { print ''; print ''; print ''; } print ''; if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) { print ''; print ''; print ''; } print '
'.$langs->trans("AddMehraufwand").'
'; print $form->select_produits('', 'mehraufwand_product', '', 0, 0, -1, 2, '', 0, array(), 0, '1', 0, 'minwidth200', 0, '', null, 1); print '
'.$langs->trans("OrFreeText").':
'; print ''; print '
'; print ''; print ''; print ''; print ''; print ''; print '
'; print ''; print '
'; print '
'; } // ============================================= // BEREICH: RÜCKNAHME (bereits verbaute Produkte zurücknehmen) // ============================================= if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) { // Bestätigung für Löschen if ($action == 'delete_ruecknahme') { $line_id = GETPOST('line_id', 'int'); print $form->formconfirm($_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=products&line_id='.$line_id, $langs->trans('Delete'), $langs->trans('ConfirmDeleteRuecknahme'), 'confirm_delete_ruecknahme', '', 0, 1); } // Zähle bereits erfasste Rücknahme-Produkte $ruecknahmeProducts = array(); foreach ($object->products as $prod) { if ($prod->origin == 'returned') { $ruecknahmeProducts[] = $prod; } } $hasRuecknahmeProducts = (count($ruecknahmeProducts) > 0); // Bereich nur anzeigen wenn Einträge vorhanden ODER Checkbox aktiviert ODER Admin-Standard $defaultSectionsRueck = explode(',', getDolGlobalString('STUNDENZETTEL_DEFAULT_SECTIONS', '')); $showRuecknahme = $hasRuecknahmeProducts || GETPOST('show_ruecknahme', 'int') || in_array('ruecknahme', $defaultSectionsRueck); // Checkbox zum Ein-/Ausblenden print '
'; print ''; print '
'; print '
'; print ''; print ''; print ''; print ''; print ''; print ''; print ''; // Save print ''; // Delete print ''; // Bereits erfasste Rücknahme-Produkte anzeigen if ($hasRuecknahmeProducts) { foreach ($ruecknahmeProducts as $prod) { print ''; // Produkt print ''; // Menge print ''; // Grund/Beschreibung (auf Mobile ausgeblendet) print ''; // Save print ''; // Delete print ''; print ''; } } // Formular zum Hinzufügen - nur wenn Produkte verbaut wurden // Produkte laden die bereits verbaut wurden (aus allen Stundenzetteln dieses Auftrags) $deliveredProducts = array(); if ($object->fk_commande > 0) { $sqlDelivered = "SELECT cd.rowid, cd.fk_product, cd.description,"; $sqlDelivered .= " p.ref as product_ref, p.label as product_label,"; // Bereits verbaut (auf allen Stundenzetteln dieses Auftrags) $sqlDelivered .= " COALESCE((SELECT SUM(sp.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp"; $sqlDelivered .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = sp.fk_stundenzettel"; $sqlDelivered .= " WHERE (sp.fk_commandedet = cd.rowid OR (sp.fk_product = cd.fk_product AND cd.fk_product > 0))"; $sqlDelivered .= " AND sp.origin IN ('order', 'added') AND s.fk_commande = ".((int)$object->fk_commande)."), 0) as qty_delivered,"; // Bereits zurückgenommen $sqlDelivered .= " COALESCE((SELECT SUM(sp2.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp2"; $sqlDelivered .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s2 ON s2.rowid = sp2.fk_stundenzettel"; $sqlDelivered .= " WHERE (sp2.fk_commandedet = cd.rowid OR (sp2.fk_product = cd.fk_product AND cd.fk_product > 0))"; $sqlDelivered .= " AND sp2.origin = 'returned' AND s2.fk_commande = ".((int)$object->fk_commande)."), 0) as qty_returned"; $sqlDelivered .= " FROM ".MAIN_DB_PREFIX."commandedet as cd"; $sqlDelivered .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product"; $sqlDelivered .= " WHERE cd.fk_commande = ".((int)$object->fk_commande); $sqlDelivered .= " AND (cd.fk_product > 0 OR ((cd.fk_product IS NULL OR cd.fk_product = 0) AND cd.description IS NOT NULL AND cd.description != ''))"; $sqlDelivered .= " AND (cd.special_code IS NULL OR cd.special_code = 0)"; $sqlDelivered .= " ORDER BY cd.rang"; $resqlDelivered = $db->query($sqlDelivered); if ($resqlDelivered) { while ($objProd = $db->fetch_object($resqlDelivered)) { // Verfügbare Menge = verbaut - bereits zurückgenommen $objProd->qty_available = $objProd->qty_delivered - $objProd->qty_returned; if ($objProd->qty_available > 0) { $deliveredProducts[] = $objProd; } } } // Auch verbaute Produkte ohne Auftragszeile berücksichtigen // (origin='added' mit fk_commandedet IS NULL = in Produktliste hinzugefügt, nicht aus Auftrag) $alreadyFoundProductIds = array(); foreach ($deliveredProducts as $dp) { if ($dp->fk_product > 0) { $alreadyFoundProductIds[] = (int)$dp->fk_product; } } // Verbaute Produkte mit fk_product > 0, ohne Auftragszeile $sqlAdded = "SELECT sp.fk_product, p.ref as product_ref, p.label as product_label,"; $sqlAdded .= " SUM(sp.qty_done) as qty_delivered,"; $sqlAdded .= " COALESCE((SELECT SUM(sp2.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp2"; $sqlAdded .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s2 ON s2.rowid = sp2.fk_stundenzettel"; $sqlAdded .= " WHERE sp2.fk_product = sp.fk_product AND sp2.origin = 'returned'"; $sqlAdded .= " AND s2.fk_commande = ".((int)$object->fk_commande)."), 0) as qty_returned"; $sqlAdded .= " FROM ".MAIN_DB_PREFIX."stundenzettel_product sp"; $sqlAdded .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = sp.fk_stundenzettel"; $sqlAdded .= " LEFT JOIN ".MAIN_DB_PREFIX."product p ON p.rowid = sp.fk_product"; $sqlAdded .= " WHERE sp.origin = 'added'"; $sqlAdded .= " AND (sp.fk_commandedet IS NULL OR sp.fk_commandedet = 0)"; $sqlAdded .= " AND s.fk_commande = ".((int)$object->fk_commande); $sqlAdded .= " AND sp.fk_product > 0"; if (!empty($alreadyFoundProductIds)) { $sqlAdded .= " AND sp.fk_product NOT IN (".implode(',', $alreadyFoundProductIds).")"; } $sqlAdded .= " GROUP BY sp.fk_product, p.ref, p.label"; $resqlAdded = $db->query($sqlAdded); if ($resqlAdded) { while ($objAdd = $db->fetch_object($resqlAdded)) { $objAdd->qty_available = $objAdd->qty_delivered - $objAdd->qty_returned; $objAdd->rowid = 0; $objAdd->description = ''; if ($objAdd->qty_available > 0) { $deliveredProducts[] = $objAdd; } } } // Verbaute Freitext-Produkte ohne Auftragszeile (fk_product = 0, fk_commandedet = NULL) $sqlAddedFree = "SELECT sp.rowid as sp_rowid, sp.description,"; $sqlAddedFree .= " sp.qty_done as qty_delivered, 0 as qty_returned, 0 as fk_product,"; $sqlAddedFree .= " '' as product_ref, '' as product_label"; $sqlAddedFree .= " FROM ".MAIN_DB_PREFIX."stundenzettel_product sp"; $sqlAddedFree .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = sp.fk_stundenzettel"; $sqlAddedFree .= " WHERE sp.origin = 'added'"; $sqlAddedFree .= " AND (sp.fk_commandedet IS NULL OR sp.fk_commandedet = 0)"; $sqlAddedFree .= " AND s.fk_commande = ".((int)$object->fk_commande); $sqlAddedFree .= " AND (sp.fk_product IS NULL OR sp.fk_product = 0)"; $sqlAddedFree .= " AND sp.description IS NOT NULL AND sp.description != ''"; $resqlAddedFree = $db->query($sqlAddedFree); if ($resqlAddedFree) { while ($objFree = $db->fetch_object($resqlAddedFree)) { $objFree->qty_available = $objFree->qty_delivered; $objFree->rowid = 0; $objFree->fk_product = 0; $objFree->is_mehraufwand_freetext = true; if ($objFree->qty_available > 0) { $deliveredProducts[] = $objFree; } } } } $hasDeliveredProducts = (count($deliveredProducts) > 0); print ''; print ''; print ''; print ''; print ''; print ''; print ''; // Produkt-Auswahl - NUR Produkte die bereits verbaut wurden print ''; // Menge (mit dynamischem max basierend auf Produktauswahl) print ''; // Grund (Desktop: in Zeile, Mobile: ausgeblendet) print ''; // Hinzufügen-Button (colspan für beide Button-Spalten) print ''; print ''; print ''; // Mobile: Grund in separater Zeile print ''; print ''; print ''; // JavaScript für dynamische Max-Menge print ''; print '
'.$langs->trans("Ruecknahme").' - '.$langs->trans("RuecknahmeDesc").'
'.$langs->trans("Product").''.$langs->trans("Qty").''.$langs->trans("Reason").'
'; if ($prod->fk_product > 0) { print ''; print img_picto('', 'product', 'class="pictofixedwidth"'); print $prod->product_ref.' - '.$prod->product_label; print ''; } else { print img_picto('', 'generic', 'class="pictofixedwidth"'); $displayText = !empty($prod->description) ? strip_tags($prod->description) : $langs->trans("FreeText"); if (strlen($displayText) > 80) { $displayText = substr($displayText, 0, 77).'...'; } print ''.$displayText.''; } print ' '.$langs->trans("Ruecknahme").''; // Mobile: Grund unter Produktname anzeigen if (!empty($prod->description)) { print '
'.dol_trunc(strip_tags($prod->description), 50).'
'; } print '
'; print '
'; print ''; print ''; print ''; print ''; print '
'; print '
'; if (!empty($prod->description)) { print dol_htmlentitiesbr($prod->description); } else { print '-'; } print ''; print ''; print ''; print ''; print ''; print ''; print ''; print ''; print '
'.$langs->trans("AddRuecknahme").'
'; if ($hasDeliveredProducts) { print ''; } else { print ''.$langs->trans("NoProductsDelivered").''; } print ''; print ''; print ''; print ''; print ''; print ''; print '
'; print ''; print '
'; print '
'; } // ============================================= // BEREICH: MERKZETTEL (abhakbare Notizen) // ============================================= // Bestätigung für Löschen if ($action == 'delete_note') { $note_id = GETPOST('note_id', 'int'); print $form->formconfirm($_SERVER['PHP_SELF'].'?id='.$object->id.'¬e_id='.$note_id, $langs->trans('Delete'), $langs->trans('ConfirmDeleteNote'), 'confirm_delete_note', '', 0, 1); } // Prüfen ob Notizen vorhanden $hasNotes = (count($object->notes) > 0); // Bereich nur anzeigen wenn Einträge vorhanden ODER Checkbox aktiviert ODER Admin-Standard $defaultSectionsMerk = explode(',', getDolGlobalString('STUNDENZETTEL_DEFAULT_SECTIONS', '')); $showMerkzettel = $hasNotes || GETPOST('show_merkzettel', 'int') || in_array('merkzettel', $defaultSectionsMerk); // Checkbox zum Ein-/Ausblenden print '
'; print ''; print '
'; print '
'; print ''; print ''; // Vorhandene Notizen anzeigen if (count($object->notes) > 0) { foreach ($object->notes as $note) { $isChecked = ($note->checked == 1); print ''; // Checkbox print ''; // Notiz-Text print ''; print dol_htmlentitiesbr($note->note); print ''; // Datum print ''; // Löschen print ''; print ''; } } else { print ''; } // Formular zum Hinzufügen (nur im Entwurf) if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) { print ''; print ''; print ''; print ''; // Leere Checkbox-Spalte print ''; // Notiz-Eingabe print ''; // Leere Datum-Spalte print ''; // Hinzufügen-Button print ''; print ''; print ''; } print '
'.$langs->trans("NotesMemo").' - '.$langs->trans("NotesForNextVisit").'
'; if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) { $newChecked = $isChecked ? 0 : 1; print ''; if ($isChecked) { print ''; } else { print ''; } print ''; } else { if ($isChecked) { print ''; } else { print ''; } } print ''; print dol_print_date($db->jdate($note->datec), 'dayhour'); print ''; if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) { print ''; print ''; print ''; } print '
'.$langs->trans("NoNotesYet").'
'; print ''; print ''; print ''; print '
'; print '
'; } // ============================================= // TAB: NOTIZEN // ============================================= if ($subtab == 'notes') { if ($action == 'edit' && $permissiontoadd) { // Bearbeitungsmodus print '
'; print ''; print ''; print ''; print ''; print ''; print ''; // Notiz öffentlich (für nächsten Termin) print ''; // Notiz privat print ''; print '
'.$langs->trans("NotePublic").'
('.$langs->trans("NotesForNextVisit").')
'; $doleditor = new DolEditor('note_public', $object->note_public, '', 200, 'dolibarr_notes', '', false, true, getDolGlobalString('FCKEDITOR_ENABLE_NOTE_PUBLIC'), ROWS_5, '90%'); print $doleditor->Create(1); print '
'.$langs->trans("NotePrivate").''; $doleditor = new DolEditor('note_private', $object->note_private, '', 200, 'dolibarr_notes', '', false, true, getDolGlobalString('FCKEDITOR_ENABLE_NOTE_PRIVATE'), ROWS_5, '90%'); print $doleditor->Create(1); print '
'; print '
'; print ''; print '   '; print ''.$langs->trans("Cancel").''; print '
'; print '
'; } else { // Ansicht print ''; // Notiz öffentlich (für nächsten Termin) print ''; // Notiz privat print ''; print '
'.$langs->trans("NotePublic").'
('.$langs->trans("NotesForNextVisit").')
'; if (!empty($object->note_public)) { print dol_htmlentitiesbr($object->note_public); } else { print '-'; } print '
'.$langs->trans("NotePrivate").''; if (!empty($object->note_private)) { print dol_htmlentitiesbr($object->note_private); } else { print '-'; } print '
'; // Bearbeiten-Button if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) { print '
'; print ''; print img_picto('', 'edit', 'class="pictofixedwidth"').$langs->trans("Modify"); print ''; print '
'; } } } print '
'; // fichecenter print dol_get_fiche_end(); // Buttons (immer anzeigen) print '
'; if ($object->status == Stundenzettel::STATUS_DRAFT) { if ($permissiontovalidate) { print ''.$langs->trans("Validate").''; } if ($permissiontodeleteobj) { print ''.$langs->trans("Delete").''; } } if ($object->status == Stundenzettel::STATUS_VALIDATED) { if ($permissiontoadd) { print ''.$langs->trans("SetToDraft").''; } } print '
'; } llxFooter(); $db->close();