Version 2.1.0: Rechnungsübernahme erweitert
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
8ea1180041
commit
dabcdbde13
5 changed files with 325 additions and 68 deletions
18
README.md
18
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)
|
||||
|
|
|
|||
|
|
@ -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 '</tr>';
|
|||
|
||||
print '</table>';
|
||||
|
||||
// Rechnungsübernahme-Einstellungen
|
||||
print '<br>';
|
||||
print '<table class="noborder centpercent">';
|
||||
print '<tr class="liste_titre">';
|
||||
print '<th class="titlefield">'.$langs->trans("InvoiceTransferSettings").'</th>';
|
||||
print '<th style="width: 300px;">'.$langs->trans("Value").'</th>';
|
||||
print '<th class="right" style="width: 100px;"></th>';
|
||||
print '</tr>';
|
||||
|
||||
// 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 '<tr class="oddeven">';
|
||||
print '<td>'.$langs->trans("InvoiceBankAccount").'<br><small class="opacitymedium">'.$langs->trans("InvoiceBankAccountDesc").'</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="setINVOICE_BANK_ACCOUNT">';
|
||||
print '<select name="bank_account_value" class="flat minwidth200">';
|
||||
print '<option value="0">'.$langs->trans("None").'</option>';
|
||||
if ($resqlBank) {
|
||||
while ($objBank = $db->fetch_object($resqlBank)) {
|
||||
$selected = ($currentBankAccount == $objBank->rowid) ? ' selected' : '';
|
||||
print '<option value="'.$objBank->rowid.'"'.$selected.'>'.dol_escape_htmltag($objBank->label.' ('.$objBank->ref.')').'</option>';
|
||||
}
|
||||
}
|
||||
print '</select>';
|
||||
print '</td>';
|
||||
print '<td class="right">';
|
||||
print '<input type="submit" class="button small" value="'.$langs->trans("Modify").'">';
|
||||
print '</form>';
|
||||
print '</td>';
|
||||
print '</tr>';
|
||||
|
||||
// Mehraufwand als Produktgruppe
|
||||
$currentMehraufwandSection = getDolGlobalString('STUNDENZETTEL_INVOICE_MEHRAUFWAND_SECTION', '1');
|
||||
print '<tr class="oddeven">';
|
||||
print '<td>'.$langs->trans("InvoiceMehraufwandSection").'<br><small class="opacitymedium">'.$langs->trans("InvoiceMehraufwandSectionDesc").'</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="setINVOICE_MEHRAUFWAND_SECTION">';
|
||||
print '<select name="mehraufwand_section_value" class="flat minwidth200">';
|
||||
print '<option value="1"'.($currentMehraufwandSection == '1' ? ' selected' : '').'>'.$langs->trans("Yes").'</option>';
|
||||
print '<option value="0"'.($currentMehraufwandSection == '0' ? ' selected' : '').'>'.$langs->trans("No").'</option>';
|
||||
print '</select>';
|
||||
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>';
|
||||
|
||||
// PWA Mobile App Bereich
|
||||
print '<br>';
|
||||
print '<table class="noborder centpercent">';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 '<div class="confirmmessage">';
|
||||
print '<div class="confirmmessagebox" style="max-width:600px; margin:10px auto; padding:20px; border:1px solid #c0c0c0; border-radius:5px; background:#f8f8f8;">';
|
||||
print '<h3 style="margin-top:0;">'.img_picto('', 'bill', 'class="pictofixedwidth"').$langs->trans('ConfirmTransferInvoiceTitle').'</h3>';
|
||||
print '<p>'.$langs->trans('ConfirmTransferInvoiceChoice').'</p>';
|
||||
print '<div style="display:flex; gap:10px; justify-content:center; flex-wrap:wrap; margin-top:15px;">';
|
||||
// Button: Mit Produktgruppen
|
||||
print '<a class="butAction" href="'.$baseUrl.'&action=confirm_transfer_invoice">';
|
||||
print img_picto('', 'object_list', 'class="pictofixedwidth"').$langs->trans('TransferWithSections');
|
||||
print '</a>';
|
||||
// Button: Ohne Produktgruppen
|
||||
print '<a class="butAction" href="'.$baseUrl.'&action=confirm_transfer_invoice_flat">';
|
||||
print img_picto('', 'object_line', 'class="pictofixedwidth"').$langs->trans('TransferWithoutSections');
|
||||
print '</a>';
|
||||
print '</div>';
|
||||
print '<div style="display:flex; gap:10px; justify-content:center; margin-top:10px;">';
|
||||
print '<a class="butActionDelete" href="'.$baseUrl.'">'.$langs->trans('Cancel').'</a>';
|
||||
print '</div>';
|
||||
print '<div style="margin-top:12px; font-size:0.9em; color:#666;">';
|
||||
print '<p style="margin:3px 0;"><strong>'.$langs->trans('TransferWithSections').':</strong> '.$langs->trans('ConfirmTransferInvoiceWithSections').'</p>';
|
||||
print '<p style="margin:3px 0;"><strong>'.$langs->trans('TransferWithoutSections').':</strong> '.$langs->trans('ConfirmTransferInvoiceWithoutSections').'</p>';
|
||||
print '</div>';
|
||||
print '</div>';
|
||||
print '</div>';
|
||||
}
|
||||
|
||||
// Info banner - Produktliste zeigt immer den Auftrag, andere Tabs den Stundenzettel
|
||||
|
|
|
|||
Loading…
Reference in a new issue