diff --git a/admin/setup.php b/admin/setup.php
index c8b0ccd..1129faa 100644
--- a/admin/setup.php
+++ b/admin/setup.php
@@ -71,6 +71,18 @@ if ($action == 'setINVOICE_HOURS_MODE') {
exit;
}
+if ($action == 'setDEFAULT_SECTIONS') {
+ $sections = GETPOST('default_sections', 'array');
+ $value = implode(',', $sections);
+ if (dolibarr_set_const($db, 'STUNDENZETTEL_DEFAULT_SECTIONS', $value, 'chaine', 0, '', $conf->entity) > 0) {
+ setEventMessages($langs->trans("SetupSaved"), null, 'mesgs');
+ } else {
+ setEventMessages($langs->trans("Error"), null, 'errors');
+ }
+ header("Location: ".$_SERVER["PHP_SELF"]);
+ exit;
+}
+
/*
* View
*/
@@ -176,6 +188,28 @@ print '';
print '';
print '';
+// Standard-Bereiche anzeigen
+$currentSections = getDolGlobalString('STUNDENZETTEL_DEFAULT_SECTIONS', '');
+$selectedSections = !empty($currentSections) ? explode(',', $currentSections) : array();
+print '
';
+print ''.$langs->trans("DefaultSections").' '.$langs->trans("DefaultSectionsDesc").' | ';
+print '';
+print ' | ';
+print '';
+print '';
+print '';
+print ' | ';
+print '
';
+
print '';
print dol_get_fiche_end();
diff --git a/card.php b/card.php
old mode 100755
new mode 100644
index af26f4a..369c2a5
--- a/card.php
+++ b/card.php
@@ -78,12 +78,27 @@ if ($id > 0 || !empty($ref)) {
}
}
-// Permissions
+// Permissions - Basis-Berechtigungen
$permissiontoread = $user->hasRight('stundenzettel', 'read');
-$permissiontoadd = $user->hasRight('stundenzettel', 'write');
+$permissiontoreadall = $user->hasRight('stundenzettel', 'read', 'all') || $user->admin;
+$permissiontowrite = $user->hasRight('stundenzettel', 'write');
+$permissiontowriteall = $user->hasRight('stundenzettel', 'write', 'all') || $user->admin;
$permissiontodelete = $user->hasRight('stundenzettel', 'delete');
+$permissiontodeleteall = $user->hasRight('stundenzettel', 'delete', 'all') || $user->admin;
$permissiontovalidate = $user->hasRight('stundenzettel', 'validate');
+// Prüfen ob der aktuelle Stundenzettel dem Benutzer gehört
+$isOwner = ($object->id > 0 && $object->fk_user_author == $user->id);
+
+// Effektive Berechtigungen für diesen Stundenzettel
+$permissiontoadd = $permissiontowrite && ($isOwner || $permissiontowriteall || $action == 'create');
+$permissiontodeleteobj = $permissiontodelete && ($isOwner || $permissiontodeleteall);
+
+// Zugriffskontrolle: Wenn geladen und nicht berechtigt -> Zugriff verweigern
+if ($object->id > 0 && !$isOwner && !$permissiontoreadall) {
+ accessforbidden('Sie haben keine Berechtigung, diesen Stundenzettel anzuzeigen.');
+}
+
/*
* Actions
*/
@@ -207,7 +222,7 @@ if ($action == 'confirm_setdraft' && $confirm == 'yes' && $permissiontoadd) {
}
// Delete
-if ($action == 'confirm_delete' && $confirm == 'yes' && $permissiontodelete) {
+if ($action == 'confirm_delete' && $confirm == 'yes' && $permissiontodeleteobj) {
$fk_commande = $object->fk_commande; // Speichern vor dem Löschen
$result = $object->delete($user);
if ($result > 0) {
@@ -605,6 +620,121 @@ if ($action == 'confirm_delete_entfaellt' && $confirm == 'yes' && $permissiontoa
exit;
}
+// Rücknahme hinzufügen (bereits verbautes Produkt wird zurückgenommen)
+if ($action == 'add_ruecknahme' && $permissiontoadd) {
+ $ruecknahme_product_raw = GETPOST('ruecknahme_product', 'alpha');
+ $qty = (float)price2num(GETPOST('ruecknahme_qty', 'alpha'));
+ $reason = GETPOST('ruecknahme_description', 'restricthtml');
+
+ // Prüfen ob es ein Freitext-Produkt oder normales Produkt ist
+ $fk_product = 0;
+ $freetext_description = '';
+ $commandedet_id = 0;
+
+ if (strpos($ruecknahme_product_raw, 'freetext_') === 0) {
+ // Freitext-Produkt aus dem Auftrag
+ $commandedet_id = (int)substr($ruecknahme_product_raw, 9);
+ // Beschreibung aus commandedet laden
+ $sqlDesc = "SELECT description FROM ".MAIN_DB_PREFIX."commandedet WHERE rowid = ".((int)$commandedet_id);
+ $resqlDesc = $db->query($sqlDesc);
+ if ($resqlDesc && ($objDesc = $db->fetch_object($resqlDesc))) {
+ $freetext_description = $objDesc->description;
+ }
+ } else {
+ $fk_product = (int)$ruecknahme_product_raw;
+ }
+
+ // Beschreibung: Freitext-Beschreibung + Grund
+ $description = $reason;
+ if (!empty($freetext_description)) {
+ $description = strip_tags($freetext_description) . (!empty($reason) ? ' - ' . $reason : '');
+ }
+
+ $error = 0;
+
+ if ($fk_product > 0 || !empty($freetext_description)) {
+ // Server-seitige Validierung: Prüfen ob Menge bereits verbaut wurde
+ if ($object->fk_commande > 0) {
+ if ($fk_product > 0) {
+ // Produkt-Validierung: Prüfe bereits verbaute Menge
+ $sqlCheck = "SELECT COALESCE(SUM(sp.qty_done), 0) as qty_delivered,";
+ $sqlCheck .= " COALESCE((SELECT SUM(sp2.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp2";
+ $sqlCheck .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s2 ON s2.rowid = sp2.fk_stundenzettel";
+ $sqlCheck .= " WHERE sp2.fk_product = ".((int)$fk_product)." AND sp2.origin = 'returned' AND s2.fk_commande = ".((int)$object->fk_commande)."), 0) as qty_already_returned";
+ $sqlCheck .= " FROM ".MAIN_DB_PREFIX."stundenzettel_product sp";
+ $sqlCheck .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = sp.fk_stundenzettel";
+ $sqlCheck .= " WHERE sp.fk_product = ".((int)$fk_product);
+ $sqlCheck .= " AND sp.origin IN ('order', 'added')";
+ $sqlCheck .= " AND s.fk_commande = ".((int)$object->fk_commande);
+
+ $resqlCheck = $db->query($sqlCheck);
+ if ($resqlCheck && ($objCheck = $db->fetch_object($resqlCheck))) {
+ $qty_available = $objCheck->qty_delivered - $objCheck->qty_already_returned;
+ if ($qty > $qty_available) {
+ setEventMessages($langs->trans('ErrorQtyExceedsAvailable', $qty_available), null, 'errors');
+ $error++;
+ }
+ }
+ } elseif (!empty($commandedet_id)) {
+ // Freitext-Produkt Validierung
+ $sqlCheck = "SELECT COALESCE(SUM(sp.qty_done), 0) as qty_delivered,";
+ $sqlCheck .= " COALESCE((SELECT SUM(sp2.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp2";
+ $sqlCheck .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s2 ON s2.rowid = sp2.fk_stundenzettel";
+ $sqlCheck .= " WHERE sp2.fk_commandedet = ".((int)$commandedet_id)." AND sp2.origin = 'returned' AND s2.fk_commande = ".((int)$object->fk_commande)."), 0) as qty_already_returned";
+ $sqlCheck .= " FROM ".MAIN_DB_PREFIX."stundenzettel_product sp";
+ $sqlCheck .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = sp.fk_stundenzettel";
+ $sqlCheck .= " WHERE sp.fk_commandedet = ".((int)$commandedet_id);
+ $sqlCheck .= " AND sp.origin IN ('order', 'added')";
+ $sqlCheck .= " AND s.fk_commande = ".((int)$object->fk_commande);
+
+ $resqlCheck = $db->query($sqlCheck);
+ if ($resqlCheck && ($objCheck = $db->fetch_object($resqlCheck))) {
+ $qty_available = $objCheck->qty_delivered - $objCheck->qty_already_returned;
+ if ($qty > $qty_available) {
+ setEventMessages($langs->trans('ErrorQtyExceedsAvailable', $qty_available), null, 'errors');
+ $error++;
+ }
+ }
+ }
+ }
+
+ if (!$error) {
+ // Produkt zum Stundenzettel hinzufügen mit origin='returned'
+ $result = $object->addProduct(
+ $fk_product,
+ $commandedet_id, // fk_commandedet
+ 0, // fk_manager_line
+ 0, // qty_original
+ $qty, // qty_done (Menge die zurückgenommen wird)
+ 'returned', // origin (rücknahme)
+ $description // description (Grund)
+ );
+ if ($result > 0) {
+ setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
+ } else {
+ setEventMessages($object->error, null, 'errors');
+ }
+ }
+ } else {
+ setEventMessages($langs->trans('ErrorNoProductSelected'), null, 'errors');
+ }
+ header('Location: '.$_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=products');
+ exit;
+}
+
+// Rücknahme-Produkt löschen
+if ($action == 'confirm_delete_ruecknahme' && $confirm == 'yes' && $permissiontoadd) {
+ $line_id = GETPOST('line_id', 'int');
+ $result = $object->deleteProduct($line_id);
+ if ($result > 0) {
+ setEventMessages($langs->trans('RecordDeleted'), null, 'mesgs');
+ } else {
+ setEventMessages($object->error, null, 'errors');
+ }
+ header('Location: '.$_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=products');
+ exit;
+}
+
// Mehraufwand hinzufügen (zusätzliches Produkt nicht aus Auftrag)
if ($action == 'add_mehraufwand' && $permissiontoadd) {
$fk_product = GETPOST('mehraufwand_product', 'int');
@@ -1346,7 +1476,31 @@ elseif ($object->id > 0) {
print $form->formconfirm($_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=products&line_id='.$line_id, $langs->trans('Delete'), $langs->trans('ConfirmDeleteEntfaellt'), 'confirm_delete_entfaellt', '', 0, 1);
}
- print '';
+ // Zuerst bereits erfasste Entfällt-Produkte zählen
+ $entfaelltProducts = array();
+ foreach ($object->products as $prod) {
+ if ($prod->origin == 'omitted') {
+ $entfaelltProducts[] = $prod;
+ }
+ }
+ $hasEntfaelltProducts = (count($entfaelltProducts) > 0);
+
+ // Bereich nur anzeigen wenn Einträge vorhanden ODER Checkbox aktiviert ODER Admin-Standard
+ $defaultSections = explode(',', getDolGlobalString('STUNDENZETTEL_DEFAULT_SECTIONS', ''));
+ $showEntfaellt = $hasEntfaelltProducts || GETPOST('show_entfaellt', 'int') || in_array('entfaellt', $defaultSections);
+
+ // Checkbox zum Ein-/Ausblenden
+ print '
';
+ print '';
+ print '
';
+
+ print '
';
print '
';
print '| '.$langs->trans("Entfaellt").' - '.$langs->trans("EntfaelltDesc").' |
';
print '';
@@ -1357,15 +1511,7 @@ elseif ($object->id > 0) {
print ' | '; // Delete
print '
';
- // Zuerst bereits erfasste Entfällt-Produkte anzeigen
- $entfaelltProducts = array();
- foreach ($object->products as $prod) {
- if ($prod->origin == 'omitted') {
- $entfaelltProducts[] = $prod;
- }
- }
-
- if (count($entfaelltProducts) > 0) {
+ if ($hasEntfaelltProducts) {
foreach ($entfaelltProducts as $prod) {
print '';
@@ -1580,20 +1726,26 @@ elseif ($object->id > 0) {
print '';
print '
';
- // JavaScript für dynamische Max-Menge
+ // JavaScript für dynamische Max-Menge und Bereichs-Toggle
print '';
print '
';
@@ -1610,7 +1762,31 @@ elseif ($object->id > 0) {
print $form->formconfirm($_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=products&line_id='.$line_id, $langs->trans('Delete'), $langs->trans('ConfirmDeleteMehraufwand'), 'confirm_delete_mehraufwand', '', 0, 1);
}
- print '
';
+ // Zuerst bereits erfasste Mehraufwand-Produkte zählen
+ $mehraufwandProducts = array();
+ foreach ($object->products as $prod) {
+ if ($prod->origin == 'additional') {
+ $mehraufwandProducts[] = $prod;
+ }
+ }
+ $hasMehraufwandProducts = (count($mehraufwandProducts) > 0);
+
+ // Bereich nur anzeigen wenn Einträge vorhanden ODER Checkbox aktiviert ODER Admin-Standard
+ $defaultSectionsMehr = explode(',', getDolGlobalString('STUNDENZETTEL_DEFAULT_SECTIONS', ''));
+ $showMehraufwand = $hasMehraufwandProducts || GETPOST('show_mehraufwand', 'int') || in_array('mehraufwand', $defaultSectionsMehr);
+
+ // Checkbox zum Ein-/Ausblenden
+ print '
';
+ print '';
+ print '
';
+
+ print '
';
print '
';
print '| '.$langs->trans("Mehraufwand").' - '.$langs->trans("MehraufwandDesc").' |
';
print '';
@@ -1621,15 +1797,7 @@ elseif ($object->id > 0) {
print ' | '; // Delete
print '
';
- // Zuerst bereits erfasste Mehraufwand-Produkte anzeigen
- $mehraufwandProducts = array();
- foreach ($object->products as $prod) {
- if ($prod->origin == 'additional') {
- $mehraufwandProducts[] = $prod;
- }
- }
-
- if (count($mehraufwandProducts) > 0) {
+ if ($hasMehraufwandProducts) {
foreach ($mehraufwandProducts as $prod) {
$qty = (float)$prod->qty_done;
@@ -1759,6 +1927,240 @@ elseif ($object->id > 0) {
print '';
}
+ // =============================================
+ // BEREICH: RÜCKNAHME (bereits verbaute Produkte zurücknehmen)
+ // =============================================
+ if ($object->status == Stundenzettel::STATUS_DRAFT && $permissiontoadd) {
+ // Bestätigung für Löschen
+ if ($action == 'delete_ruecknahme') {
+ $line_id = GETPOST('line_id', 'int');
+ print $form->formconfirm($_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=products&line_id='.$line_id, $langs->trans('Delete'), $langs->trans('ConfirmDeleteRuecknahme'), 'confirm_delete_ruecknahme', '', 0, 1);
+ }
+
+ // Zähle bereits erfasste Rücknahme-Produkte
+ $ruecknahmeProducts = array();
+ foreach ($object->products as $prod) {
+ if ($prod->origin == 'returned') {
+ $ruecknahmeProducts[] = $prod;
+ }
+ }
+ $hasRuecknahmeProducts = (count($ruecknahmeProducts) > 0);
+
+ // Bereich nur anzeigen wenn Einträge vorhanden ODER Checkbox aktiviert ODER Admin-Standard
+ $defaultSectionsRueck = explode(',', getDolGlobalString('STUNDENZETTEL_DEFAULT_SECTIONS', ''));
+ $showRuecknahme = $hasRuecknahmeProducts || GETPOST('show_ruecknahme', 'int') || in_array('ruecknahme', $defaultSectionsRueck);
+
+ // Checkbox zum Ein-/Ausblenden
+ print '';
+ print '';
+ print '
';
+
+ print '';
+ print '
';
+ print '| '.$langs->trans("Ruecknahme").' - '.$langs->trans("RuecknahmeDesc").' |
';
+ print '';
+ print '| '.$langs->trans("Product").' | ';
+ print ''.$langs->trans("Qty").' | ';
+ print ''.$langs->trans("Reason").' | ';
+ print ' | '; // Save
+ print ' | '; // Delete
+ print '
';
+
+ // Bereits erfasste Rücknahme-Produkte anzeigen
+ if ($hasRuecknahmeProducts) {
+ foreach ($ruecknahmeProducts as $prod) {
+ print '';
+
+ // Produkt
+ print '| ';
+ if ($prod->fk_product > 0) {
+ print '';
+ print img_picto('', 'product', 'class="pictofixedwidth"');
+ print $prod->product_ref.' - '.$prod->product_label;
+ print '';
+ } else {
+ print img_picto('', 'generic', 'class="pictofixedwidth"');
+ $displayText = !empty($prod->description) ? strip_tags($prod->description) : $langs->trans("FreeText");
+ if (strlen($displayText) > 80) {
+ $displayText = substr($displayText, 0, 77).'...';
+ }
+ print ''.$displayText.'';
+ }
+ print ' '.$langs->trans("Ruecknahme").'';
+ // Mobile: Grund unter Produktname anzeigen
+ if (!empty($prod->description)) {
+ print ' '.dol_trunc(strip_tags($prod->description), 50).' ';
+ }
+ print ' | ';
+
+ // Menge
+ print '';
+ print '';
+ print ' | ';
+
+ // Grund/Beschreibung (auf Mobile ausgeblendet)
+ print '';
+ if (!empty($prod->description)) {
+ print dol_htmlentitiesbr($prod->description);
+ } else {
+ print '-';
+ }
+ print ' | ';
+
+ // Save
+ print '';
+ print '';
+ print '';
+ print '';
+ print ' | ';
+
+ // Delete
+ print '';
+ print '';
+ print '';
+ print '';
+ print ' | ';
+
+ print '
';
+ }
+ }
+
+ // Formular zum Hinzufügen - nur wenn Produkte verbaut wurden
+ // Produkte laden die bereits verbaut wurden (aus allen Stundenzetteln dieses Auftrags)
+ $deliveredProducts = array();
+ if ($object->fk_commande > 0) {
+ $sqlDelivered = "SELECT cd.rowid, cd.fk_product, cd.description,";
+ $sqlDelivered .= " p.ref as product_ref, p.label as product_label,";
+ // Bereits verbaut (auf allen Stundenzetteln dieses Auftrags)
+ $sqlDelivered .= " COALESCE((SELECT SUM(sp.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp";
+ $sqlDelivered .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s ON s.rowid = sp.fk_stundenzettel";
+ $sqlDelivered .= " WHERE (sp.fk_commandedet = cd.rowid OR (sp.fk_product = cd.fk_product AND cd.fk_product > 0))";
+ $sqlDelivered .= " AND sp.origin IN ('order', 'added') AND s.fk_commande = ".((int)$object->fk_commande)."), 0) as qty_delivered,";
+ // Bereits zurückgenommen
+ $sqlDelivered .= " COALESCE((SELECT SUM(sp2.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp2";
+ $sqlDelivered .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s2 ON s2.rowid = sp2.fk_stundenzettel";
+ $sqlDelivered .= " WHERE (sp2.fk_commandedet = cd.rowid OR (sp2.fk_product = cd.fk_product AND cd.fk_product > 0))";
+ $sqlDelivered .= " AND sp2.origin = 'returned' AND s2.fk_commande = ".((int)$object->fk_commande)."), 0) as qty_returned";
+ $sqlDelivered .= " FROM ".MAIN_DB_PREFIX."commandedet as cd";
+ $sqlDelivered .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
+ $sqlDelivered .= " WHERE cd.fk_commande = ".((int)$object->fk_commande);
+ $sqlDelivered .= " AND (cd.fk_product > 0 OR (cd.fk_product = 0 AND cd.description IS NOT NULL AND cd.description != ''))";
+ $sqlDelivered .= " AND (cd.special_code IS NULL OR cd.special_code = 0)";
+ $sqlDelivered .= " ORDER BY cd.rang";
+ $resqlDelivered = $db->query($sqlDelivered);
+ if ($resqlDelivered) {
+ while ($objProd = $db->fetch_object($resqlDelivered)) {
+ // Verfügbare Menge = verbaut - bereits zurückgenommen
+ $objProd->qty_available = $objProd->qty_delivered - $objProd->qty_returned;
+ if ($objProd->qty_available > 0) {
+ $deliveredProducts[] = $objProd;
+ }
+ }
+ }
+ }
+
+ $hasDeliveredProducts = (count($deliveredProducts) > 0);
+
+ print '';
+ print '| '.$langs->trans("AddRuecknahme").' | ';
+ print '
';
+
+ print '';
+ print '';
+ print '
';
+
+ // Mobile: Grund in separater Zeile
+ print '';
+ print '| ';
+ print '';
+ print ' | ';
+ print '
';
+
+ // JavaScript für dynamische Max-Menge
+ print '';
+
+ print '
';
+ print '
';
+ }
+
// =============================================
// BEREICH: MERKZETTEL (abhakbare Notizen)
// =============================================
@@ -1768,7 +2170,25 @@ elseif ($object->id > 0) {
print $form->formconfirm($_SERVER['PHP_SELF'].'?id='.$object->id.'¬e_id='.$note_id, $langs->trans('Delete'), $langs->trans('ConfirmDeleteNote'), 'confirm_delete_note', '', 0, 1);
}
- print '';
+ // Prüfen ob Notizen vorhanden
+ $hasNotes = (count($object->notes) > 0);
+
+ // Bereich nur anzeigen wenn Einträge vorhanden ODER Checkbox aktiviert ODER Admin-Standard
+ $defaultSectionsMerk = explode(',', getDolGlobalString('STUNDENZETTEL_DEFAULT_SECTIONS', ''));
+ $showMerkzettel = $hasNotes || GETPOST('show_merkzettel', 'int') || in_array('merkzettel', $defaultSectionsMerk);
+
+ // Checkbox zum Ein-/Ausblenden
+ print '
';
+ print '';
+ print '
';
+
+ print '
';
print '
';
print '| '.$langs->trans("NotesMemo").' - '.$langs->trans("NotesForNextVisit").' |
';
@@ -1938,7 +2358,7 @@ elseif ($object->id > 0) {
if ($permissiontovalidate) {
print ''.$langs->trans("Validate").'';
}
- if ($permissiontodelete) {
+ if ($permissiontodeleteobj) {
print ''.$langs->trans("Delete").'';
}
}
diff --git a/core/modules/modStundenzettel.class.php b/core/modules/modStundenzettel.class.php
old mode 100755
new mode 100644
index c99c28d..99803ec
--- a/core/modules/modStundenzettel.class.php
+++ b/core/modules/modStundenzettel.class.php
@@ -53,7 +53,7 @@ class modStundenzettel extends DolibarrModules
$this->descriptionlong = "Verwaltet Stundenzettel für Kundenaufträge. Ermöglicht die Dokumentation von Arbeitszeiten, verbrauchten Materialien und Notizen. Integration mit SubtotalTitle für Produktgruppen-Unterstützung.";
// Version
- $this->version = '1.4.0';
+ $this->version = '1.5.0';
// Autor
$this->editor_name = 'Data IT Solution';
@@ -133,22 +133,38 @@ class modStundenzettel extends DolibarrModules
$r = 0;
- // Lesen
+ // Lesen (eigene)
$this->rights[$r][0] = $this->numero + $r;
- $this->rights[$r][1] = 'Stundenzettel lesen';
- $this->rights[$r][3] = 0;
+ $this->rights[$r][1] = 'Eigene Stundenzettel lesen';
+ $this->rights[$r][3] = 1; // Standard aktiviert
$this->rights[$r][4] = 'read';
$this->rights[$r][5] = '';
$r++;
+ // Lesen (alle)
+ $this->rights[$r][0] = $this->numero + $r;
+ $this->rights[$r][1] = 'Alle Stundenzettel lesen';
+ $this->rights[$r][3] = 0;
+ $this->rights[$r][4] = 'read';
+ $this->rights[$r][5] = 'all';
+ $r++;
+
// Erstellen
$this->rights[$r][0] = $this->numero + $r;
$this->rights[$r][1] = 'Stundenzettel erstellen';
- $this->rights[$r][3] = 0;
+ $this->rights[$r][3] = 1; // Standard aktiviert
$this->rights[$r][4] = 'write';
$this->rights[$r][5] = '';
$r++;
+ // Bearbeiten (alle)
+ $this->rights[$r][0] = $this->numero + $r;
+ $this->rights[$r][1] = 'Alle Stundenzettel bearbeiten';
+ $this->rights[$r][3] = 0;
+ $this->rights[$r][4] = 'write';
+ $this->rights[$r][5] = 'all';
+ $r++;
+
// Freigeben
$this->rights[$r][0] = $this->numero + $r;
$this->rights[$r][1] = 'Stundenzettel freigeben';
@@ -157,14 +173,22 @@ class modStundenzettel extends DolibarrModules
$this->rights[$r][5] = '';
$r++;
- // Löschen
+ // Löschen (eigene)
$this->rights[$r][0] = $this->numero + $r;
- $this->rights[$r][1] = 'Stundenzettel löschen';
+ $this->rights[$r][1] = 'Eigene Stundenzettel löschen';
$this->rights[$r][3] = 0;
$this->rights[$r][4] = 'delete';
$this->rights[$r][5] = '';
$r++;
+ // Löschen (alle)
+ $this->rights[$r][0] = $this->numero + $r;
+ $this->rights[$r][1] = 'Alle Stundenzettel löschen';
+ $this->rights[$r][3] = 0;
+ $this->rights[$r][4] = 'delete';
+ $this->rights[$r][5] = 'all';
+ $r++;
+
// Hauptmenü
$this->menu = array();
$r = 0;
diff --git a/langs/de_DE/stundenzettel.lang b/langs/de_DE/stundenzettel.lang
old mode 100755
new mode 100644
index eb1b72e..11df85d
--- a/langs/de_DE/stundenzettel.lang
+++ b/langs/de_DE/stundenzettel.lang
@@ -155,6 +155,23 @@ Reason = Grund
Optional = optional
ConfirmDeleteEntfaellt = Diesen Eintrag wirklich löschen?
+# Rücknahme
+Ruecknahme = Rücknahme
+RuecknahmeDesc = Hier können Sie bereits verbaute Produkte erfassen, die wieder zurückgenommen wurden.
+AddRuecknahme = Rücknahme hinzufügen
+ConfirmDeleteRuecknahme = Diese Rücknahme wirklich löschen?
+QtyReturned = Zurückgenommen
+ReturnedProducts = Zurückgenommene Produkte
+NoProductsDelivered = Noch keine Produkte verbaut
+
+# Bereichs-Sichtbarkeit
+ShowSection = einblenden
+HideSection = ausblenden
+
+# Admin-Einstellungen Bereiche
+DefaultSections = Standard-Bereiche anzeigen
+DefaultSectionsDesc = Welche Bereiche sollen standardmäßig eingeblendet sein (auch wenn leer)?
+
# Fehler
ErrorNoOrder = Kein Auftrag ausgewählt
ErrorOrderNotFound = Auftrag nicht gefunden
@@ -169,10 +186,13 @@ BoxRecentStundenzettel = Zuletzt bearbeitete Stundenzettel
BoxOpenStundenzettel = Offene Stundenzettel
# Berechtigungen
-PermissionRead = Stundenzettel lesen
-PermissionWrite = Stundenzettel erstellen/bearbeiten
+PermissionRead = Eigene Stundenzettel lesen
+PermissionReadAll = Alle Stundenzettel lesen
+PermissionWrite = Stundenzettel erstellen
+PermissionWriteAll = Alle Stundenzettel bearbeiten
PermissionValidate = Stundenzettel freigeben
-PermissionDelete = Stundenzettel löschen
+PermissionDelete = Eigene Stundenzettel löschen
+PermissionDeleteAll = Alle Stundenzettel löschen
# Einstellungen
StundenzettelSetup = Stundenzettel Einstellungen
diff --git a/list.php b/list.php
old mode 100755
new mode 100644
index f51e1e5..a19d2e5
--- a/list.php
+++ b/list.php
@@ -22,6 +22,11 @@ if (!$user->hasRight('stundenzettel', 'read')) {
accessforbidden();
}
+// Berechtigungen prüfen
+$permissiontoreadall = $user->hasRight('stundenzettel', 'read', 'all') || $user->admin;
+$permissiontowriteall = $user->hasRight('stundenzettel', 'write', 'all') || $user->admin;
+$permissiontodeleteall = $user->hasRight('stundenzettel', 'delete', 'all') || $user->admin;
+
// Get parameters
$action = GETPOST('action', 'aZ09');
$massaction = GETPOST('massaction', 'alpha');
@@ -72,6 +77,11 @@ if ($search_author > 0) {
$sql .= " AND s.fk_user_author = ".((int)$search_author);
}
+// Einschränkung auf eigene Stundenzettel, wenn keine Berechtigung für alle
+if (!$permissiontoreadall) {
+ $sql .= " AND s.fk_user_author = ".((int)$user->id);
+}
+
$sql .= $db->order($sortfield, $sortorder);
// Count total
diff --git a/stundenzettel_commande.php b/stundenzettel_commande.php
old mode 100755
new mode 100644
index e62a1c7..0148985
--- a/stundenzettel_commande.php
+++ b/stundenzettel_commande.php
@@ -1606,7 +1606,7 @@ if ($tab == 'products') {
}
// Dann alle Produkte laden mit Section-Zuordnung
- // Berechne qty_delivered, qty_added (Mehraufwand) und qty_removed (Entfällt) direkt aus Stundenzetteln
+ // Berechne qty_delivered, qty_added (Mehraufwand), qty_removed (Entfällt) und qty_returned (Rücknahme) direkt aus Stundenzetteln
$sql = "SELECT m.rowid as manager_id, m.fk_commandedet, m.parent_section, m.line_order,";
$sql .= " cd.rowid, cd.fk_product, cd.qty, cd.description,";
$sql .= " p.ref as product_ref, p.label as product_label,";
@@ -1621,7 +1621,11 @@ if ($tab == 'products') {
// qty_removed: Entfällt für dieses Produkt (origin = 'omitted')
$sql .= " COALESCE((SELECT SUM(sp3.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp3";
$sql .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s3 ON s3.rowid = sp3.fk_stundenzettel";
- $sql .= " WHERE sp3.fk_product = cd.fk_product AND sp3.origin = 'omitted' AND s3.fk_commande = ".((int)$order->id)."), 0) as qty_removed";
+ $sql .= " WHERE sp3.fk_product = cd.fk_product AND sp3.origin = 'omitted' AND s3.fk_commande = ".((int)$order->id)."), 0) as qty_removed,";
+ // qty_returned: Rücknahme für dieses Produkt (origin = 'returned')
+ $sql .= " COALESCE((SELECT SUM(sp4.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp4";
+ $sql .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s4 ON s4.rowid = sp4.fk_stundenzettel";
+ $sql .= " WHERE sp4.fk_product = cd.fk_product AND sp4.origin = 'returned' AND s4.fk_commande = ".((int)$order->id)."), 0) as qty_returned";
$sql .= " FROM ".MAIN_DB_PREFIX."facture_lines_manager as m";
$sql .= " JOIN ".MAIN_DB_PREFIX."commandedet as cd ON cd.rowid = m.fk_commandedet";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = cd.fk_product";
@@ -1644,7 +1648,7 @@ if ($tab == 'products') {
// Falls keine Manager-Einträge, direkt aus commandedet laden
$hasManagerData = (count($sections) > 0 || count($products_without_section) > 0);
if (!$hasManagerData) {
- // Berechne qty_delivered, qty_added (Mehraufwand) und qty_removed (Entfällt) direkt aus Stundenzetteln
+ // Berechne qty_delivered, qty_added (Mehraufwand), qty_removed (Entfällt) und qty_returned (Rücknahme) direkt aus Stundenzetteln
$sql = "SELECT cd.rowid, cd.fk_product, cd.qty, cd.description,";
$sql .= " p.ref as product_ref, p.label as product_label,";
// qty_delivered: Summe aller qty_done für diese Auftragszeile (origin = 'order' oder 'added')
@@ -1658,7 +1662,11 @@ if ($tab == 'products') {
// qty_removed: Entfällt für dieses Produkt (origin = 'omitted')
$sql .= " COALESCE((SELECT SUM(sp3.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp3";
$sql .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s3 ON s3.rowid = sp3.fk_stundenzettel";
- $sql .= " WHERE sp3.fk_product = cd.fk_product AND sp3.origin = 'omitted' AND s3.fk_commande = ".((int)$order->id)."), 0) as qty_removed";
+ $sql .= " WHERE sp3.fk_product = cd.fk_product AND sp3.origin = 'omitted' AND s3.fk_commande = ".((int)$order->id)."), 0) as qty_removed,";
+ // qty_returned: Rücknahme für dieses Produkt (origin = 'returned')
+ $sql .= " COALESCE((SELECT SUM(sp4.qty_done) FROM ".MAIN_DB_PREFIX."stundenzettel_product sp4";
+ $sql .= " JOIN ".MAIN_DB_PREFIX."stundenzettel s4 ON s4.rowid = sp4.fk_stundenzettel";
+ $sql .= " WHERE sp4.fk_product = cd.fk_product AND sp4.origin = 'returned' AND s4.fk_commande = ".((int)$order->id)."), 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)$order->id);
@@ -1701,10 +1709,13 @@ if ($tab == 'products') {
$printProductRow = function($obj, $color = null, $sectionId = null) use ($langs, $filter, $alreadyOnStundenzettel) {
$qty_added = isset($obj->qty_added) ? (float)$obj->qty_added : 0;
$qty_removed = isset($obj->qty_removed) ? (float)$obj->qty_removed : 0;
+ $qty_returned = isset($obj->qty_returned) ? (float)$obj->qty_returned : 0;
// Effektive Gesamtmenge = Original + Hinzugefügt - Entfallen
$effectiveTotal = $obj->qty + $qty_added - $qty_removed;
- $remaining = $effectiveTotal - $obj->qty_delivered;
+ // Effektive Liefermenge = Geliefert - Zurückgenommen
+ $effectiveDelivered = $obj->qty_delivered - $qty_returned;
+ $remaining = $effectiveTotal - $effectiveDelivered;
$isDone = ($remaining <= 0);
// Filter anwenden
@@ -1764,8 +1775,13 @@ if ($tab == 'products') {
}
print '';
- // Menge geliefert/verbaut
- print ''.formatQty($obj->qty_delivered).' | ';
+ // Menge geliefert/verbaut (abzüglich Rücknahmen)
+ print '';
+ print formatQty($effectiveDelivered);
+ if ($qty_returned > 0) {
+ print ' -'.formatQty($qty_returned).'';
+ }
+ print ' | ';
// Verbleibend
print '';
@@ -1807,8 +1823,10 @@ if ($tab == 'products') {
foreach ($sectionProducts as $prod) {
$prodQtyAdded = isset($prod->qty_added) ? (float)$prod->qty_added : 0;
$prodQtyRemoved = isset($prod->qty_removed) ? (float)$prod->qty_removed : 0;
+ $prodQtyReturned = isset($prod->qty_returned) ? (float)$prod->qty_returned : 0;
$prodEffectiveTotal = $prod->qty + $prodQtyAdded - $prodQtyRemoved;
- $remaining = $prodEffectiveTotal - $prod->qty_delivered;
+ $prodEffectiveDelivered = $prod->qty_delivered - $prodQtyReturned;
+ $remaining = $prodEffectiveTotal - $prodEffectiveDelivered;
$isDone = ($remaining <= 0);
// Filter-Logik
@@ -1849,8 +1867,10 @@ if ($tab == 'products') {
foreach ($products_without_section as $prod) {
$prodQtyAdded = isset($prod->qty_added) ? (float)$prod->qty_added : 0;
$prodQtyRemoved = isset($prod->qty_removed) ? (float)$prod->qty_removed : 0;
+ $prodQtyReturned = isset($prod->qty_returned) ? (float)$prod->qty_returned : 0;
$prodEffectiveTotal = $prod->qty + $prodQtyAdded - $prodQtyRemoved;
- $remaining = $prodEffectiveTotal - $prod->qty_delivered;
+ $prodEffectiveDelivered = $prod->qty_delivered - $prodQtyReturned;
+ $remaining = $prodEffectiveTotal - $prodEffectiveDelivered;
$isDone = ($remaining <= 0);
// Filter-Logik
|