- Mengenanzeige klickbar für Dezimaleingabe (Komma), +/- bleiben Ganzzahl - Freigeben/Wiedereröffnen-Button für einzelne Stundenzettel - Warnung bei Freigabe ohne Leistung mit Service-Auswahl-Dialog (Standard-Dienstleistung des Kunden vorausgewählt) - API: validate_stz und setdraft_stz Endpunkte - API: default_service_id/label im get_order_context - Produktübernahme: qty_done Standard auf 1 statt 0 - Merkzettel auf Produktliste: nur Anzeige + Abhaken, kein Hinzufügen - Scroll-Position nach Panel-Neurendern zurücksetzen Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1640 lines
55 KiB
PHP
1640 lines
55 KiB
PHP
<?php
|
|
/* Copyright (C) 2026 Eduard Wisch <data@data-it-solution.de>
|
|
*
|
|
* 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');
|
|
dol_include_once('/stundenzettel/lib/stundenzettel.lib.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);
|
|
|
|
// Standard-Dienstleistung des Kunden ermitteln
|
|
$defaultServiceId = 0;
|
|
$defaultServiceLabel = '';
|
|
if (isset($customer->array_options['options_stundenzettel_default_service'])) {
|
|
$defaultServiceId = (int)$customer->array_options['options_stundenzettel_default_service'];
|
|
if ($defaultServiceId > 0) {
|
|
$sqlDS = "SELECT ref, label FROM ".MAIN_DB_PREFIX."product WHERE rowid = ".((int)$defaultServiceId);
|
|
$resDS = $db->query($sqlDS);
|
|
if ($resDS && ($objDS = $db->fetch_object($resDS))) {
|
|
$defaultServiceLabel = $objDS->ref.' - '.$objDS->label;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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,
|
|
'default_service_id' => $defaultServiceId,
|
|
'default_service_label' => $defaultServiceLabel
|
|
);
|
|
|
|
// 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
|
|
1, // qty_done (Standard: 1)
|
|
'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
|
|
1, // qty_done (Standard: 1)
|
|
'added',
|
|
$description ?: $productLabel
|
|
);
|
|
$added++;
|
|
}
|
|
}
|
|
}
|
|
|
|
$response['success'] = true;
|
|
$response['added'] = $added;
|
|
break;
|
|
|
|
// ---- Stundenzettel freigeben (validieren) ----
|
|
case 'validate_stz':
|
|
if (!$canWrite) {
|
|
$response['error'] = 'Keine Schreibberechtigung';
|
|
break;
|
|
}
|
|
|
|
$stzId = GETPOST('stz_id', 'int');
|
|
$stz = new Stundenzettel($db);
|
|
if ($stz->fetch($stzId) <= 0) {
|
|
$response['error'] = 'Stundenzettel nicht gefunden';
|
|
break;
|
|
}
|
|
if ($stz->status != Stundenzettel::STATUS_DRAFT) {
|
|
$response['error'] = 'Stundenzettel ist nicht im Entwurf';
|
|
break;
|
|
}
|
|
if (!canEditStz($stz, $user, $canWriteAll)) {
|
|
$response['error'] = 'Keine Berechtigung';
|
|
break;
|
|
}
|
|
|
|
$result = $stz->validate($user);
|
|
if ($result > 0) {
|
|
// Netto-Wert aller Stundenzettel des Auftrags neu berechnen
|
|
updateOrderNettoSTZ($db, $stz->fk_commande);
|
|
$response['success'] = true;
|
|
} else {
|
|
$response['error'] = $stz->error ?: 'Fehler beim Freigeben';
|
|
}
|
|
break;
|
|
|
|
// ---- Stundenzettel zurueck auf Entwurf setzen ----
|
|
case 'setdraft_stz':
|
|
if (!$canWrite) {
|
|
$response['error'] = 'Keine Schreibberechtigung';
|
|
break;
|
|
}
|
|
|
|
$stzId = GETPOST('stz_id', 'int');
|
|
$stz = new Stundenzettel($db);
|
|
if ($stz->fetch($stzId) <= 0) {
|
|
$response['error'] = 'Stundenzettel nicht gefunden';
|
|
break;
|
|
}
|
|
if ($stz->status == Stundenzettel::STATUS_DRAFT) {
|
|
$response['error'] = 'Stundenzettel ist bereits im Entwurf';
|
|
break;
|
|
}
|
|
|
|
$result = $stz->setDraft($user);
|
|
if ($result > 0) {
|
|
updateOrderNettoSTZ($db, $stz->fk_commande);
|
|
$response['success'] = true;
|
|
} else {
|
|
$response['error'] = $stz->error ?: 'Fehler beim Zuruecksetzen';
|
|
}
|
|
break;
|
|
|
|
default:
|
|
$response['error'] = 'Unbekannte Aktion: '.$action;
|
|
}
|
|
|
|
echo json_encode($response);
|
|
$db->close();
|