Version 1.5.0: Rücknahme-Bereich, Checkbox-Sichtbarkeit, Berechtigungen
- Neuer Rücknahme-Bereich (origin='returned') für zurückgenommene Produkte - Checkbox-Logik für Bereiche: Entfällt, Mehraufwand, Rücknahme, Merkzettel - Admin-Einstellungen für Standard-Sichtbarkeit der Bereiche - Erweiterte Berechtigungen: eigene vs alle (read/write/delete) - Tracking-Berechnung: qty_returned wird von Liefermenge abgezogen - Mobile-freundliches Layout beibehalten Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
fe2eb751c2
commit
585d530992
6 changed files with 576 additions and 48 deletions
|
|
@ -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 '</form>';
|
|||
print '</td>';
|
||||
print '</tr>';
|
||||
|
||||
// Standard-Bereiche anzeigen
|
||||
$currentSections = getDolGlobalString('STUNDENZETTEL_DEFAULT_SECTIONS', '');
|
||||
$selectedSections = !empty($currentSections) ? explode(',', $currentSections) : array();
|
||||
print '<tr class="oddeven">';
|
||||
print '<td>'.$langs->trans("DefaultSections").'<br><small class="opacitymedium">'.$langs->trans("DefaultSectionsDesc").'</small></td>';
|
||||
print '<td>';
|
||||
print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'" style="display: inline;">';
|
||||
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||
print '<input type="hidden" name="action" value="setDEFAULT_SECTIONS">';
|
||||
print '<div style="display: flex; flex-direction: column; gap: 5px;">';
|
||||
print '<label><input type="checkbox" name="default_sections[]" value="entfaellt"'.(in_array('entfaellt', $selectedSections) ? ' checked' : '').'> '.$langs->trans("Entfaellt").'</label>';
|
||||
print '<label><input type="checkbox" name="default_sections[]" value="mehraufwand"'.(in_array('mehraufwand', $selectedSections) ? ' checked' : '').'> '.$langs->trans("Mehraufwand").'</label>';
|
||||
print '<label><input type="checkbox" name="default_sections[]" value="ruecknahme"'.(in_array('ruecknahme', $selectedSections) ? ' checked' : '').'> '.$langs->trans("Ruecknahme").'</label>';
|
||||
print '<label><input type="checkbox" name="default_sections[]" value="merkzettel"'.(in_array('merkzettel', $selectedSections) ? ' checked' : '').'> '.$langs->trans("NotesMemo").'</label>';
|
||||
print '</div>';
|
||||
print '</td>';
|
||||
print '<td class="right">';
|
||||
print '<input type="submit" class="button small" value="'.$langs->trans("Modify").'">';
|
||||
print '</form>';
|
||||
print '</td>';
|
||||
print '</tr>';
|
||||
|
||||
print '</table>';
|
||||
|
||||
print dol_get_fiche_end();
|
||||
|
|
|
|||
478
card.php
Executable file → Normal file
478
card.php
Executable file → Normal file
|
|
@ -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 '<div class="div-table-responsive-no-min" style="margin-top: 15px;">';
|
||||
// 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 '<div style="margin-top: 15px; margin-bottom: 5px;">';
|
||||
print '<label style="cursor: pointer;">';
|
||||
print '<input type="checkbox" id="toggle_entfaellt" '.($showEntfaellt ? 'checked' : '').' onchange="toggleSection(\'entfaellt\', this.checked)">';
|
||||
print ' <span class="opacitymedium">'.$langs->trans("Entfaellt").' '.$langs->trans("ShowSection").'</span>';
|
||||
if ($hasEntfaelltProducts) {
|
||||
print ' <span class="badge badge-secondary">'.count($entfaelltProducts).'</span>';
|
||||
}
|
||||
print '</label>';
|
||||
print '</div>';
|
||||
|
||||
print '<div id="section_entfaellt" class="div-table-responsive-no-min" style="'.($showEntfaellt ? '' : 'display:none;').'">';
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre"><th colspan="5" style="border-bottom: 2px solid #dee2e6;"><strong>'.$langs->trans("Entfaellt").'</strong> - <span class="opacitymedium" style="font-weight:normal;">'.$langs->trans("EntfaelltDesc").'</span></th></tr>';
|
||||
print '<tr class="liste_titre">';
|
||||
|
|
@ -1357,15 +1511,7 @@ elseif ($object->id > 0) {
|
|||
print '<th class="center" style="width:40px;"></th>'; // Delete
|
||||
print '</tr>';
|
||||
|
||||
// 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 '<tr class="oddeven">';
|
||||
|
||||
|
|
@ -1580,20 +1726,26 @@ elseif ($object->id > 0) {
|
|||
print '</td>';
|
||||
print '</tr>';
|
||||
|
||||
// JavaScript für dynamische Max-Menge
|
||||
// JavaScript für dynamische Max-Menge und Bereichs-Toggle
|
||||
print '<script>
|
||||
function updateMaxQty(selectElement) {
|
||||
var selectedOption = selectElement.options[selectElement.selectedIndex];
|
||||
var maxQty = parseInt(selectedOption.getAttribute("data-max")) || 999;
|
||||
var maxQty = parseFloat(selectedOption.getAttribute("data-max")) || 999;
|
||||
var qtyInput = document.getElementById("entfaellt_qty_input");
|
||||
if (qtyInput) {
|
||||
qtyInput.max = maxQty;
|
||||
// Wenn aktuelle Menge größer als max, auf max setzen
|
||||
if (parseInt(qtyInput.value) > maxQty) {
|
||||
if (parseFloat(qtyInput.value) > maxQty) {
|
||||
qtyInput.value = maxQty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSection(sectionName, show) {
|
||||
var section = document.getElementById("section_" + sectionName);
|
||||
if (section) {
|
||||
section.style.display = show ? "" : "none";
|
||||
}
|
||||
}
|
||||
</script>';
|
||||
|
||||
print '</table>';
|
||||
|
|
@ -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 '<div class="div-table-responsive-no-min" style="margin-top: 15px;">';
|
||||
// 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 '<div style="margin-top: 15px; margin-bottom: 5px;">';
|
||||
print '<label style="cursor: pointer;">';
|
||||
print '<input type="checkbox" id="toggle_mehraufwand" '.($showMehraufwand ? 'checked' : '').' onchange="toggleSection(\'mehraufwand\', this.checked)">';
|
||||
print ' <span class="opacitymedium">'.$langs->trans("Mehraufwand").' '.$langs->trans("ShowSection").'</span>';
|
||||
if ($hasMehraufwandProducts) {
|
||||
print ' <span class="badge badge-warning">'.count($mehraufwandProducts).'</span>';
|
||||
}
|
||||
print '</label>';
|
||||
print '</div>';
|
||||
|
||||
print '<div id="section_mehraufwand" class="div-table-responsive-no-min" style="'.($showMehraufwand ? '' : 'display:none;').'">';
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre"><th colspan="5" style="border-bottom: 2px solid #dee2e6;"><strong>'.$langs->trans("Mehraufwand").'</strong> - <span class="opacitymedium" style="font-weight:normal;">'.$langs->trans("MehraufwandDesc").'</span></th></tr>';
|
||||
print '<tr class="liste_titre">';
|
||||
|
|
@ -1621,15 +1797,7 @@ elseif ($object->id > 0) {
|
|||
print '<th class="center" style="width:40px;"></th>'; // Delete
|
||||
print '</tr>';
|
||||
|
||||
// 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 '</div>';
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// 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 '<div style="margin-top: 15px; margin-bottom: 5px;">';
|
||||
print '<label style="cursor: pointer;">';
|
||||
print '<input type="checkbox" id="toggle_ruecknahme" '.($showRuecknahme ? 'checked' : '').' onchange="toggleSection(\'ruecknahme\', this.checked)">';
|
||||
print ' <span class="opacitymedium">'.$langs->trans("Ruecknahme").' '.$langs->trans("ShowSection").'</span>';
|
||||
if ($hasRuecknahmeProducts) {
|
||||
print ' <span class="badge badge-info">'.count($ruecknahmeProducts).'</span>';
|
||||
}
|
||||
print '</label>';
|
||||
print '</div>';
|
||||
|
||||
print '<div id="section_ruecknahme" class="div-table-responsive-no-min" style="'.($showRuecknahme ? '' : 'display:none;').'">';
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre"><th colspan="5" style="border-bottom: 2px solid #dee2e6;"><strong>'.$langs->trans("Ruecknahme").'</strong> - <span class="opacitymedium" style="font-weight:normal;">'.$langs->trans("RuecknahmeDesc").'</span></th></tr>';
|
||||
print '<tr class="liste_titre">';
|
||||
print '<th>'.$langs->trans("Product").'</th>';
|
||||
print '<th class="center" style="width:80px;">'.$langs->trans("Qty").'</th>';
|
||||
print '<th class="mobile-hide" style="width:200px;">'.$langs->trans("Reason").'</th>';
|
||||
print '<th class="center" style="width:40px;"></th>'; // Save
|
||||
print '<th class="center" style="width:40px;"></th>'; // Delete
|
||||
print '</tr>';
|
||||
|
||||
// Bereits erfasste Rücknahme-Produkte anzeigen
|
||||
if ($hasRuecknahmeProducts) {
|
||||
foreach ($ruecknahmeProducts as $prod) {
|
||||
print '<tr class="oddeven">';
|
||||
|
||||
// Produkt
|
||||
print '<td>';
|
||||
if ($prod->fk_product > 0) {
|
||||
print '<a href="'.DOL_URL_ROOT.'/product/card.php?id='.$prod->fk_product.'">';
|
||||
print img_picto('', 'product', 'class="pictofixedwidth"');
|
||||
print $prod->product_ref.' - '.$prod->product_label;
|
||||
print '</a>';
|
||||
} 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 '<span class="opacitymedium">'.$displayText.'</span>';
|
||||
}
|
||||
print ' <span class="badge badge-danger">'.$langs->trans("Ruecknahme").'</span>';
|
||||
// Mobile: Grund unter Produktname anzeigen
|
||||
if (!empty($prod->description)) {
|
||||
print '<div class="mobile-inline-desc"><small class="opacitymedium">'.dol_trunc(strip_tags($prod->description), 50).'</small></div>';
|
||||
}
|
||||
print '</td>';
|
||||
|
||||
// Menge
|
||||
print '<td class="center" style="width:80px;">';
|
||||
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=products" style="display:inline;" id="form_rn_qty_'.$prod->rowid.'">';
|
||||
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||
print '<input type="hidden" name="action" value="update_qty">';
|
||||
print '<input type="hidden" name="line_id" value="'.$prod->rowid.'">';
|
||||
print '<input type="number" name="qty_done" value="'.formatQty($prod->qty_done).'" class="flat" style="width:70px; text-align:center;" min="0" step="any">';
|
||||
print '</form>';
|
||||
print '</td>';
|
||||
|
||||
// Grund/Beschreibung (auf Mobile ausgeblendet)
|
||||
print '<td class="mobile-hide" style="width:200px;">';
|
||||
if (!empty($prod->description)) {
|
||||
print dol_htmlentitiesbr($prod->description);
|
||||
} else {
|
||||
print '<span class="opacitymedium">-</span>';
|
||||
}
|
||||
print '</td>';
|
||||
|
||||
// Save
|
||||
print '<td class="center" style="width:40px;">';
|
||||
print '<a href="javascript:document.getElementById(\'form_rn_qty_'.$prod->rowid.'\').submit();" title="'.$langs->trans("Save").'">';
|
||||
print '<span class="fas fa-save" style="color: #007bff;"></span>';
|
||||
print '</a>';
|
||||
print '</td>';
|
||||
|
||||
// Delete
|
||||
print '<td class="center" style="width:40px;">';
|
||||
print '<a href="'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=products&action=delete_ruecknahme&line_id='.$prod->rowid.'&token='.newToken().'" title="'.$langs->trans("Delete").'">';
|
||||
print '<span class="fas fa-trash" style="color: #dc3545;"></span>';
|
||||
print '</a>';
|
||||
print '</td>';
|
||||
|
||||
print '</tr>';
|
||||
}
|
||||
}
|
||||
|
||||
// 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 '<tr class="liste_titre">';
|
||||
print '<th colspan="5">'.$langs->trans("AddRuecknahme").'</th>';
|
||||
print '</tr>';
|
||||
|
||||
print '<tr class="oddeven">';
|
||||
print '<form method="POST" action="'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&tab=products" id="form_ruecknahme">';
|
||||
print '<input type="hidden" name="token" value="'.newToken().'">';
|
||||
print '<input type="hidden" name="action" value="add_ruecknahme">';
|
||||
|
||||
// Produkt-Auswahl - NUR Produkte die bereits verbaut wurden
|
||||
print '<td>';
|
||||
if ($hasDeliveredProducts) {
|
||||
print '<select name="ruecknahme_product" class="flat minwidth300" id="ruecknahme_product_select" onchange="updateMaxQtyRuecknahme(this)">';
|
||||
print '<option value="" data-max="1">-- '.$langs->trans("SelectProducts").' --</option>';
|
||||
|
||||
foreach ($deliveredProducts as $dp) {
|
||||
if ($dp->fk_product > 0) {
|
||||
print '<option value="'.$dp->fk_product.'" data-commandedet="'.$dp->rowid.'" data-max="'.formatQty($dp->qty_available).'">';
|
||||
print $dp->product_ref.' - '.$dp->product_label;
|
||||
print ' ('.$langs->trans("QtyDelivered").': '.formatQty($dp->qty_available).')';
|
||||
print '</option>';
|
||||
} else {
|
||||
$desc = strip_tags($dp->description);
|
||||
$descShort = (strlen($desc) > 50) ? substr($desc, 0, 47).'...' : $desc;
|
||||
print '<option value="freetext_'.$dp->rowid.'" data-commandedet="'.$dp->rowid.'" data-description="'.dol_escape_htmltag($desc).'" data-max="'.formatQty($dp->qty_available).'">';
|
||||
print $descShort.' ('.$langs->trans("QtyDelivered").': '.formatQty($dp->qty_available).')';
|
||||
print '</option>';
|
||||
}
|
||||
}
|
||||
|
||||
print '</select>';
|
||||
} else {
|
||||
print '<small class="opacitymedium">'.$langs->trans("NoProductsDelivered").'</small>';
|
||||
}
|
||||
print '</td>';
|
||||
|
||||
// Menge (mit dynamischem max basierend auf Produktauswahl)
|
||||
print '<td class="center" style="width:80px;">';
|
||||
print '<input type="number" name="ruecknahme_qty" id="ruecknahme_qty_input" class="flat" style="width:70px; text-align:center;" value="1" min="0" step="any">';
|
||||
print '</td>';
|
||||
|
||||
// Grund (Desktop: in Zeile, Mobile: ausgeblendet)
|
||||
print '<td class="mobile-hide" style="width:200px;">';
|
||||
print '<input type="text" name="ruecknahme_description" class="flat" style="width:100%;" placeholder="'.$langs->trans("Reason").'">';
|
||||
print '</td>';
|
||||
|
||||
// Hinzufügen-Button (colspan für beide Button-Spalten)
|
||||
print '<td class="center" colspan="2" style="width:80px;">';
|
||||
print '<button type="submit" class="button" title="'.$langs->trans("Add").'" style="border: none; background: none; cursor: pointer;">';
|
||||
print '<span class="fas fa-plus-circle" style="color: #28a745; font-size: 1.3em;"></span>';
|
||||
print '</button>';
|
||||
print '</td>';
|
||||
|
||||
print '</form>';
|
||||
print '</tr>';
|
||||
|
||||
// Mobile: Grund in separater Zeile
|
||||
print '<tr class="oddeven mobile-description-row">';
|
||||
print '<td colspan="5">';
|
||||
print '<input type="text" name="ruecknahme_description" form="form_ruecknahme" class="flat" style="width:100%;" placeholder="'.$langs->trans("Reason").'">';
|
||||
print '</td>';
|
||||
print '</tr>';
|
||||
|
||||
// JavaScript für dynamische Max-Menge
|
||||
print '<script>
|
||||
function updateMaxQtyRuecknahme(selectElement) {
|
||||
var selectedOption = selectElement.options[selectElement.selectedIndex];
|
||||
var maxQty = parseFloat(selectedOption.getAttribute("data-max")) || 999;
|
||||
var qtyInput = document.getElementById("ruecknahme_qty_input");
|
||||
if (qtyInput) {
|
||||
qtyInput.max = maxQty;
|
||||
if (parseFloat(qtyInput.value) > maxQty) {
|
||||
qtyInput.value = maxQty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSection(sectionName, show) {
|
||||
var section = document.getElementById("section_" + sectionName);
|
||||
if (section) {
|
||||
section.style.display = show ? "" : "none";
|
||||
}
|
||||
}
|
||||
</script>';
|
||||
|
||||
print '</table>';
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
// =============================================
|
||||
// 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 '<div class="div-table-responsive-no-min" style="margin-top: 15px;">';
|
||||
// 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 '<div style="margin-top: 15px; margin-bottom: 5px;">';
|
||||
print '<label style="cursor: pointer;">';
|
||||
print '<input type="checkbox" id="toggle_merkzettel" '.($showMerkzettel ? 'checked' : '').' onchange="toggleSection(\'merkzettel\', this.checked)">';
|
||||
print ' <span class="opacitymedium">'.$langs->trans("NotesMemo").' '.$langs->trans("ShowSection").'</span>';
|
||||
if ($hasNotes) {
|
||||
print ' <span class="badge badge-info">'.count($object->notes).'</span>';
|
||||
}
|
||||
print '</label>';
|
||||
print '</div>';
|
||||
|
||||
print '<div id="section_merkzettel" class="div-table-responsive-no-min" style="'.($showMerkzettel ? '' : 'display:none;').'">';
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre"><th colspan="4" style="border-bottom: 2px solid #dee2e6;"><strong>'.$langs->trans("NotesMemo").'</strong> - <span class="opacitymedium" style="font-weight:normal;">'.$langs->trans("NotesForNextVisit").'</span></th></tr>';
|
||||
|
||||
|
|
@ -1938,7 +2358,7 @@ elseif ($object->id > 0) {
|
|||
if ($permissiontovalidate) {
|
||||
print '<a class="butAction" href="'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&action=validate">'.$langs->trans("Validate").'</a>';
|
||||
}
|
||||
if ($permissiontodelete) {
|
||||
if ($permissiontodeleteobj) {
|
||||
print '<a class="butActionDelete" href="'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&action=delete">'.$langs->trans("Delete").'</a>';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
38
core/modules/modStundenzettel.class.php
Executable file → Normal file
38
core/modules/modStundenzettel.class.php
Executable file → Normal file
|
|
@ -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;
|
||||
|
|
|
|||
26
langs/de_DE/stundenzettel.lang
Executable file → Normal file
26
langs/de_DE/stundenzettel.lang
Executable file → Normal file
|
|
@ -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
|
||||
|
|
|
|||
10
list.php
Executable file → Normal file
10
list.php
Executable file → Normal file
|
|
@ -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
|
||||
|
|
|
|||
38
stundenzettel_commande.php
Executable file → Normal file
38
stundenzettel_commande.php
Executable file → Normal file
|
|
@ -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 '</td>';
|
||||
|
||||
// Menge geliefert/verbaut
|
||||
print '<td class="right">'.formatQty($obj->qty_delivered).'</td>';
|
||||
// Menge geliefert/verbaut (abzüglich Rücknahmen)
|
||||
print '<td class="right">';
|
||||
print formatQty($effectiveDelivered);
|
||||
if ($qty_returned > 0) {
|
||||
print ' <span class="badge" style="background-color: #6c757d; color: #fff; font-size: 0.75em;" title="'.$langs->trans("Ruecknahme").'">-'.formatQty($qty_returned).'</span>';
|
||||
}
|
||||
print '</td>';
|
||||
|
||||
// Verbleibend
|
||||
print '<td class="right">';
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue