From dabcdbde13a7824dfc51ac6e03bb2675d1ca9dca Mon Sep 17 00:00:00 2001 From: data Date: Mon, 2 Mar 2026 18:39:37 +0100 Subject: [PATCH] =?UTF-8?q?Version=202.1.0:=20Rechnungs=C3=BCbernahme=20er?= =?UTF-8?q?weitert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Neuer Button "Ohne Produktgruppen" für flache Übernahme ohne Sections/Zwischensummen - Mehraufwand-Produktgruppe über Modul-Einstellung konfigurierbar - Standard-Bankkonto wird automatisch aus Einstellung gesetzt - "Ihr Zeichen" (ref_client) vom Auftrag übernommen - Extrafelder Angebotsnummer/Auftragsnummer vom Auftrag kopiert - Verwaiste Zwischensummen werden nach Rechnungserstellung bereinigt - Zwei neue Modul-Einstellungen: Bankkonto und Mehraufwand-Sektion Co-Authored-By: Claude Opus 4.6 --- README.md | 18 +- admin/setup.php | 78 +++++++ core/modules/modStundenzettel.class.php | 2 +- langs/de_DE/stundenzettel.lang | 13 ++ stundenzettel_commande.php | 282 ++++++++++++++++++------ 5 files changed, 325 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index ecf1358..350a458 100755 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Stundenzettel Modul für Dolibarr -**Version:** 1.8.0 +**Version:** 2.1.0 **Autor:** Data IT Solution **Kompatibilität:** Dolibarr 16.0+ **Lizenz:** GPL v3 @@ -56,6 +56,8 @@ Die Moduleinstellungen finden Sie unter **Einstellungen > Module > Stundenzettel | **Standard-Filter** | Welcher Filter in der Produktliste standardmäßig angezeigt wird | | **Standard-Datum** | Aktuelles Datum oder Datum des letzten offenen Stundenzettels | | **Stunden-Übernahme** | Gesamtstunden auf einer Zeile oder pro Tag eine Zeile | +| **Standard-Bankkonto** | Welches Bankkonto bei der Rechnungsübernahme automatisch gesetzt wird | +| **Mehraufwand als Produktgruppe** | Ob der Mehraufwand bei Übernahme mit Produktgruppen als eigene Sektion übernommen wird | ### Stundenübernahme-Modus (pro Auftrag) @@ -117,6 +119,20 @@ Sie können beim Kunden (unter **Kunden > Kundenkarte**) eine Standard-Leistung ## Changelog +### Version 2.1.0 +- **Rechnungsübernahme ohne Produktgruppen**: Neuer Button "Ohne Produktgruppen" überträgt Produkte und Leistungen als flache Liste ohne Section-Titel und Zwischensummen +- **Mehraufwand-Produktgruppe konfigurierbar**: Neue Modul-Einstellung ob Mehraufwand bei Übernahme mit Produktgruppen als eigene Sektion übernommen wird +- **Standard-Bankkonto**: Bankkonto wird bei der Rechnungsübernahme automatisch aus Modul-Einstellung gesetzt +- **Ihr Zeichen (ref_client)**: Wird automatisch vom Auftrag in die Rechnung übernommen +- **Extrafelder-Übernahme**: Angebotsnummer und Auftragsnummer werden automatisch vom Auftrag in die Rechnung kopiert +- **Verwaiste Zwischensummen bereinigen**: Nach Rechnungserstellung werden verwaiste Section-Titel und Zwischensummen aus facturedet und facture_lines_manager entfernt + +### Version 2.0.0 +- **PWA Mobile App**: Installierbare Progressive Web App für Stundenzettel-Verwaltung unterwegs +- **4-Panel-Navigation**: Alle STZ, Stundenzettel, Produktliste und Lieferauflistung per Swipe +- **Merkzettel auf Produktliste**: Merkzettel-Box über der Produktliste mit Abhaken und Hinzufügen +- **Mehraufwand-Transfer**: MA-Produkte aus Auftrag in Stundenzettel übernehmen + ### Version 1.8.0 - **Rücknahmen in Rechnungsübernahme**: Zurückgenommene Produkte (origin='returned') werden nicht mehr in die Rechnung übertragen - Mehraufwand: Rücknahmen werden von der Rechnungsmenge abgezogen (Matching über fk_product oder description) diff --git a/admin/setup.php b/admin/setup.php index c20aea9..6429681 100755 --- a/admin/setup.php +++ b/admin/setup.php @@ -83,6 +83,28 @@ if ($action == 'setDEFAULT_SECTIONS') { exit; } +if ($action == 'setINVOICE_BANK_ACCOUNT') { + $value = GETPOST('bank_account_value', 'int'); + if (dolibarr_set_const($db, 'STUNDENZETTEL_INVOICE_BANK_ACCOUNT', $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; +} + +if ($action == 'setINVOICE_MEHRAUFWAND_SECTION') { + $value = GETPOST('mehraufwand_section_value', 'alpha'); + if (dolibarr_set_const($db, 'STUNDENZETTEL_INVOICE_MEHRAUFWAND_SECTION', $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 */ @@ -212,6 +234,62 @@ print ''; print ''; +// Rechnungsübernahme-Einstellungen +print '
'; +print ''; +print ''; +print ''; +print ''; +print ''; +print ''; + +// Bankkonto für Rechnungen +$currentBankAccount = getDolGlobalString('STUNDENZETTEL_INVOICE_BANK_ACCOUNT', '0'); +$sqlBankAccounts = "SELECT rowid, ref, label FROM ".MAIN_DB_PREFIX."bank_account WHERE clos = 0 ORDER BY label"; +$resqlBank = $db->query($sqlBankAccounts); +print ''; +print ''; +print ''; +print ''; +print ''; + +// Mehraufwand als Produktgruppe +$currentMehraufwandSection = getDolGlobalString('STUNDENZETTEL_INVOICE_MEHRAUFWAND_SECTION', '1'); +print ''; +print ''; +print ''; +print ''; +print ''; + +print '
'.$langs->trans("InvoiceTransferSettings").''.$langs->trans("Value").'
'.$langs->trans("InvoiceBankAccount").'
'.$langs->trans("InvoiceBankAccountDesc").'
'; +print '
'; +print ''; +print ''; +print ''; +print '
'; +print ''; +print ''; +print '
'.$langs->trans("InvoiceMehraufwandSection").'
'.$langs->trans("InvoiceMehraufwandSectionDesc").'
'; +print '
'; +print ''; +print ''; +print ''; +print '
'; +print ''; +print ''; +print '
'; + // PWA Mobile App Bereich print '
'; print ''; diff --git a/core/modules/modStundenzettel.class.php b/core/modules/modStundenzettel.class.php index 719fc07..420a76b 100755 --- 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.8.0'; + $this->version = '2.1.0'; // Autor $this->editor_name = 'Data IT Solution'; diff --git a/langs/de_DE/stundenzettel.lang b/langs/de_DE/stundenzettel.lang index fda7257..5826ffb 100755 --- a/langs/de_DE/stundenzettel.lang +++ b/langs/de_DE/stundenzettel.lang @@ -283,6 +283,19 @@ incl = inkl. NettoSTZ = Netto STZ NettoSTZHelp = Netto-Wert aller freigegebenen Stundenzettel (Produkte + Arbeitsstunden) +# Rechnungsübernahme Einstellungen +InvoiceTransferSettings = Rechnungsübernahme +InvoiceBankAccount = Standard-Bankkonto +InvoiceBankAccountDesc = Welches Bankkonto soll bei der Rechnungsübernahme automatisch gesetzt werden? +InvoiceMehraufwandSection = Mehraufwand als Produktgruppe +InvoiceMehraufwandSectionDesc = Soll der Mehraufwand bei der Übernahme mit Produktgruppen als eigene Sektion übernommen werden? +TransferWithSections = Mit Produktgruppen +TransferWithoutSections = Ohne Produktgruppen +ConfirmTransferInvoiceChoice = Wie sollen die Produkte in die Rechnung übertragen werden? +ConfirmTransferInvoiceWithSections = Die Produkte werden mit Produktgruppen (Sektionen und Zwischensummen) in die Rechnung übertragen. +ConfirmTransferInvoiceWithoutSections = Die Produkte werden ohne Produktgruppen als flache Liste in die Rechnung übertragen. +None = Keins + # PWA Mobile App PWAMobileApp = PWA Mobile App PWALink = Stundenzettel PWA öffnen diff --git a/stundenzettel_commande.php b/stundenzettel_commande.php index 04cf466..92619f7 100755 --- a/stundenzettel_commande.php +++ b/stundenzettel_commande.php @@ -359,10 +359,13 @@ if ($action == 'reset_stundenzettel' && $canReset) { exit; } -// In Rechnung übernehmen -if ($action == 'confirm_transfer_invoice' && GETPOST('confirm', 'alpha') == 'yes' && $user->hasRight('facture', 'creer')) { +// In Rechnung übernehmen (mit oder ohne Produktgruppen) +if (in_array($action, array('confirm_transfer_invoice', 'confirm_transfer_invoice_flat')) && $user->hasRight('facture', 'creer')) { require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php'; + // Steuerung: Mit oder ohne Produktgruppen + $withSections = ($action == 'confirm_transfer_invoice'); + $db->begin(); $error = 0; @@ -377,9 +380,36 @@ if ($action == 'confirm_transfer_invoice' && GETPOST('confirm', 'alpha') == 'yes $facture->note_private = $langs->trans('CreatedFromStundenzettel').' - '.$order->ref; $facture->linked_objects['commande'] = $order->id; + // "Ihr Zeichen" vom Auftrag übernehmen + if (!empty($order->ref_client)) { + $facture->ref_customer = $order->ref_client; + } + + // Bankkonto aus Modul-Einstellung + $bankAccount = getDolGlobalString('STUNDENZETTEL_INVOICE_BANK_ACCOUNT', 0); + if ($bankAccount > 0) { + $facture->fk_account = (int)$bankAccount; + } + $facture_id = $facture->create($user); if ($facture_id > 0) { + // Extrafelder vom Auftrag übernehmen (Angebotsnummer, Auftragsnummer) + $order->fetch_optionals(); + $facture->fetch_optionals(); + $extrafieldsUpdated = false; + if (!empty($order->array_options['options_angebotsnummer'])) { + $facture->array_options['options_angebotsnummer'] = $order->array_options['options_angebotsnummer']; + $extrafieldsUpdated = true; + } + if (!empty($order->array_options['options_auftragsnummer'])) { + $facture->array_options['options_auftragsnummer'] = $order->array_options['options_auftragsnummer']; + $extrafieldsUpdated = true; + } + if ($extrafieldsUpdated) { + $facture->insertExtraFields(); + } + // Einkaufspreise aller Produkte vorladen // Priorität: 1. cost_price (manuell gesetzt), 2. bester Lieferanten-Stückpreis, 3. PMP $buyPrices = array(); // fk_product => buy_price @@ -562,31 +592,35 @@ if ($action == 'confirm_transfer_invoice' && GETPOST('confirm', 'alpha') == 'yes // Nur Sections mit tatsächlich verwendeten Produkten hinzufügen if ($sectionHasInvoicedProducts) { - // Section-Titel hinzufügen (special_code = 100) - $result = $facture->addline( - $section->title, - 0, // subprice - 0, // qty - 0, // tva_tx - 0, 0, // localtax - 0, // fk_product - 0, // remise_percent - '', '', // dates - 0, 0, '', 'HT', 0, - 9, // product_type (9 = Title) - $rang++, - 100, // special_code = 100 für Section - 0, '', 0, 0 - ); + $currentSectionDetId = 0; - if ($result > 0) { - $invoiceManagerLines[] = array( - 'type' => 'section', - 'fk_facturedet' => $result, - 'title' => $section->title, - 'parent' => null + // Section-Titel hinzufügen (nur bei Übernahme mit Produktgruppen) + if ($withSections) { + $result = $facture->addline( + $section->title, + 0, // subprice + 0, // qty + 0, // tva_tx + 0, 0, // localtax + 0, // fk_product + 0, // remise_percent + '', '', // dates + 0, 0, '', 'HT', 0, + 9, // product_type (9 = Title) + $rang++, + 100, // special_code = 100 für Section + 0, '', 0, 0 ); - $currentSectionDetId = $result; + + if ($result > 0) { + $invoiceManagerLines[] = array( + 'type' => 'section', + 'fk_facturedet' => $result, + 'title' => $section->title, + 'parent' => null + ); + $currentSectionDetId = $result; + } } // Produkte der Section hinzufügen @@ -627,12 +661,14 @@ if ($action == 'confirm_transfer_invoice' && GETPOST('confirm', 'alpha') == 'yes ); if ($result > 0) { - $invoiceManagerLines[] = array( - 'type' => 'product', - 'fk_facturedet' => $result, - 'title' => null, - 'parent' => $currentSectionDetId - ); + if ($withSections && $currentSectionDetId > 0) { + $invoiceManagerLines[] = array( + 'type' => 'product', + 'fk_facturedet' => $result, + 'title' => null, + 'parent' => $currentSectionDetId + ); + } $sectionSubtotal += $prod->subprice * $qtyToInvoice; } elseif ($result < 0) { $error++; @@ -641,8 +677,8 @@ if ($action == 'confirm_transfer_invoice' && GETPOST('confirm', 'alpha') == 'yes } } - // Zwischensumme hinzufügen wenn Section show_subtotal hat - if ($section->show_subtotal && $sectionSubtotal > 0) { + // Zwischensumme hinzufügen wenn Section show_subtotal hat (nur mit Produktgruppen) + if ($withSections && $section->show_subtotal && $sectionSubtotal > 0) { $subtotalLabel = 'Zwischensumme: '.$section->title; $result = $facture->addline( $subtotalLabel, @@ -689,8 +725,8 @@ if ($action == 'confirm_transfer_invoice' && GETPOST('confirm', 'alpha') == 'yes if ($hasOrphansWithQty) { $sonstigeSectionResult = 0; - // Nur Section erstellen wenn im Auftrag auch Sections vorhanden sind - if ($orderHasSections) { + // Nur Section erstellen wenn mit Produktgruppen und im Auftrag Sections vorhanden + if ($withSections && $orderHasSections) { $sonstigeSectionResult = $facture->addline( $langs->trans('OtherProducts'), 0, 0, 0, 0, 0, 0, 0, '', '', 0, 0, '', 'HT', 0, @@ -745,7 +781,7 @@ if ($action == 'confirm_transfer_invoice' && GETPOST('confirm', 'alpha') == 'yes $prod->fk_unit // 28: fk_unit ); - if ($result > 0 && $orderHasSections && $sonstigeSectionResult > 0) { + if ($result > 0 && $withSections && $orderHasSections && $sonstigeSectionResult > 0) { $invoiceManagerLines[] = array( 'type' => 'product', 'fk_facturedet' => $result, @@ -759,8 +795,8 @@ if ($action == 'confirm_transfer_invoice' && GETPOST('confirm', 'alpha') == 'yes } } - // Zwischensumme für Sonstige Produkte - if ($sonstigeSectionResult > 0) { + // Zwischensumme für Sonstige Produkte (nur mit Produktgruppen) + if ($withSections && $sonstigeSectionResult > 0) { $subtotalLabel = 'Zwischensumme: '.$langs->trans('OtherProducts'); $subtotalResult = $facture->addline( $subtotalLabel, @@ -779,12 +815,13 @@ if ($action == 'confirm_transfer_invoice' && GETPOST('confirm', 'alpha') == 'yes } // Mehraufwand hinzufügen (am Ende) - // Nur als Section wenn im Auftrag auch Sections vorhanden sind + // Section nur wenn: mit Produktgruppen UND Sections im Auftrag UND Einstellung aktiv + $mehraufwandAsSection = getDolGlobalString('STUNDENZETTEL_INVOICE_MEHRAUFWAND_SECTION', '1') == '1'; if (count($additionalProducts) > 0) { $mehraufwandSectionResult = 0; - // Nur Section erstellen wenn im Auftrag auch Sections vorhanden sind - if ($orderHasSections) { + // Section nur erstellen wenn mit Produktgruppen, Sections vorhanden und Einstellung aktiv + if ($withSections && $orderHasSections && $mehraufwandAsSection) { $mehraufwandSectionResult = $facture->addline( $langs->trans('Mehraufwand'), 0, 0, 0, 0, 0, 0, 0, '', '', 0, 0, '', 'HT', 0, @@ -880,7 +917,7 @@ if ($action == 'confirm_transfer_invoice' && GETPOST('confirm', 'alpha') == 'yes ); } - if ($productResult > 0 && $orderHasSections && $mehraufwandSectionResult > 0) { + if ($productResult > 0 && $withSections && $orderHasSections && $mehraufwandAsSection && $mehraufwandSectionResult > 0) { $invoiceManagerLines[] = array( 'type' => 'product', 'fk_facturedet' => $productResult, @@ -893,8 +930,8 @@ if ($action == 'confirm_transfer_invoice' && GETPOST('confirm', 'alpha') == 'yes } } - // Zwischensumme für Mehraufwand - if ($mehraufwandSectionResult > 0) { + // Zwischensumme für Mehraufwand (nur mit Produktgruppen und Einstellung aktiv) + if ($withSections && $mehraufwandAsSection && $mehraufwandSectionResult > 0) { $subtotalLabel = 'Zwischensumme: '.$langs->trans('Mehraufwand'); $subtotalResult = $facture->addline( $subtotalLabel, @@ -968,7 +1005,7 @@ if ($action == 'confirm_transfer_invoice' && GETPOST('confirm', 'alpha') == 'yes if ($hasWorkHours) { // Arbeitszeit-Section erstellen (nur wenn Sections im Auftrag) $arbeitszeitSectionResult = 0; - if ($orderHasSections) { + if ($withSections && $orderHasSections) { $arbeitszeitSectionResult = $facture->addline( $langs->trans('Leistungen'), 0, 0, 0, 0, 0, 0, 0, '', '', 0, 0, '', 'HT', 0, @@ -1049,7 +1086,7 @@ if ($action == 'confirm_transfer_invoice' && GETPOST('confirm', 'alpha') == 'yes ); if ($hoursResult > 0) { - if ($orderHasSections && $arbeitszeitSectionResult > 0) { + if ($withSections && $orderHasSections && $arbeitszeitSectionResult > 0) { $invoiceManagerLines[] = array( 'type' => 'product', 'fk_facturedet' => $hoursResult, @@ -1064,8 +1101,8 @@ if ($action == 'confirm_transfer_invoice' && GETPOST('confirm', 'alpha') == 'yes } } - // Zwischensumme für Arbeitszeit - if ($arbeitszeitSectionResult > 0 && $arbeitszeitSubtotal > 0) { + // Zwischensumme für Arbeitszeit (nur mit Produktgruppen) + if ($withSections && $arbeitszeitSectionResult > 0 && $arbeitszeitSubtotal > 0) { $subtotalLabel = 'Zwischensumme: '.$langs->trans('Leistungen'); $subtotalResult = $facture->addline( $subtotalLabel, @@ -1083,8 +1120,8 @@ if ($action == 'confirm_transfer_invoice' && GETPOST('confirm', 'alpha') == 'yes } } - // llx_facture_lines_manager für Rechnung erstellen - if (!$error && count($invoiceManagerLines) > 0) { + // llx_facture_lines_manager für Rechnung erstellen (nur mit Produktgruppen) + if ($withSections && !$error && count($invoiceManagerLines) > 0) { $lineOrder = 1; $sectionMap = array(); // old_facturedet_id => new_manager_rowid @@ -1122,14 +1159,112 @@ if ($action == 'confirm_transfer_invoice' && GETPOST('confirm', 'alpha') == 'yes if (!$error) { // ============================================ - // Rang-Werte in facturedet korrigieren + // Rang-Werte in facturedet korrigieren (nur mit Produktgruppen) // Synchronisiert rang mit line_order aus facture_lines_manager // ============================================ - $sqlFixRang = "UPDATE ".MAIN_DB_PREFIX."facturedet fd - INNER JOIN ".MAIN_DB_PREFIX."facture_lines_manager flm ON flm.fk_facturedet = fd.rowid - SET fd.rang = flm.line_order - WHERE flm.fk_facture = ".((int)$facture_id); - $db->query($sqlFixRang); + if ($withSections) { + $sqlFixRang = "UPDATE ".MAIN_DB_PREFIX."facturedet fd + INNER JOIN ".MAIN_DB_PREFIX."facture_lines_manager flm ON flm.fk_facturedet = fd.rowid + SET fd.rang = flm.line_order + WHERE flm.fk_facture = ".((int)$facture_id); + $db->query($sqlFixRang); + } + + // ============================================ + // Verwaiste Zwischensummen/Sections bereinigen + // SubtotalTitle syncManagerTable() kann beim Seitenaufruf + // manager-Einträge ohne gültigen fk_facturedet erstellen. + // Außerdem können addline()-Fehler zu Orphans führen. + // ============================================ + + // 1. Verwaiste facture_lines_manager-Einträge löschen (fk_facturedet=NULL oder ungültig) + $sqlCleanManager = "DELETE FROM ".MAIN_DB_PREFIX."facture_lines_manager"; + $sqlCleanManager .= " WHERE fk_facture = ".((int)$facture_id); + $sqlCleanManager .= " AND (fk_facturedet IS NULL OR fk_facturedet NOT IN ("; + $sqlCleanManager .= " SELECT rowid FROM ".MAIN_DB_PREFIX."facturedet WHERE fk_facture = ".((int)$facture_id); + $sqlCleanManager .= " ))"; + $db->query($sqlCleanManager); + + // 2. Verwaiste Section-Titel in facturedet löschen (product_type=9 ohne zugehörige Produkte) + // Ein Section-Titel (special_code=100) ist verwaist wenn keine Produkte zwischen ihm + // und dem nächsten Section-Titel/Ende existieren + $sqlOrphanSections = "SELECT fd.rowid, fd.rang, fd.special_code, fd.description"; + $sqlOrphanSections .= " FROM ".MAIN_DB_PREFIX."facturedet fd"; + $sqlOrphanSections .= " WHERE fd.fk_facture = ".((int)$facture_id); + $sqlOrphanSections .= " AND fd.product_type = 9"; + $sqlOrphanSections .= " ORDER BY fd.rang ASC"; + $resqlOrphan = $db->query($sqlOrphanSections); + if ($resqlOrphan) { + $titleLines = array(); + while ($obj = $db->fetch_object($resqlOrphan)) { + $titleLines[] = $obj; + } + + // Alle regulären Produkt-/Dienstleistungszeilen laden + $sqlProducts = "SELECT rowid, rang FROM ".MAIN_DB_PREFIX."facturedet"; + $sqlProducts .= " WHERE fk_facture = ".((int)$facture_id); + $sqlProducts .= " AND product_type < 9"; + $sqlProducts .= " ORDER BY rang ASC"; + $resqlProducts = $db->query($sqlProducts); + $productRangs = array(); + if ($resqlProducts) { + while ($objP = $db->fetch_object($resqlProducts)) { + $productRangs[] = (int)$objP->rang; + } + } + + // Section-Titel und Zwischensummen prüfen + $orphanIds = array(); + for ($i = 0; $i < count($titleLines); $i++) { + $line = $titleLines[$i]; + + // Zwischensummen (special_code=102) ohne zugehörigen Section-Titel + if ($line->special_code == 102) { + // Prüfe ob es einen vorherigen Section-Titel gibt + $hasSection = false; + for ($j = $i - 1; $j >= 0; $j--) { + if ($titleLines[$j]->special_code == 100 && !in_array($titleLines[$j]->rowid, $orphanIds)) { + $hasSection = true; + break; + } + } + if (!$hasSection) { + $orphanIds[] = (int)$line->rowid; + } + continue; + } + + // Section-Titel (special_code=100): verwaist wenn keine Produkte zwischen + // diesem Titel und dem nächsten Titel/Zwischensumme + if ($line->special_code == 100) { + $currentRang = (int)$line->rang; + // Nächsten Titel/Zwischensumme finden + $nextTitleRang = PHP_INT_MAX; + for ($j = $i + 1; $j < count($titleLines); $j++) { + $nextTitleRang = (int)$titleLines[$j]->rang; + break; + } + // Prüfen ob Produkte zwischen currentRang und nextTitleRang existieren + $hasProducts = false; + foreach ($productRangs as $pRang) { + if ($pRang > $currentRang && $pRang < $nextTitleRang) { + $hasProducts = true; + break; + } + } + if (!$hasProducts) { + $orphanIds[] = (int)$line->rowid; + } + } + } + + // Verwaiste Zeilen aus facturedet und facture_lines_manager löschen + if (!empty($orphanIds)) { + $orphanIdList = implode(',', $orphanIds); + $db->query("DELETE FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid IN (".$orphanIdList.")"); + $db->query("DELETE FROM ".MAIN_DB_PREFIX."facture_lines_manager WHERE fk_facture = ".((int)$facture_id)." AND fk_facturedet IN (".$orphanIdList.")"); + } + } // Status auf "in Rechnung übertragen" setzen (direkt per SQL) $sqlUpdate = "UPDATE ".MAIN_DB_PREFIX."commande_extrafields SET stundenzettel_status = 2 WHERE fk_object = ".((int)$order->id); @@ -1269,17 +1404,32 @@ if ($action == 'confirm_release_warning') { ); } -// Bestätigung für Rechnungsübertragung +// Bestätigung für Rechnungsübertragung (mit Auswahl: mit/ohne Produktgruppen) if ($action == 'transfer_invoice') { - print $form->formconfirm( - $_SERVER['PHP_SELF'].'?id='.$order->id.'&tab=products&noredirect=1', - $langs->trans('ConfirmTransferInvoiceTitle'), - $langs->trans('ConfirmTransferInvoice'), - 'confirm_transfer_invoice', - array(), - 0, - 1 - ); + $baseUrl = $_SERVER['PHP_SELF'].'?id='.$order->id.'&tab=products&noredirect=1'; + print '
'; + print '
'; + print '

'.img_picto('', 'bill', 'class="pictofixedwidth"').$langs->trans('ConfirmTransferInvoiceTitle').'

'; + print '

'.$langs->trans('ConfirmTransferInvoiceChoice').'

'; + print '
'; + // Button: Mit Produktgruppen + print ''; + print img_picto('', 'object_list', 'class="pictofixedwidth"').$langs->trans('TransferWithSections'); + print ''; + // Button: Ohne Produktgruppen + print ''; + print img_picto('', 'object_line', 'class="pictofixedwidth"').$langs->trans('TransferWithoutSections'); + print ''; + print '
'; + print '
'; + print ''.$langs->trans('Cancel').''; + print '
'; + print '
'; + print '

'.$langs->trans('TransferWithSections').': '.$langs->trans('ConfirmTransferInvoiceWithSections').'

'; + print '

'.$langs->trans('TransferWithoutSections').': '.$langs->trans('ConfirmTransferInvoiceWithoutSections').'

'; + print '
'; + print '
'; + print '
'; } // Info banner - Produktliste zeigt immer den Auftrag, andere Tabs den Stundenzettel