* * Stundenzettel PWA - Zentrale JSON-API * Alle CRUD-Operationen fuer die Mobile App */ if (!defined('NOLOGIN')) { define('NOLOGIN', '1'); } if (!defined('NOREQUIREMENU')) { define('NOREQUIREMENU', '1'); } if (!defined('NOREQUIREHTML')) { define('NOREQUIREHTML', '1'); } if (!defined('NOREQUIREAJAX')) { define('NOREQUIREAJAX', '1'); } // Dolibarr-Umgebung laden $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(json_encode(array('success' => false, 'error' => 'Dolibarr nicht geladen'))); 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.'/user/class/user.class.php'; dol_include_once('/stundenzettel/class/stundenzettel.class.php'); header('Content-Type: application/json; charset=UTF-8'); $action = GETPOST('action', 'aZ09'); $response = array('success' => false); // ============================================================ // TOKEN-VALIDIERUNG // ============================================================ $token = GETPOST('token', 'none'); if (empty($token)) { http_response_code(401); echo json_encode(array('success' => false, 'error' => 'Kein Token')); exit; } $tokenData = json_decode(base64_decode($token), true); if (!$tokenData || empty($tokenData['user_id']) || empty($tokenData['expires'])) { http_response_code(401); echo json_encode(array('success' => false, 'error' => 'Ungueltiges Token')); exit; } if ($tokenData['expires'] < time()) { http_response_code(401); echo json_encode(array('success' => false, 'error' => 'Token abgelaufen')); exit; } $expectedHash = md5($tokenData['user_id'] . $tokenData['login'] . getDolGlobalString('MAIN_SECURITY_SALT', 'defaultsalt')); if ($tokenData['hash'] !== $expectedHash) { http_response_code(401); echo json_encode(array('success' => false, 'error' => 'Token manipuliert')); exit; } // Benutzer laden $user = new User($db); if ($user->fetch($tokenData['user_id']) <= 0 || $user->statut != 1) { http_response_code(401); echo json_encode(array('success' => false, 'error' => 'Benutzer nicht aktiv')); exit; } $user->getrights(); // Basis-Berechtigung if (!$user->hasRight('stundenzettel', 'read')) { http_response_code(403); echo json_encode(array('success' => false, 'error' => 'Keine Berechtigung')); exit; } $langs->loadLangs(array("stundenzettel@stundenzettel", "orders", "products")); // Schreib-Berechtigung pruefen $canWrite = $user->hasRight('stundenzettel', 'write'); $canWriteAll = $user->hasRight('stundenzettel', 'write', 'all') || $user->admin; $canReadAll = $user->hasRight('stundenzettel', 'read', 'all') || $user->admin; /** * Hilfsfunktion: Mengen formatieren */ function pwaFormatQty($qty) { $qty = (float)$qty; if ($qty == floor($qty)) { return number_format($qty, 0, ',', '.'); } return rtrim(rtrim(number_format($qty, 2, ',', '.'), '0'), ','); } /** * Hilfsfunktion: Pruefen ob User Schreibzugriff auf STZ hat */ function canEditStz($stz, $user, $canWriteAll) { if ($stz->status != Stundenzettel::STATUS_DRAFT) return false; if ($stz->fk_user_author == $user->id) return true; if ($canWriteAll) return true; return false; } /** * Hilfsfunktion: Status-Label zurueckgeben */ function getStatusLabel($status) { switch ((int)$status) { case 0: return 'Entwurf'; case 1: return 'Freigegeben'; case 2: return 'Abgerechnet'; case 9: return 'Storniert'; default: return 'Unbekannt'; } } // ============================================================ // API ACTIONS // ============================================================ switch ($action) { // ---- Kundensuche ---- case 'search_customers': $term = GETPOST('term', 'alphanohtml'); if (strlen($term) < 2) { $response['error'] = 'Mindestens 2 Zeichen'; break; } $sql = "SELECT s.rowid as id, s.nom as name, COUNT(DISTINCT c.rowid) as order_count"; $sql .= " FROM ".MAIN_DB_PREFIX."societe as s"; $sql .= " INNER JOIN ".MAIN_DB_PREFIX."commande as c ON c.fk_soc = s.rowid"; $sql .= " WHERE s.nom LIKE '%".$db->escape($term)."%'"; $sql .= " AND c.fk_statut = 1"; // Nur validierte (laufende) Auftraege $sql .= " AND s.entity IN (".getEntity('societe').")"; $sql .= " GROUP BY s.rowid, s.nom"; $sql .= " ORDER BY s.nom ASC"; $sql .= " LIMIT 20"; $resql = $db->query($sql); $customers = array(); if ($resql) { while ($obj = $db->fetch_object($resql)) { $customers[] = array( 'id' => (int)$obj->id, 'name' => $obj->name, 'order_count' => (int)$obj->order_count ); } } $response['success'] = true; $response['customers'] = $customers; break; // ---- Auftraege eines Kunden ---- case 'get_customer_orders': $customerId = GETPOST('customer_id', 'int'); if ($customerId <= 0) { $response['error'] = 'Keine Kunden-ID'; break; } $sql = "SELECT c.rowid as id, c.ref, c.ref_client, c.date_commande, c.fk_statut as status"; $sql .= " FROM ".MAIN_DB_PREFIX."commande as c"; $sql .= " WHERE c.fk_soc = ".((int)$customerId); $sql .= " AND c.fk_statut = 1"; // Nur validierte (laufende) Auftraege, nicht geliefert/fakturiert $sql .= " AND c.entity IN (".getEntity('commande').")"; $sql .= " ORDER BY c.date_commande DESC, c.rowid DESC"; $resql = $db->query($sql); $orders = array(); if ($resql) { while ($obj = $db->fetch_object($resql)) { // Offener Stundenzettel pruefen $sqlStz = "SELECT rowid FROM ".MAIN_DB_PREFIX."stundenzettel"; $sqlStz .= " WHERE fk_commande = ".((int)$obj->id); $sqlStz .= " AND status = 0"; if (!$canReadAll) { $sqlStz .= " AND fk_user_author = ".((int)$user->id); } $sqlStz .= " ORDER BY date_stundenzettel DESC LIMIT 1"; $resStz = $db->query($sqlStz); $hasDraftStz = false; $draftStzId = 0; if ($resStz && $db->num_rows($resStz) > 0) { $objStz = $db->fetch_object($resStz); $hasDraftStz = true; $draftStzId = (int)$objStz->rowid; } $orders[] = array( 'id' => (int)$obj->id, 'ref' => $obj->ref, 'ref_client' => $obj->ref_client ?: '', 'date' => dol_print_date($db->jdate($obj->date_commande), 'day'), 'status' => (int)$obj->status, 'has_draft_stz' => $hasDraftStz, 'draft_stz_id' => $draftStzId ); } } $response['success'] = true; $response['orders'] = $orders; break; // ---- Komplett-Kontext fuer einen Auftrag ---- case 'get_order_context': $orderId = GETPOST('order_id', 'int'); $stzId = GETPOST('stz_id', 'int'); if ($orderId <= 0) { $response['error'] = 'Keine Auftrags-ID'; break; } // Auftrag laden $order = new Commande($db); if ($order->fetch($orderId) <= 0) { $response['error'] = 'Auftrag nicht gefunden'; break; } // Kunde laden $customer = new Societe($db); $customer->fetch($order->socid); // Auftragsdaten $response['order'] = array( 'id' => (int)$order->id, 'ref' => $order->ref, 'date' => dol_print_date($order->date_commande, 'day'), 'customer_name' => $customer->name, 'customer_id' => (int)$customer->id, 'status' => (int)$order->statut ); // Stundenzettel finden (per ID oder letzten Draft) $stz = new Stundenzettel($db); $stzFound = false; if ($stzId > 0) { if ($stz->fetch($stzId) > 0) { $stzFound = true; } } if (!$stzFound) { // Letzten Draft fuer diesen Auftrag suchen $sqlStz = "SELECT rowid FROM ".MAIN_DB_PREFIX."stundenzettel"; $sqlStz .= " WHERE fk_commande = ".((int)$orderId); if (!$canReadAll) { $sqlStz .= " AND fk_user_author = ".((int)$user->id); } $sqlStz .= " ORDER BY status ASC, date_stundenzettel DESC, rowid DESC"; $sqlStz .= " LIMIT 1"; $resStz = $db->query($sqlStz); if ($resStz && $db->num_rows($resStz) > 0) { $objStz = $db->fetch_object($resStz); $stz->fetch($objStz->rowid); $stzFound = true; } } // Prüfe ob User den aktuellen STZ editieren kann $canEdit = false; if ($stzFound) { $canEdit = canEditStz($stz, $user, $canWriteAll); } $response['can_write'] = $canWrite; // Allgemeine Schreibberechtigung $response['can_edit_stz'] = $canWrite && $canEdit; // Aktuellen STZ editierbar // STZ-Daten if ($stzFound) { $response['stz'] = array( 'id' => (int)$stz->id, 'ref' => $stz->ref, 'date' => dol_print_date($stz->date_stundenzettel, 'day'), 'date_iso' => dol_print_date($stz->date_stundenzettel, '%Y-%m-%d'), 'status' => (int)$stz->status, 'status_label' => getStatusLabel($stz->status), 'hourly_rate' => $stz->hourly_rate, 'fk_user_author' => (int)$stz->fk_user_author ); // Produkte laden $stz->fetchProducts(); $products = array(); foreach ($stz->products as $p) { $products[] = array( 'id' => (int)$p->rowid, 'fk_product' => (int)$p->fk_product, 'fk_commandedet' => (int)$p->fk_commandedet, 'ref' => $p->product_ref, 'label' => $p->product_label, 'description' => strip_tags($p->description), 'qty_original' => (float)$p->qty_original, 'qty_done' => (float)$p->qty_done, 'origin' => $p->origin ); } $response['products'] = $products; // Leistungen laden $stz->fetchLeistungen(); $leistungen = array(); foreach ($stz->leistungen as $l) { $leistungen[] = array( 'id' => (int)$l->rowid, 'date' => dol_print_date($db->jdate($l->date_leistung), 'day'), 'date_iso' => dol_print_date($db->jdate($l->date_leistung), '%Y-%m-%d'), 'time_start' => substr($l->time_start, 0, 5), 'time_end' => substr($l->time_end, 0, 5), 'duration_minutes' => (int)$l->duration, 'description' => strip_tags($l->description ?: ''), 'service_name' => $l->product_label ?: '', 'fk_product' => (int)$l->fk_product ); } $response['leistungen'] = $leistungen; // Notizen laden $stz->fetchNotes(); $notes = array(); foreach ($stz->notes as $n) { $notes[] = array( 'id' => (int)$n->rowid, 'note' => $n->note, 'checked' => (int)$n->checked, 'date' => dol_print_date($db->jdate($n->datec), 'dayhour') ); } $response['notes'] = $notes; } else { $response['stz'] = null; $response['products'] = array(); $response['leistungen'] = array(); $response['notes'] = array(); } // Tracking-Daten (Lieferauflistung) - Live-Berechnung wie Desktop // Auftragspositionen mit Verbaut/Mehraufwand/Entfaellt/Ruecknahme berechnen $tracking = array(); $sqlTrack = "SELECT cd.rowid, cd.fk_product, cd.qty as qty_ordered, cd.description,"; $sqlTrack .= " p.ref as product_ref, p.label as product_label,"; // Verbaut (origin order/added) $sqlTrack .= " COALESCE((SELECT SUM(sp.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp"; $sqlTrack .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = sp.fk_stundenzettel"; $sqlTrack .= " WHERE sp.fk_commandedet = cd.rowid AND sp.origin IN ('order', 'added')), 0) as qty_delivered,"; // Mehraufwand $sqlTrack .= " COALESCE((SELECT SUM(sp2.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp2"; $sqlTrack .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s2 ON s2.rowid = sp2.fk_stundenzettel"; $sqlTrack .= " WHERE sp2.fk_product = cd.fk_product AND cd.fk_product > 0 AND sp2.origin = 'additional' AND s2.fk_commande = ".((int)$orderId)."), 0) as qty_additional,"; // Entfaellt $sqlTrack .= " COALESCE((SELECT SUM(sp3.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp3"; $sqlTrack .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s3 ON s3.rowid = sp3.fk_stundenzettel"; $sqlTrack .= " WHERE sp3.origin = 'omitted' AND s3.fk_commande = ".((int)$orderId); $sqlTrack .= " AND ((sp3.fk_product = cd.fk_product AND cd.fk_product > 0) OR sp3.fk_commandedet = cd.rowid)), 0) as qty_omitted,"; // Ruecknahmen $sqlTrack .= " COALESCE((SELECT SUM(sp4.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp4"; $sqlTrack .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s4 ON s4.rowid = sp4.fk_stundenzettel"; $sqlTrack .= " WHERE sp4.origin = 'returned' AND s4.fk_commande = ".((int)$orderId); $sqlTrack .= " AND ((sp4.fk_product = cd.fk_product AND cd.fk_product > 0) OR sp4.fk_commandedet = cd.rowid)), 0) as qty_returned"; $sqlTrack .= " FROM ".MAIN_DB_PREFIX."commandedet as cd"; $sqlTrack .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product"; $sqlTrack .= " WHERE cd.fk_commande = ".((int)$orderId); $sqlTrack .= " 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 != ''))"; $sqlTrack .= " AND (cd.special_code IS NULL OR cd.special_code = 0)"; $sqlTrack .= " ORDER BY cd.rang"; $resTrack = $db->query($sqlTrack); if ($resTrack) { while ($obj = $db->fetch_object($resTrack)) { $qtyAdditional = (float)$obj->qty_additional; $qtyOmitted = (float)$obj->qty_omitted; $qtyReturned = (float)$obj->qty_returned; // Effektiv bestellt = Original + Mehraufwand - Entfaellt - Ruecknahmen $effectiveOrdered = $obj->qty_ordered + $qtyAdditional - $qtyOmitted - $qtyReturned; // Effektiv verbaut = Verbaut - Ruecknahmen $effectiveDelivered = $obj->qty_delivered - $qtyReturned; $qtyRemaining = $effectiveOrdered - $effectiveDelivered; $label = ''; if ($obj->product_ref) { $label = $obj->product_ref.' - '.$obj->product_label; } else { $label = strip_tags($obj->description ?: ''); if (strlen($label) > 80) $label = substr($label, 0, 77).'...'; } $tracking[] = array( 'ref' => $obj->product_ref ?: '', 'label' => $label, 'qty_ordered' => (float)$effectiveOrdered, 'qty_delivered' => (float)$effectiveDelivered, 'qty_remaining' => (float)$qtyRemaining, 'qty_additional' => $qtyAdditional, 'qty_omitted' => $qtyOmitted, 'qty_returned' => $qtyReturned ); } } $response['tracking'] = $tracking; // Leistungen/Arbeitsstunden gruppiert nach Service (fuer Lieferauflistung) $leistungenSummary = array(); $sqlLeist = "SELECT COALESCE(l.fk_product, 0) as service_id,"; $sqlLeist .= " p.ref as service_ref, p.label as service_label,"; $sqlLeist .= " SUM(l.duration) as total_minutes, COUNT(l.rowid) as entry_count"; $sqlLeist .= " FROM ".MAIN_DB_PREFIX."stundenzettel_leistung l"; $sqlLeist .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = l.fk_stundenzettel"; $sqlLeist .= " LEFT JOIN ".MAIN_DB_PREFIX."product p ON p.rowid = l.fk_product"; $sqlLeist .= " WHERE s.fk_commande = ".((int)$orderId); if (!$canReadAll) { $sqlLeist .= " AND s.fk_user_author = ".((int)$user->id); } $sqlLeist .= " GROUP BY COALESCE(l.fk_product, 0), p.ref, p.label"; $sqlLeist .= " ORDER BY p.ref, p.label"; $resLeist = $db->query($sqlLeist); if ($resLeist) { while ($obj = $db->fetch_object($resLeist)) { $totalMin = (int)$obj->total_minutes; $h = floor($totalMin / 60); $m = $totalMin % 60; $leistungenSummary[] = array( 'service_ref' => $obj->service_ref ?: '', 'service_label' => $obj->service_label ?: 'Nicht zugeordnet', 'total_minutes' => $totalMin, 'total_hours' => sprintf('%d:%02d h', $h, $m), 'entry_count' => (int)$obj->entry_count ); } } $response['leistungen_summary'] = $leistungenSummary; // Einzelne Leistungen aller STZ (fuer Lieferauflistung Detail) $leistungenAll = array(); $sqlLeistAll = "SELECT l.rowid, l.fk_stundenzettel, l.date_leistung, l.time_start, l.time_end,"; $sqlLeistAll .= " l.duration, l.description, s.ref as stz_ref,"; $sqlLeistAll .= " p.ref as service_ref, p.label as service_label"; $sqlLeistAll .= " FROM ".MAIN_DB_PREFIX."stundenzettel_leistung l"; $sqlLeistAll .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = l.fk_stundenzettel"; $sqlLeistAll .= " LEFT JOIN ".MAIN_DB_PREFIX."product p ON p.rowid = l.fk_product"; $sqlLeistAll .= " WHERE s.fk_commande = ".((int)$orderId); if (!$canReadAll) { $sqlLeistAll .= " AND s.fk_user_author = ".((int)$user->id); } $sqlLeistAll .= " ORDER BY s.date_stundenzettel DESC, l.date_leistung, l.time_start"; $resLeistAll = $db->query($sqlLeistAll); if ($resLeistAll) { while ($obj = $db->fetch_object($resLeistAll)) { $dur = (int)$obj->duration; $h = floor($dur / 60); $m = $dur % 60; $leistungenAll[] = array( 'stz_ref' => $obj->stz_ref, 'date' => dol_print_date($db->jdate($obj->date_leistung), 'day'), 'time_start' => $obj->time_start ? substr($obj->time_start, 0, 5) : '', 'time_end' => $obj->time_end ? substr($obj->time_end, 0, 5) : '', 'duration' => sprintf('%d:%02d h', $h, $m), 'service' => $obj->service_ref ? $obj->service_ref : '', 'description' => strip_tags($obj->description ?: '') ); } } $response['leistungen_all'] = $leistungenAll; // Alle STZ fuer diesen Auftrag $stzList = array(); $sqlList = "SELECT s.rowid, s.ref, s.date_stundenzettel, s.status, s.fk_user_author,"; $sqlList .= " (SELECT COUNT(*) FROM ".MAIN_DB_PREFIX."stundenzettel_leistung WHERE fk_stundenzettel = s.rowid) as leistung_count,"; $sqlList .= " (SELECT COALESCE(SUM(duration), 0) FROM ".MAIN_DB_PREFIX."stundenzettel_leistung WHERE fk_stundenzettel = s.rowid) as total_minutes,"; $sqlList .= " (SELECT COUNT(*) FROM ".MAIN_DB_PREFIX."stundenzettel_product WHERE fk_stundenzettel = s.rowid AND origin IN ('order','added')) as product_count"; $sqlList .= " FROM ".MAIN_DB_PREFIX."stundenzettel as s"; $sqlList .= " WHERE s.fk_commande = ".((int)$orderId); if (!$canReadAll) { $sqlList .= " AND s.fk_user_author = ".((int)$user->id); } $sqlList .= " ORDER BY s.date_stundenzettel DESC, s.rowid DESC"; $resList = $db->query($sqlList); if ($resList) { while ($obj = $db->fetch_object($resList)) { $totalMin = (int)$obj->total_minutes; $h = floor($totalMin / 60); $m = $totalMin % 60; $totalHours = ($h > 0 ? $h.'h' : '') . ($m > 0 ? ' '.$m.'min' : ''); if (!$totalHours) $totalHours = '0h'; $stzList[] = array( 'id' => (int)$obj->rowid, 'ref' => $obj->ref, 'date' => dol_print_date($db->jdate($obj->date_stundenzettel), 'day'), 'status' => (int)$obj->status, 'status_label' => getStatusLabel($obj->status), 'leistung_count' => (int)$obj->leistung_count, 'total_hours' => trim($totalHours), 'product_count' => (int)$obj->product_count ); } } $response['stz_list'] = $stzList; // Auftragspositionen fuer Produktliste-Tab (Uebernahme in STZ) // Live-Berechnung wie im Desktop (stundenzettel_commande.php) $orderLines = array(); $sqlOL = "SELECT cd.rowid, cd.fk_product, cd.qty, cd.description,"; $sqlOL .= " p.ref as product_ref, p.label as product_label,"; // Verbaut (origin order/added) $sqlOL .= " COALESCE((SELECT SUM(sp.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp"; $sqlOL .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = sp.fk_stundenzettel"; $sqlOL .= " WHERE sp.fk_commandedet = cd.rowid AND sp.origin IN ('order', 'added')), 0) as qty_delivered,"; // Mehraufwand (origin additional) $sqlOL .= " COALESCE((SELECT SUM(sp2.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp2"; $sqlOL .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s2 ON s2.rowid = sp2.fk_stundenzettel"; $sqlOL .= " WHERE sp2.fk_product = cd.fk_product AND cd.fk_product > 0 AND sp2.origin = 'additional' AND s2.fk_commande = ".((int)$orderId)."), 0) as qty_additional,"; // Entfaellt $sqlOL .= " COALESCE((SELECT SUM(sp3.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp3"; $sqlOL .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s3 ON s3.rowid = sp3.fk_stundenzettel"; $sqlOL .= " WHERE sp3.origin = 'omitted' AND s3.fk_commande = ".((int)$orderId); $sqlOL .= " AND ((sp3.fk_product = cd.fk_product AND cd.fk_product > 0) OR sp3.fk_commandedet = cd.rowid)), 0) as qty_omitted,"; // Ruecknahmen $sqlOL .= " COALESCE((SELECT SUM(sp4.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp4"; $sqlOL .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s4 ON s4.rowid = sp4.fk_stundenzettel"; $sqlOL .= " WHERE sp4.origin = 'returned' AND s4.fk_commande = ".((int)$orderId); $sqlOL .= " AND ((sp4.fk_product = cd.fk_product AND cd.fk_product > 0) OR sp4.fk_commandedet = cd.rowid)), 0) as qty_returned"; $sqlOL .= " FROM ".MAIN_DB_PREFIX."commandedet as cd"; $sqlOL .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product"; $sqlOL .= " WHERE cd.fk_commande = ".((int)$orderId); $sqlOL .= " 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 != ''))"; $sqlOL .= " AND (cd.special_code IS NULL OR cd.special_code = 0)"; $sqlOL .= " ORDER BY cd.rang"; $resOL = $db->query($sqlOL); if ($resOL) { while ($obj = $db->fetch_object($resOL)) { $label = ''; if ($obj->product_ref) { $label = $obj->product_ref.' - '.$obj->product_label; } else { $label = strip_tags($obj->description ?: ''); if (strlen($label) > 80) $label = substr($label, 0, 77).'...'; } $qtyDelivered = (float)$obj->qty_delivered; $qtyAdditional = (float)$obj->qty_additional; $qtyOmitted = (float)$obj->qty_omitted; $qtyReturned = (float)$obj->qty_returned; $effectiveDelivered = $qtyDelivered - $qtyReturned; // Effektive Gesamtmenge = Original + Mehraufwand - Entfaellt - Ruecknahmen (wie Desktop) $effectiveOrdered = $obj->qty + $qtyAdditional - $qtyOmitted - $qtyReturned; $qtyRemaining = $effectiveOrdered - $effectiveDelivered; // Pruefen ob bereits auf dem aktuellen STZ $alreadyOnStz = false; if ($stzFound) { foreach ($stz->products as $sp) { if ($sp->fk_commandedet == $obj->rowid && in_array($sp->origin, array('order', 'added'))) { $alreadyOnStz = true; break; } } } $orderLines[] = array( 'id' => (int)$obj->rowid, 'fk_product' => (int)$obj->fk_product, 'ref' => $obj->product_ref ?: '', 'label' => $label, 'description' => strip_tags($obj->description ?: ''), 'qty' => (float)$obj->qty, 'qty_effective' => (float)$effectiveOrdered, 'qty_delivered' => $effectiveDelivered, 'qty_remaining' => $qtyRemaining, 'qty_additional' => $qtyAdditional, 'qty_omitted' => $qtyOmitted, 'qty_returned' => $qtyReturned, 'already_on_stz' => $alreadyOnStz ); } } $response['order_lines'] = $orderLines; // Mehraufwand-Produkte laden (nicht aus Auftrag, wie Desktop stundenzettel_commande.php) // origin='additional' (Mehraufwand-Bestellung) + origin='added' ohne fk_commandedet (manuell hinzugefuegt) $mehraufwandLines = array(); // Ruecknahmen laden fuer Zuordnung $returnedProducts = array(); $sqlRet = "SELECT sp.fk_product, sp.product_label, sp.description, SUM(sp.qty_done) as qty_returned"; $sqlRet .= " FROM ".MAIN_DB_PREFIX."stundenzettel_product sp"; $sqlRet .= " INNER JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = sp.fk_stundenzettel"; $sqlRet .= " WHERE s.fk_commande = ".((int)$orderId); $sqlRet .= " AND sp.origin = 'returned'"; $sqlRet .= " GROUP BY sp.fk_product, sp.product_label, sp.description"; $resRet = $db->query($sqlRet); if ($resRet) { while ($objRet = $db->fetch_object($resRet)) { $key = ''; if ($objRet->fk_product > 0) { $key = 'prod_'.$objRet->fk_product; } else { $key = 'desc_'.md5(trim(strip_tags($objRet->description))); } $returnedProducts[$key] = (float)$objRet->qty_returned; } } // Mehraufwand-Produkte $sqlMA = "SELECT sp.fk_product, sp.product_ref, sp.product_label, sp.description,"; $sqlMA .= " SUM(CASE WHEN sp.origin = 'additional' THEN sp.qty_done ELSE 0 END) as qty_additional,"; $sqlMA .= " SUM(CASE WHEN sp.origin = 'added' AND (sp.fk_commandedet IS NULL OR sp.fk_commandedet = 0) THEN sp.qty_done ELSE 0 END) as qty_added,"; $sqlMA .= " GROUP_CONCAT(DISTINCT s.ref SEPARATOR ', ') as stundenzettel_refs"; $sqlMA .= " FROM ".MAIN_DB_PREFIX."stundenzettel_product sp"; $sqlMA .= " INNER JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = sp.fk_stundenzettel"; $sqlMA .= " WHERE s.fk_commande = ".((int)$orderId); $sqlMA .= " AND (sp.origin = 'additional' OR (sp.origin = 'added' AND (sp.fk_commandedet IS NULL OR sp.fk_commandedet = 0)))"; $sqlMA .= " GROUP BY sp.fk_product, sp.product_ref, sp.product_label, sp.description"; $sqlMA .= " ORDER BY sp.product_ref, sp.description"; $resMA = $db->query($sqlMA); if ($resMA) { while ($objMA = $db->fetch_object($resMA)) { $qtyAdditional = (float)$objMA->qty_additional; $qtyAdded = (float)$objMA->qty_added; // Ruecknahmen zuordnen $returnKey = ($objMA->fk_product > 0) ? 'prod_'.$objMA->fk_product : 'desc_'.md5(trim(strip_tags($objMA->description))); $qtyReturned = isset($returnedProducts[$returnKey]) ? $returnedProducts[$returnKey] : 0; // Beauftragt (Zielmenge) und Verbaut berechnen (wie Desktop) $qtyTarget = (($qtyAdditional > 0) ? $qtyAdditional : $qtyAdded) - $qtyReturned; $qtyDone = $qtyAdded - $qtyReturned; $qtyRemaining = $qtyTarget - $qtyDone; $isDone = ($qtyRemaining <= 0) || ($qtyTarget <= 0 && $qtyReturned > 0); // Label zusammenbauen $label = ''; if ($objMA->product_ref) { $label = $objMA->product_ref.' - '.$objMA->product_label; } else { $label = strip_tags($objMA->description ?: ''); if (strlen($label) > 80) $label = substr($label, 0, 77).'...'; } $mehraufwandLines[] = array( 'fk_product' => (int)$objMA->fk_product, 'ref' => $objMA->product_ref ?: '', 'label' => $label, 'description' => strip_tags($objMA->description ?: ''), 'qty_target' => (float)$qtyTarget, 'qty_done' => (float)$qtyDone, 'qty_remaining' => (float)$qtyRemaining, 'qty_returned' => (float)$qtyReturned, 'stz_refs' => $objMA->stundenzettel_refs ?: '', 'is_done' => $isDone ); } } $response['mehraufwand_lines'] = $mehraufwandLines; $response['success'] = true; break; // ---- Neuen Stundenzettel erstellen ---- case 'create_stundenzettel': if (!$canWrite) { $response['error'] = 'Keine Schreibberechtigung'; break; } $orderId = GETPOST('order_id', 'int'); $date = GETPOST('date', 'alpha'); if ($orderId <= 0 || empty($date)) { $response['error'] = 'Auftrags-ID und Datum erforderlich'; break; } // Auftrag pruefen $order = new Commande($db); if ($order->fetch($orderId) <= 0) { $response['error'] = 'Auftrag nicht gefunden'; break; } // Prüfe ob fuer dieses Datum bereits ein STZ existiert $sqlCheck = "SELECT rowid FROM ".MAIN_DB_PREFIX."stundenzettel"; $sqlCheck .= " WHERE fk_commande = ".((int)$orderId); $sqlCheck .= " AND date_stundenzettel = '".$db->escape($date)."'"; $sqlCheck .= " AND fk_user_author = ".((int)$user->id); $resCheck = $db->query($sqlCheck); if ($resCheck && $db->num_rows($resCheck) > 0) { $objExist = $db->fetch_object($resCheck); $response['success'] = true; $response['stz_id'] = (int)$objExist->rowid; $response['message'] = 'Stundenzettel fuer dieses Datum existiert bereits'; break; } $stz = new Stundenzettel($db); $stz->fk_commande = $orderId; $stz->fk_soc = $order->socid; $stz->date_stundenzettel = $db->idate(strtotime($date)); $stz->fk_user_author = $user->id; $result = $stz->create($user); if ($result > 0) { $response['success'] = true; $response['stz_id'] = (int)$result; } else { $response['error'] = $stz->error ?: 'Fehler beim Erstellen'; } break; // ---- Leistung hinzufuegen ---- case 'add_leistung': $stzId = GETPOST('stz_id', 'int'); $date = GETPOST('date', 'alpha'); $timeStart = GETPOST('time_start', 'alpha'); $timeEnd = GETPOST('time_end', 'alpha'); $description = GETPOST('description', 'restricthtml'); $fkProduct = GETPOST('fk_product', 'int'); $stz = new Stundenzettel($db); if ($stz->fetch($stzId) <= 0) { $response['error'] = 'Stundenzettel nicht gefunden'; break; } if (!canEditStz($stz, $user, $canWriteAll)) { $response['error'] = 'Keine Berechtigung oder nicht im Entwurf'; break; } $result = $stz->addLeistung($user, $date, $timeStart, $timeEnd, $description, $fkProduct > 0 ? $fkProduct : null); if ($result > 0) { $response['success'] = true; $response['leistung_id'] = (int)$result; } else { $response['error'] = $stz->error ?: 'Fehler beim Hinzufuegen'; } break; // ---- Leistung aktualisieren ---- case 'update_leistung': $stzId = GETPOST('stz_id', 'int'); $leistungId = GETPOST('leistung_id', 'int'); $date = GETPOST('date', 'alpha'); $timeStart = GETPOST('time_start', 'alpha'); $timeEnd = GETPOST('time_end', 'alpha'); $description = GETPOST('description', 'restricthtml'); $fkProduct = GETPOST('fk_product', 'int'); $stz = new Stundenzettel($db); if ($stz->fetch($stzId) <= 0) { $response['error'] = 'Stundenzettel nicht gefunden'; break; } if (!canEditStz($stz, $user, $canWriteAll)) { $response['error'] = 'Keine Berechtigung oder nicht im Entwurf'; break; } $result = $stz->updateLeistung($leistungId, $date, $timeStart, $timeEnd, $description, $fkProduct > 0 ? $fkProduct : null); if ($result > 0) { $response['success'] = true; } else { $response['error'] = $stz->error ?: 'Fehler beim Aktualisieren'; } break; // ---- Leistung loeschen ---- case 'delete_leistung': $stzId = GETPOST('stz_id', 'int'); $leistungId = GETPOST('leistung_id', 'int'); $stz = new Stundenzettel($db); if ($stz->fetch($stzId) <= 0) { $response['error'] = 'Stundenzettel nicht gefunden'; break; } if (!canEditStz($stz, $user, $canWriteAll)) { $response['error'] = 'Keine Berechtigung oder nicht im Entwurf'; break; } $result = $stz->deleteLeistung($leistungId); if ($result > 0) { $response['success'] = true; } else { $response['error'] = 'Fehler beim Loeschen'; } break; // ---- Produktmenge aktualisieren ---- case 'update_qty': $stzId = GETPOST('stz_id', 'int'); $lineId = GETPOST('line_id', 'int'); $qtyDone = GETPOST('qty_done', 'alpha'); $stz = new Stundenzettel($db); if ($stz->fetch($stzId) <= 0) { $response['error'] = 'Stundenzettel nicht gefunden'; break; } if (!canEditStz($stz, $user, $canWriteAll)) { $response['error'] = 'Keine Berechtigung oder nicht im Entwurf'; break; } $result = $stz->updateProductQty($lineId, (float)$qtyDone); if ($result > 0) { $response['success'] = true; } else { $response['error'] = 'Fehler beim Aktualisieren'; } break; // ---- Produkt hinzufuegen ---- case 'add_product': $stzId = GETPOST('stz_id', 'int'); $fkProduct = GETPOST('fk_product', 'int'); $qty = (float)GETPOST('qty', 'alpha'); $description = GETPOST('description', 'restricthtml'); $stz = new Stundenzettel($db); if ($stz->fetch($stzId) <= 0) { $response['error'] = 'Stundenzettel nicht gefunden'; break; } if (!canEditStz($stz, $user, $canWriteAll)) { $response['error'] = 'Keine Berechtigung oder nicht im Entwurf'; break; } if ($fkProduct <= 0 && empty($description)) { $response['error'] = 'Produkt oder Beschreibung erforderlich'; break; } // Pruefen ob Produkt im Auftrag vorhanden (fk_commandedet finden) $fkCommandedet = 0; if ($fkProduct > 0 && $stz->fk_commande > 0) { $sqlCd = "SELECT cd.rowid FROM ".MAIN_DB_PREFIX."commandedet as cd"; $sqlCd .= " WHERE cd.fk_commande = ".((int)$stz->fk_commande); $sqlCd .= " AND cd.fk_product = ".((int)$fkProduct); $sqlCd .= " LIMIT 1"; $resCd = $db->query($sqlCd); if ($resCd && $db->num_rows($resCd) > 0) { $objCd = $db->fetch_object($resCd); $fkCommandedet = (int)$objCd->rowid; } } // Pruefen ob bereits eine Zeile mit gleichem Produkt existiert -> addieren $stz->fetchProducts(); $existingLine = null; foreach ($stz->products as $p) { if ($fkProduct > 0 && $p->fk_product == $fkProduct && in_array($p->origin, array('order', 'added'))) { $existingLine = $p; break; } } if ($existingLine) { // Menge addieren $newQty = (float)$existingLine->qty_done + $qty; $result = $stz->updateProductQty($existingLine->rowid, $newQty); } else { $result = $stz->addProduct( $fkProduct > 0 ? $fkProduct : 0, $fkCommandedet > 0 ? $fkCommandedet : null, null, 0, $qty, 'added', $description ); } if ($result > 0) { $response['success'] = true; } else { $response['error'] = $stz->error ?: 'Fehler beim Hinzufuegen'; } break; // ---- Produkt loeschen ---- case 'delete_product': $stzId = GETPOST('stz_id', 'int'); $lineId = GETPOST('line_id', 'int'); $stz = new Stundenzettel($db); if ($stz->fetch($stzId) <= 0) { $response['error'] = 'Stundenzettel nicht gefunden'; break; } if (!canEditStz($stz, $user, $canWriteAll)) { $response['error'] = 'Keine Berechtigung oder nicht im Entwurf'; break; } $result = $stz->deleteProduct($lineId); if ($result > 0) { $response['success'] = true; } else { $response['error'] = 'Fehler beim Loeschen'; } break; // ---- Mehraufwand hinzufuegen ---- case 'add_mehraufwand': $stzId = GETPOST('stz_id', 'int'); $fkProduct = GETPOST('fk_product', 'int'); $qty = (float)GETPOST('qty', 'alpha'); $description = GETPOST('description', 'restricthtml'); $reason = GETPOST('reason', 'restricthtml'); $stz = new Stundenzettel($db); if ($stz->fetch($stzId) <= 0) { $response['error'] = 'Stundenzettel nicht gefunden'; break; } if (!canEditStz($stz, $user, $canWriteAll)) { $response['error'] = 'Keine Berechtigung oder nicht im Entwurf'; break; } $desc = $reason ?: $description; $result = $stz->addProduct( $fkProduct > 0 ? $fkProduct : 0, null, null, 0, $qty, 'additional', $desc ); if ($result > 0) { $response['success'] = true; } else { $response['error'] = $stz->error ?: 'Fehler beim Hinzufuegen'; } break; // ---- Mehraufwand loeschen ---- case 'delete_mehraufwand': $stzId = GETPOST('stz_id', 'int'); $lineId = GETPOST('line_id', 'int'); $stz = new Stundenzettel($db); if ($stz->fetch($stzId) <= 0) { $response['error'] = 'Stundenzettel nicht gefunden'; break; } if (!canEditStz($stz, $user, $canWriteAll)) { $response['error'] = 'Keine Berechtigung oder nicht im Entwurf'; break; } $result = $stz->deleteProduct($lineId); if ($result > 0) { $response['success'] = true; } else { $response['error'] = 'Fehler beim Loeschen'; } break; // ---- Entfaellt hinzufuegen ---- case 'add_entfaellt': $stzId = GETPOST('stz_id', 'int'); $source = GETPOST('source', 'alphanohtml'); // commandedet_ID oder mehraufwand_SPROWID $qty = (float)GETPOST('qty', 'alpha'); $reason = GETPOST('reason', 'restricthtml'); $stz = new Stundenzettel($db); if ($stz->fetch($stzId) <= 0) { $response['error'] = 'Stundenzettel nicht gefunden'; break; } if (!canEditStz($stz, $user, $canWriteAll)) { $response['error'] = 'Keine Berechtigung oder nicht im Entwurf'; break; } if (empty($source)) { $response['error'] = 'Quelle erforderlich'; break; } // Source parsen: "cd_123" (Auftragszeile) oder "ma_456" (Mehraufwand) $parts = explode('_', $source); $sourceType = $parts[0]; $sourceId = isset($parts[1]) ? (int)$parts[1] : 0; if ($sourceType === 'cd' && $sourceId > 0) { // Auftragsposition $sqlCd = "SELECT cd.rowid, cd.fk_product, cd.description, p.ref, p.label"; $sqlCd .= " FROM ".MAIN_DB_PREFIX."commandedet as cd"; $sqlCd .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product"; $sqlCd .= " WHERE cd.rowid = ".((int)$sourceId); $resCd = $db->query($sqlCd); if ($resCd && $db->num_rows($resCd) > 0) { $objCd = $db->fetch_object($resCd); $result = $stz->addProduct( $objCd->fk_product > 0 ? (int)$objCd->fk_product : 0, (int)$objCd->rowid, null, 0, $qty, 'omitted', $reason ?: '', $objCd->label ?: strip_tags($objCd->description) ); if ($result > 0) { $response['success'] = true; } else { $response['error'] = $stz->error ?: 'Fehler'; } } else { $response['error'] = 'Auftragsposition nicht gefunden'; } } elseif ($sourceType === 'ma' && $sourceId > 0) { // Mehraufwand-Zeile $sqlMa = "SELECT sp.* FROM ".MAIN_DB_PREFIX."stundenzettel_product as sp"; $sqlMa .= " WHERE sp.rowid = ".((int)$sourceId); $sqlMa .= " AND sp.fk_stundenzettel = ".((int)$stz->id); $sqlMa .= " AND sp.origin = 'additional'"; $resMa = $db->query($sqlMa); if ($resMa && $db->num_rows($resMa) > 0) { $objMa = $db->fetch_object($resMa); // Mehraufwand-Menge reduzieren $newMaQty = (float)$objMa->qty_done - $qty; if ($newMaQty > 0) { $stz->updateProductQty($sourceId, $newMaQty); } else { $stz->deleteProduct($sourceId); } // Entfaellt-Zeile anlegen $result = $stz->addProduct( $objMa->fk_product > 0 ? (int)$objMa->fk_product : 0, $objMa->fk_commandedet > 0 ? (int)$objMa->fk_commandedet : null, null, 0, $qty, 'omitted', $reason ?: '', $objMa->product_label ); $response['success'] = ($result > 0); } else { $response['error'] = 'Mehraufwand nicht gefunden'; } } else { $response['error'] = 'Ungueltige Quelle'; } break; // ---- Entfaellt loeschen ---- case 'delete_entfaellt': $stzId = GETPOST('stz_id', 'int'); $lineId = GETPOST('line_id', 'int'); $stz = new Stundenzettel($db); if ($stz->fetch($stzId) <= 0) { $response['error'] = 'Stundenzettel nicht gefunden'; break; } if (!canEditStz($stz, $user, $canWriteAll)) { $response['error'] = 'Keine Berechtigung oder nicht im Entwurf'; break; } $result = $stz->deleteProduct($lineId); if ($result > 0) { $response['success'] = true; } else { $response['error'] = 'Fehler beim Loeschen'; } break; // ---- Ruecknahme hinzufuegen ---- case 'add_ruecknahme': $stzId = GETPOST('stz_id', 'int'); $source = GETPOST('source', 'alphanohtml'); $qty = (float)GETPOST('qty', 'alpha'); $reason = GETPOST('reason', 'restricthtml'); $stz = new Stundenzettel($db); if ($stz->fetch($stzId) <= 0) { $response['error'] = 'Stundenzettel nicht gefunden'; break; } if (!canEditStz($stz, $user, $canWriteAll)) { $response['error'] = 'Keine Berechtigung oder nicht im Entwurf'; break; } if (empty($source)) { $response['error'] = 'Quelle erforderlich'; break; } // Source: "cd_123" (Auftragszeile) oder "sp_456" (Stundenzettel-Produkt) $parts = explode('_', $source); $sourceType = $parts[0]; $sourceId = isset($parts[1]) ? (int)$parts[1] : 0; if ($sourceType === 'cd' && $sourceId > 0) { // Auftragsposition - Ruecknahme $sqlCd = "SELECT cd.rowid, cd.fk_product, cd.description, p.ref, p.label"; $sqlCd .= " FROM ".MAIN_DB_PREFIX."commandedet as cd"; $sqlCd .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product"; $sqlCd .= " WHERE cd.rowid = ".((int)$sourceId); $resCd = $db->query($sqlCd); if ($resCd && $db->num_rows($resCd) > 0) { $objCd = $db->fetch_object($resCd); $result = $stz->addProduct( $objCd->fk_product > 0 ? (int)$objCd->fk_product : 0, (int)$objCd->rowid, null, 0, $qty, 'returned', $reason ?: '', $objCd->label ?: strip_tags($objCd->description) ); if ($result > 0) { $response['success'] = true; } else { $response['error'] = $stz->error ?: 'Fehler'; } } else { $response['error'] = 'Auftragsposition nicht gefunden'; } } elseif ($sourceType === 'sp' && $sourceId > 0) { // Stundenzettel-Produkt (added ohne commandedet) $sqlSp = "SELECT sp.* FROM ".MAIN_DB_PREFIX."stundenzettel_product as sp"; $sqlSp .= " WHERE sp.rowid = ".((int)$sourceId); $resSp = $db->query($sqlSp); if ($resSp && $db->num_rows($resSp) > 0) { $objSp = $db->fetch_object($resSp); $result = $stz->addProduct( $objSp->fk_product > 0 ? (int)$objSp->fk_product : 0, $objSp->fk_commandedet > 0 ? (int)$objSp->fk_commandedet : null, null, 0, $qty, 'returned', $reason ?: '', $objSp->product_label ); if ($result > 0) { $response['success'] = true; } else { $response['error'] = $stz->error ?: 'Fehler'; } } else { $response['error'] = 'Produkt nicht gefunden'; } } else { $response['error'] = 'Ungueltige Quelle'; } break; // ---- Ruecknahme loeschen ---- case 'delete_ruecknahme': $stzId = GETPOST('stz_id', 'int'); $lineId = GETPOST('line_id', 'int'); $stz = new Stundenzettel($db); if ($stz->fetch($stzId) <= 0) { $response['error'] = 'Stundenzettel nicht gefunden'; break; } if (!canEditStz($stz, $user, $canWriteAll)) { $response['error'] = 'Keine Berechtigung oder nicht im Entwurf'; break; } $result = $stz->deleteProduct($lineId); if ($result > 0) { $response['success'] = true; } else { $response['error'] = 'Fehler beim Loeschen'; } break; // ---- Notiz hinzufuegen ---- case 'add_note': $stzId = GETPOST('stz_id', 'int'); $noteText = GETPOST('note', 'restricthtml'); $stz = new Stundenzettel($db); if ($stz->fetch($stzId) <= 0) { $response['error'] = 'Stundenzettel nicht gefunden'; break; } if (!canEditStz($stz, $user, $canWriteAll)) { $response['error'] = 'Keine Berechtigung oder nicht im Entwurf'; break; } $result = $stz->addNote($user, $noteText); if ($result > 0) { $response['success'] = true; } else { $response['error'] = 'Fehler beim Hinzufuegen'; } break; // ---- Notiz togglen ---- case 'toggle_note': $stzId = GETPOST('stz_id', 'int'); $noteId = GETPOST('note_id', 'int'); $checked = GETPOST('checked', 'int'); $stz = new Stundenzettel($db); if ($stz->fetch($stzId) <= 0) { $response['error'] = 'Stundenzettel nicht gefunden'; break; } $result = $stz->updateNoteStatus($noteId, $checked); if ($result > 0) { $response['success'] = true; } else { $response['error'] = 'Fehler beim Aktualisieren'; } break; // ---- Notiz loeschen ---- case 'delete_note': $stzId = GETPOST('stz_id', 'int'); $noteId = GETPOST('note_id', 'int'); $stz = new Stundenzettel($db); if ($stz->fetch($stzId) <= 0) { $response['error'] = 'Stundenzettel nicht gefunden'; break; } if (!canEditStz($stz, $user, $canWriteAll)) { $response['error'] = 'Keine Berechtigung oder nicht im Entwurf'; break; } $result = $stz->deleteNote($noteId); if ($result > 0) { $response['success'] = true; } else { $response['error'] = 'Fehler beim Loeschen'; } break; // ---- Produktsuche ---- case 'search_products': $term = GETPOST('term', 'alphanohtml'); if (strlen($term) < 2) { $response['error'] = 'Mindestens 2 Zeichen'; break; } $sql = "SELECT p.rowid as id, p.ref, p.label"; $sql .= " FROM ".MAIN_DB_PREFIX."product as p"; $sql .= " WHERE (p.ref LIKE '%".$db->escape($term)."%' OR p.label LIKE '%".$db->escape($term)."%')"; $sql .= " AND p.entity IN (".getEntity('product').")"; $sql .= " AND p.tosell = 1"; // Nur verkaufbare Produkte $sql .= " ORDER BY p.ref ASC"; $sql .= " LIMIT 20"; $resql = $db->query($sql); $products = array(); if ($resql) { while ($obj = $db->fetch_object($resql)) { $products[] = array( 'id' => (int)$obj->id, 'ref' => $obj->ref, 'label' => $obj->label ); } } $response['success'] = true; $response['products'] = $products; break; // ---- Entfaellt-Optionen laden ---- case 'get_entfaellt_options': $stzId = GETPOST('stz_id', 'int'); $orderId = GETPOST('order_id', 'int'); $options = array(); // Auftragspositionen mit verfuegbarer Menge $sql = "SELECT cd.rowid, cd.fk_product, cd.qty, cd.description,"; $sql .= " p.ref, p.label,"; $sql .= " COALESCE((SELECT SUM(sp.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp"; $sql .= " INNER JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = sp.fk_stundenzettel"; $sql .= " WHERE s.fk_commande = ".((int)$orderId); $sql .= " AND (sp.fk_commandedet = cd.rowid OR (sp.fk_product = cd.fk_product AND cd.fk_product > 0))"; $sql .= " AND sp.origin IN ('order','added')), 0) as qty_used,"; $sql .= " COALESCE((SELECT SUM(sp2.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp2"; $sql .= " INNER JOIN ".MAIN_DB_PREFIX."stundenzettel s2 ON s2.rowid = sp2.fk_stundenzettel"; $sql .= " WHERE s2.fk_commande = ".((int)$orderId); $sql .= " AND (sp2.fk_commandedet = cd.rowid OR (sp2.fk_product = cd.fk_product AND cd.fk_product > 0))"; $sql .= " AND sp2.origin = 'omitted'), 0) as qty_omitted"; $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd"; $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product"; $sql .= " WHERE cd.fk_commande = ".((int)$orderId); $sql .= " ORDER BY cd.rang"; $resql = $db->query($sql); if ($resql) { while ($obj = $db->fetch_object($resql)) { $available = (float)$obj->qty - (float)$obj->qty_used - (float)$obj->qty_omitted; if ($available > 0) { $label = $obj->label ?: strip_tags($obj->description); $options[] = array( 'value' => 'cd_'.$obj->rowid, 'label' => ($obj->ref ? $obj->ref.' - ' : '').$label, 'max_qty' => $available ); } } } // Mehraufwand-Zeilen if ($stzId > 0) { $sqlMa = "SELECT sp.rowid, sp.fk_product, sp.product_ref, sp.product_label, sp.description, sp.qty_done"; $sqlMa .= " FROM ".MAIN_DB_PREFIX."stundenzettel_product as sp"; $sqlMa .= " WHERE sp.fk_stundenzettel = ".((int)$stzId); $sqlMa .= " AND sp.origin = 'additional'"; $sqlMa .= " AND sp.qty_done > 0"; $resMa = $db->query($sqlMa); if ($resMa) { while ($obj = $db->fetch_object($resMa)) { $label = $obj->product_label ?: strip_tags($obj->description); $options[] = array( 'value' => 'ma_'.$obj->rowid, 'label' => '[Mehraufwand] '.($obj->product_ref ? $obj->product_ref.' - ' : '').$label, 'max_qty' => (float)$obj->qty_done ); } } } $response['success'] = true; $response['options'] = $options; break; // ---- Ruecknahme-Optionen laden ---- case 'get_ruecknahme_options': $stzId = GETPOST('stz_id', 'int'); $orderId = GETPOST('order_id', 'int'); $options = array(); // Verbaute Auftragspositionen $sql = "SELECT cd.rowid, cd.fk_product, cd.description,"; $sql .= " p.ref, p.label,"; $sql .= " COALESCE((SELECT SUM(sp.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp"; $sql .= " INNER JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = sp.fk_stundenzettel"; $sql .= " WHERE s.fk_commande = ".((int)$orderId); $sql .= " AND (sp.fk_commandedet = cd.rowid OR (sp.fk_product = cd.fk_product AND cd.fk_product > 0))"; $sql .= " AND sp.origin IN ('order','added')), 0) as qty_delivered,"; $sql .= " COALESCE((SELECT SUM(sp2.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp2"; $sql .= " INNER JOIN ".MAIN_DB_PREFIX."stundenzettel s2 ON s2.rowid = sp2.fk_stundenzettel"; $sql .= " WHERE s2.fk_commande = ".((int)$orderId); $sql .= " AND (sp2.fk_commandedet = cd.rowid OR (sp2.fk_product = cd.fk_product AND cd.fk_product > 0))"; $sql .= " AND sp2.origin = 'returned'), 0) as qty_returned"; $sql .= " FROM ".MAIN_DB_PREFIX."commandedet as cd"; $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product"; $sql .= " WHERE cd.fk_commande = ".((int)$orderId); $sql .= " ORDER BY cd.rang"; $resql = $db->query($sql); if ($resql) { while ($obj = $db->fetch_object($resql)) { $available = (float)$obj->qty_delivered - (float)$obj->qty_returned; if ($available > 0) { $label = $obj->label ?: strip_tags($obj->description); $options[] = array( 'value' => 'cd_'.$obj->rowid, 'label' => ($obj->ref ? $obj->ref.' - ' : '').$label, 'max_qty' => $available ); } } } // Verbaute Produkte ohne Auftragszeile (manuell hinzugefuegt) if ($stzId > 0) { $sqlSp = "SELECT sp.rowid, sp.fk_product, sp.product_ref, sp.product_label, sp.description, sp.qty_done"; $sqlSp .= " FROM ".MAIN_DB_PREFIX."stundenzettel_product as sp"; $sqlSp .= " WHERE sp.fk_stundenzettel = ".((int)$stzId); $sqlSp .= " AND sp.origin = 'added'"; $sqlSp .= " AND (sp.fk_commandedet IS NULL OR sp.fk_commandedet = 0)"; $sqlSp .= " AND sp.qty_done > 0"; $resSp = $db->query($sqlSp); if ($resSp) { while ($obj = $db->fetch_object($resSp)) { $label = $obj->product_label ?: strip_tags($obj->description); $options[] = array( 'value' => 'sp_'.$obj->rowid, 'label' => '[Hinzugefuegt] '.($obj->product_ref ? $obj->product_ref.' - ' : '').$label, 'max_qty' => (float)$obj->qty_done ); } } } $response['success'] = true; $response['options'] = $options; break; // ---- Dienstleistungen laden ---- case 'get_services': $sql = "SELECT p.rowid as id, p.ref, p.label"; $sql .= " FROM ".MAIN_DB_PREFIX."product as p"; $sql .= " WHERE p.fk_product_type = 1"; // Typ 1 = Dienstleistung $sql .= " AND p.tosell = 1"; $sql .= " AND p.entity IN (".getEntity('product').")"; $sql .= " ORDER BY p.ref ASC"; $sql .= " LIMIT 50"; $resql = $db->query($sql); $services = array(); if ($resql) { while ($obj = $db->fetch_object($resql)) { $services[] = array( 'id' => (int)$obj->id, 'ref' => $obj->ref, 'label' => $obj->label ); } } $response['success'] = true; $response['services'] = $services; break; // ---- Auftragspositionen in STZ uebernehmen ---- case 'transfer_order_products': if (!$canWrite) { $response['error'] = 'Keine Schreibberechtigung'; break; } $stzId = GETPOST('stz_id', 'int'); $lineIds = GETPOST('line_ids', 'alpha'); // Kommaseparierte commandedet IDs $stz = new Stundenzettel($db); if ($stz->fetch($stzId) <= 0) { $response['error'] = 'Stundenzettel nicht gefunden'; break; } if (!canEditStz($stz, $user, $canWriteAll)) { $response['error'] = 'Keine Berechtigung oder nicht im Entwurf'; break; } $ids = array_filter(array_map('intval', explode(',', $lineIds))); $added = 0; foreach ($ids as $lineId) { if ($lineId <= 0) continue; // Auftragszeile laden $sqlLine = "SELECT cd.rowid, cd.fk_product, cd.qty, cd.description"; $sqlLine .= " FROM ".MAIN_DB_PREFIX."commandedet as cd"; $sqlLine .= " WHERE cd.rowid = ".((int)$lineId); $resLine = $db->query($sqlLine); if (!$resLine || !($objLine = $db->fetch_object($resLine))) continue; // Pruefen ob schon auf diesem STZ $sqlCheck = "SELECT rowid FROM ".MAIN_DB_PREFIX."stundenzettel_product"; $sqlCheck .= " WHERE fk_stundenzettel = ".((int)$stzId); $sqlCheck .= " AND fk_commandedet = ".((int)$lineId); $sqlCheck .= " AND origin IN ('order', 'added')"; $resCheck = $db->query($sqlCheck); if ($resCheck && $db->num_rows($resCheck) > 0) continue; // Bereits vorhanden $stz->addProduct( $objLine->fk_product, $objLine->rowid, // fk_commandedet null, $objLine->qty, // qty_original 0, // qty_done 'order', $objLine->description ); $added++; } // Mehraufwand-Produkte uebernehmen (als origin='added' ohne fk_commandedet) $maProductsJson = GETPOST('ma_products', 'restricthtml'); if (!empty($maProductsJson)) { $maProducts = json_decode($maProductsJson, true); if (is_array($maProducts)) { foreach ($maProducts as $ma) { $fkProduct = isset($ma['fk_product']) ? (int)$ma['fk_product'] : 0; $description = isset($ma['description']) ? $ma['description'] : ''; $qty = isset($ma['qty']) ? (float)$ma['qty'] : 0; // Pruefen ob dieses Mehraufwand-Produkt schon auf dem STZ ist $sqlCheckMA = "SELECT rowid FROM ".MAIN_DB_PREFIX."stundenzettel_product"; $sqlCheckMA .= " WHERE fk_stundenzettel = ".((int)$stzId); $sqlCheckMA .= " AND origin = 'added'"; $sqlCheckMA .= " AND (fk_commandedet IS NULL OR fk_commandedet = 0)"; if ($fkProduct > 0) { $sqlCheckMA .= " AND fk_product = ".((int)$fkProduct); } else { $sqlCheckMA .= " AND (fk_product IS NULL OR fk_product = 0)"; $sqlCheckMA .= " AND description = '".$db->escape($description)."'"; } $resCheckMA = $db->query($sqlCheckMA); if ($resCheckMA && $db->num_rows($resCheckMA) > 0) continue; // Bereits vorhanden // Produkt-Label laden $productLabel = ''; if ($fkProduct > 0) { $sqlProd = "SELECT ref, label FROM ".MAIN_DB_PREFIX."product WHERE rowid = ".((int)$fkProduct); $resProd = $db->query($sqlProd); if ($resProd && ($objProd = $db->fetch_object($resProd))) { $productLabel = $objProd->label; } } $stz->addProduct( $fkProduct > 0 ? $fkProduct : null, null, // fk_commandedet (kein Auftragsbezug) null, $qty, // qty_original = Zielmenge 0, // qty_done 'added', $description ?: $productLabel ); $added++; } } } $response['success'] = true; $response['added'] = $added; break; default: $response['error'] = 'Unbekannte Aktion: '.$action; } echo json_encode($response); $db->close();