Achtung Do Actions wurde Code eingebaut aufgrund des Fehlerhaften löschens der normalen Produktzeilen
This commit is contained in:
parent
d8c77df6e4
commit
a1468d359e
15 changed files with 984 additions and 712 deletions
7
.claude/settings.json
Normal file
7
.claude/settings.json
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(grep:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -40,8 +40,8 @@ $fk_propal = ($docType === 'propal') ? (int)$facture_id : 'NULL';
|
|||
$fk_commande = ($docType === 'order') ? (int)$facture_id : 'NULL';
|
||||
|
||||
$sql = "INSERT INTO ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
$sql .= " (fk_facture, fk_propal, fk_commande, document_type, line_type, title, line_order, date_creation)";
|
||||
$sql .= " VALUES (".$fk_facture.", ".$fk_propal.", ".$fk_commande.", '".$db->escape($docType)."', 'text', '".$db->escape($text)."', ".$next_order.", NOW())";
|
||||
$sql .= " (fk_facture, fk_propal, fk_commande, document_type, line_type, title, line_order, in_facturedet, date_creation)";
|
||||
$sql .= " VALUES (".$fk_facture.", ".$fk_propal.", ".$fk_commande.", '".$db->escape($docType)."', 'text', '".$db->escape($text)."', ".$next_order.", 1, NOW())";
|
||||
|
||||
if ($db->query($sql)) {
|
||||
$new_id = $db->last_insert_id(MAIN_DB_PREFIX."facture_lines_manager");
|
||||
|
|
|
|||
|
|
@ -1,23 +1,31 @@
|
|||
<?php
|
||||
define('NOTOKENRENEWAL', 1);
|
||||
require '../../../main.inc.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
|
||||
require_once __DIR__.'/../lib/subtotaltitle.lib.php';
|
||||
require_once __DIR__.'/../class/DocumentTypeHelper.class.php';
|
||||
|
||||
global $user;
|
||||
|
||||
$section_id = GETPOST('section_id', 'int');
|
||||
$force = GETPOST('force', 'int');
|
||||
$docType = GETPOST('document_type', 'alpha');
|
||||
|
||||
subtotaltitle_debug_log('🔄 delete_section: section=' . $section_id . ', force=' . $force);
|
||||
subtotaltitle_debug_log('delete_section: section=' . $section_id . ', force=' . $force . ', docType=' . $docType);
|
||||
|
||||
if (!$section_id) {
|
||||
echo json_encode(['success' => false, 'error' => 'Missing parameters']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Hole die richtigen Tabellennamen fuer diesen Dokumenttyp
|
||||
$tables = DocumentTypeHelper::getTableNames($docType);
|
||||
if (!$tables) {
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid document type']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 1. Hole Section-Info
|
||||
$sql = "SELECT fk_facture FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
$sql = "SELECT ".$tables['fk_parent']." as doc_id FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
$sql .= " WHERE rowid = ".(int)$section_id;
|
||||
$sql .= " AND line_type = 'section'";
|
||||
$resql = $db->query($sql);
|
||||
|
|
@ -28,55 +36,61 @@ if (!$resql || $db->num_rows($resql) == 0) {
|
|||
}
|
||||
|
||||
$section = $db->fetch_object($resql);
|
||||
$facture_id = $section->fk_facture;
|
||||
$document_id = $section->doc_id;
|
||||
|
||||
// 2. Prüfe Rechnungsstatus
|
||||
$facture = new Facture($db);
|
||||
$facture->fetch($facture_id);
|
||||
// 2. Pruefe Dokumentstatus
|
||||
$object = DocumentTypeHelper::loadDocument($docType, $document_id, $db);
|
||||
if (!$object) {
|
||||
echo json_encode(['success' => false, 'error' => 'Dokument nicht gefunden']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($force && $facture->statut != Facture::STATUS_DRAFT) {
|
||||
echo json_encode(['success' => false, 'error' => 'Rechnung ist nicht im Entwurf']);
|
||||
$isDraft = DocumentTypeHelper::isDraft($object, $docType);
|
||||
if ($force && !$isDraft) {
|
||||
echo json_encode(['success' => false, 'error' => 'Dokument ist nicht im Entwurf']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// 3. Hole Produkt-IDs DIREKT aus DB
|
||||
$product_ids = [];
|
||||
$sql_products = "SELECT fk_facturedet FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
$sql_products = "SELECT ".$tables['fk_line']." as detail_id FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
$sql_products .= " WHERE parent_section = ".(int)$section_id;
|
||||
$sql_products .= " AND line_type = 'product'";
|
||||
$res_products = $db->query($sql_products);
|
||||
|
||||
while ($prod = $db->fetch_object($res_products)) {
|
||||
$product_ids[] = (int)$prod->fk_facturedet;
|
||||
if ($prod->detail_id) {
|
||||
$product_ids[] = (int)$prod->detail_id;
|
||||
}
|
||||
}
|
||||
|
||||
$product_count = count($product_ids);
|
||||
subtotaltitle_debug_log('🔍 Gefundene Produkte in Section: ' . implode(', ', $product_ids));
|
||||
subtotaltitle_debug_log('Gefundene Produkte in Section: ' . implode(', ', $product_ids));
|
||||
|
||||
$db->begin();
|
||||
|
||||
// 4. Force-Delete: Produkte aus Rechnung löschen
|
||||
// 4. Force-Delete: Produkte aus Dokument loeschen
|
||||
if ($force && $product_count > 0) {
|
||||
subtotaltitle_debug_log('🗑️ Lösche ' . $product_count . ' Zeilen aus Rechnung...');
|
||||
subtotaltitle_debug_log('Loesche ' . $product_count . ' Zeilen aus Dokument...');
|
||||
|
||||
foreach ($product_ids as $line_id) {
|
||||
$sql_del_line = "DELETE FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid = ".(int)$line_id;
|
||||
$sql_del_line = "DELETE FROM ".MAIN_DB_PREFIX.$tables['lines_table']." WHERE rowid = ".(int)$line_id;
|
||||
$res_del = $db->query($sql_del_line);
|
||||
|
||||
if ($res_del) {
|
||||
subtotaltitle_debug_log('✅ facturedet gelöscht: ' . $line_id);
|
||||
subtotaltitle_debug_log('Detail geloescht: ' . $line_id);
|
||||
} else {
|
||||
subtotaltitle_debug_log('❌ SQL Fehler: ' . $line_id . ' - ' . $db->lasterror());
|
||||
subtotaltitle_debug_log('SQL Fehler: ' . $line_id . ' - ' . $db->lasterror());
|
||||
}
|
||||
}
|
||||
|
||||
// Aus Manager-Tabelle löschen
|
||||
// Aus Manager-Tabelle loeschen
|
||||
$sql_del = "DELETE FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
$sql_del .= " WHERE parent_section = ".(int)$section_id;
|
||||
$sql_del .= " AND line_type = 'product'";
|
||||
$db->query($sql_del);
|
||||
|
||||
subtotaltitle_debug_log('🔴 Force-Delete abgeschlossen: ' . $product_count . ' Produkte');
|
||||
subtotaltitle_debug_log('Force-Delete abgeschlossen: ' . $product_count . ' Produkte');
|
||||
|
||||
} else if (!$force) {
|
||||
// Ohne force: Produkte nur freigeben
|
||||
|
|
@ -86,35 +100,34 @@ if ($force && $product_count > 0) {
|
|||
$sql .= " AND line_type = 'product'";
|
||||
$db->query($sql);
|
||||
|
||||
subtotaltitle_debug_log('🔓 ' . $product_count . ' Produkte freigegeben');
|
||||
subtotaltitle_debug_log($product_count . ' Produkte freigegeben');
|
||||
}
|
||||
|
||||
// ========== NEU: SUBTOTAL LÖSCHEN ==========
|
||||
// Hole Subtotal dieser Section (falls vorhanden)
|
||||
$sql_subtotal = "SELECT rowid, fk_facturedet FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
// ========== SUBTOTAL LOESCHEN ==========
|
||||
$sql_subtotal = "SELECT rowid, ".$tables['fk_line']." as detail_id FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
$sql_subtotal .= " WHERE parent_section = ".(int)$section_id;
|
||||
$sql_subtotal .= " AND line_type = 'subtotal'";
|
||||
$res_subtotal = $db->query($sql_subtotal);
|
||||
|
||||
if ($obj_sub = $db->fetch_object($res_subtotal)) {
|
||||
// Falls Subtotal in facturedet ist, dort auch löschen
|
||||
if ($obj_sub->fk_facturedet > 0) {
|
||||
$sql_del_fd = "DELETE FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid = ".(int)$obj_sub->fk_facturedet;
|
||||
// Falls Subtotal in Detail-Tabelle ist, dort auch loeschen
|
||||
if ($obj_sub->detail_id > 0) {
|
||||
$sql_del_fd = "DELETE FROM ".MAIN_DB_PREFIX.$tables['lines_table']." WHERE rowid = ".(int)$obj_sub->detail_id;
|
||||
$db->query($sql_del_fd);
|
||||
subtotaltitle_debug_log('✅ Subtotal aus facturedet gelöscht: ' . $obj_sub->fk_facturedet);
|
||||
subtotaltitle_debug_log('Subtotal aus Detail geloescht: ' . $obj_sub->detail_id);
|
||||
}
|
||||
|
||||
// Aus Manager-Tabelle löschen
|
||||
// Aus Manager-Tabelle loeschen
|
||||
$sql_del_sub = "DELETE FROM ".MAIN_DB_PREFIX."facture_lines_manager WHERE rowid = ".(int)$obj_sub->rowid;
|
||||
$db->query($sql_del_sub);
|
||||
subtotaltitle_debug_log('✅ Subtotal aus Manager gelöscht: ' . $obj_sub->rowid);
|
||||
subtotaltitle_debug_log('Subtotal aus Manager geloescht: ' . $obj_sub->rowid);
|
||||
}
|
||||
|
||||
// ========== VERWAISTE SUBTOTALS AUFRÄUMEN ==========
|
||||
// Finde alle Subtotals in dieser Rechnung, deren parent_section nicht mehr existiert
|
||||
$sql_orphans = "SELECT s.rowid, s.fk_facturedet, s.parent_section
|
||||
// ========== VERWAISTE SUBTOTALS AUFRAEUMEN ==========
|
||||
$sql_orphans = "SELECT s.rowid, s.".$tables['fk_line']." as detail_id, s.parent_section
|
||||
FROM ".MAIN_DB_PREFIX."facture_lines_manager s
|
||||
WHERE s.fk_facture = ".(int)$facture_id."
|
||||
WHERE s.".$tables['fk_parent']." = ".(int)$document_id."
|
||||
AND s.document_type = '".$db->escape($docType)."'
|
||||
AND s.line_type = 'subtotal'
|
||||
AND s.parent_section IS NOT NULL
|
||||
AND NOT EXISTS (
|
||||
|
|
@ -126,36 +139,33 @@ $res_orphans = $db->query($sql_orphans);
|
|||
|
||||
$orphan_count = 0;
|
||||
while ($orphan = $db->fetch_object($res_orphans)) {
|
||||
// Aus facturedet löschen (falls vorhanden)
|
||||
if ($orphan->fk_facturedet > 0) {
|
||||
$sql_del_orphan_fd = "DELETE FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid = ".(int)$orphan->fk_facturedet;
|
||||
if ($orphan->detail_id > 0) {
|
||||
$sql_del_orphan_fd = "DELETE FROM ".MAIN_DB_PREFIX.$tables['lines_table']." WHERE rowid = ".(int)$orphan->detail_id;
|
||||
$db->query($sql_del_orphan_fd);
|
||||
subtotaltitle_debug_log('🧹 Verwaistes Subtotal aus facturedet gelöscht: ' . $orphan->fk_facturedet . ' (parent_section=' . $orphan->parent_section . ')');
|
||||
subtotaltitle_debug_log('Verwaistes Subtotal aus Detail geloescht: ' . $orphan->detail_id);
|
||||
}
|
||||
|
||||
// Aus Manager-Tabelle löschen
|
||||
$sql_del_orphan = "DELETE FROM ".MAIN_DB_PREFIX."facture_lines_manager WHERE rowid = ".(int)$orphan->rowid;
|
||||
$db->query($sql_del_orphan);
|
||||
subtotaltitle_debug_log('🧹 Verwaistes Subtotal aus Manager gelöscht: ' . $orphan->rowid);
|
||||
$orphan_count++;
|
||||
}
|
||||
|
||||
if ($orphan_count > 0) {
|
||||
subtotaltitle_debug_log('🧹 Aufgeräumt: ' . $orphan_count . ' verwaiste Subtotals entfernt');
|
||||
subtotaltitle_debug_log('Aufgeraeumt: ' . $orphan_count . ' verwaiste Subtotals entfernt');
|
||||
}
|
||||
// ========== ENDE VERWAISTE SUBTOTALS ==========
|
||||
|
||||
// 5. Section selbst löschen
|
||||
// 5. Section selbst loeschen
|
||||
$sql = "DELETE FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
$sql .= " WHERE rowid = ".(int)$section_id;
|
||||
$db->query($sql);
|
||||
|
||||
// Rechnungstotale neu berechnen (nach allen Löschungen)
|
||||
$facture->update_price(1);
|
||||
// Dokumenttotale neu berechnen
|
||||
$object->update_price(1);
|
||||
|
||||
// 6. Neuordnen
|
||||
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
$sql .= " WHERE fk_facture = ".(int)$facture_id;
|
||||
$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id;
|
||||
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||
$sql .= " ORDER BY line_order";
|
||||
$resql = $db->query($sql);
|
||||
|
||||
|
|
@ -169,19 +179,22 @@ while ($obj = $db->fetch_object($resql)) {
|
|||
}
|
||||
|
||||
// 7. Sync rang
|
||||
$sql = "SELECT fk_facturedet FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
$sql .= " WHERE fk_facture = ".(int)$facture_id;
|
||||
$sql .= " AND line_type = 'product'";
|
||||
$sql = "SELECT ".$tables['fk_line']." as detail_id FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id;
|
||||
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||
$sql .= " AND ".$tables['fk_line']." IS NOT NULL";
|
||||
$sql .= " ORDER BY line_order";
|
||||
$resql = $db->query($sql);
|
||||
|
||||
$rang = 1;
|
||||
while ($obj = $db->fetch_object($resql)) {
|
||||
$sql_upd = "UPDATE ".MAIN_DB_PREFIX."facturedet";
|
||||
$sql_upd .= " SET rang = ".$rang;
|
||||
$sql_upd .= " WHERE rowid = ".(int)$obj->fk_facturedet;
|
||||
$db->query($sql_upd);
|
||||
$rang++;
|
||||
if ($obj->detail_id) {
|
||||
$sql_upd = "UPDATE ".MAIN_DB_PREFIX.$tables['lines_table'];
|
||||
$sql_upd .= " SET rang = ".$rang;
|
||||
$sql_upd .= " WHERE rowid = ".(int)$obj->detail_id;
|
||||
$db->query($sql_upd);
|
||||
$rang++;
|
||||
}
|
||||
}
|
||||
|
||||
$db->commit();
|
||||
|
|
|
|||
|
|
@ -6,20 +6,42 @@ if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../mai
|
|||
if (!$res && file_exists("../../../../main.inc.php")) $res = @include "../../../../main.inc.php";
|
||||
if (!$res) die("Include of main fails");
|
||||
require_once __DIR__.'/../lib/subtotaltitle.lib.php';
|
||||
require_once __DIR__.'/../class/DocumentTypeHelper.class.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$textline_id = GETPOST('textline_id', 'int');
|
||||
$docType = GETPOST('document_type', 'alpha');
|
||||
|
||||
subtotaltitle_debug_log('🗑️ delete_textline: id=' . $textline_id);
|
||||
// Fallback: Wenn kein docType, versuche aus der DB zu ermitteln
|
||||
if (!$docType) {
|
||||
$sql_type = "SELECT document_type FROM ".MAIN_DB_PREFIX."facture_lines_manager WHERE rowid = ".(int)$textline_id;
|
||||
$res_type = $db->query($sql_type);
|
||||
if ($res_type && $obj_type = $db->fetch_object($res_type)) {
|
||||
$docType = $obj_type->document_type;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$docType) {
|
||||
$docType = 'invoice'; // Fallback
|
||||
}
|
||||
|
||||
subtotaltitle_debug_log('delete_textline: id=' . $textline_id . ', docType=' . $docType);
|
||||
|
||||
if (!$textline_id) {
|
||||
echo json_encode(array('success' => false, 'error' => 'Missing parameters'));
|
||||
exit;
|
||||
}
|
||||
|
||||
// 1. Hole facture_id BEVOR wir löschen
|
||||
$sql = "SELECT fk_facture FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
// Hole die richtigen Tabellennamen fuer diesen Dokumenttyp
|
||||
$tables = DocumentTypeHelper::getTableNames($docType);
|
||||
if (!$tables) {
|
||||
echo json_encode(array('success' => false, 'error' => 'Invalid document type'));
|
||||
exit;
|
||||
}
|
||||
|
||||
// 1. Hole document_id BEVOR wir loeschen
|
||||
$sql = "SELECT ".$tables['fk_parent']." as doc_id FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
$sql .= " WHERE rowid = ".(int)$textline_id;
|
||||
$resql = $db->query($sql);
|
||||
|
||||
|
|
@ -29,31 +51,54 @@ if (!$resql || $db->num_rows($resql) == 0) {
|
|||
}
|
||||
|
||||
$obj = $db->fetch_object($resql);
|
||||
$facture_id = $obj->fk_facture;
|
||||
$document_id = $obj->doc_id;
|
||||
|
||||
// 2. DELETE ausführen
|
||||
$db->begin();
|
||||
|
||||
// 2. DELETE ausfuehren
|
||||
$sql = "DELETE FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
$sql .= " WHERE rowid = ".(int)$textline_id;
|
||||
$sql .= " AND line_type = 'text'";
|
||||
|
||||
if (!$db->query($sql)) {
|
||||
$db->rollback();
|
||||
echo json_encode(array('success' => false, 'error' => $db->lasterror()));
|
||||
exit;
|
||||
}
|
||||
|
||||
// 3. Lücken schließen
|
||||
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager
|
||||
WHERE fk_facture = ".(int)$facture_id."
|
||||
ORDER BY line_order";
|
||||
// 3. line_order neu durchnummerieren
|
||||
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id;
|
||||
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||
$sql .= " ORDER BY line_order";
|
||||
$resql = $db->query($sql);
|
||||
|
||||
$new_order = 1;
|
||||
while ($obj = $db->fetch_object($resql)) {
|
||||
$sql_upd = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager
|
||||
SET line_order = ".$new_order."
|
||||
WHERE rowid = ".(int)$obj->rowid;
|
||||
$sql_upd = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager SET line_order = ".$new_order." WHERE rowid = ".(int)$obj->rowid;
|
||||
$db->query($sql_upd);
|
||||
$new_order++;
|
||||
}
|
||||
|
||||
// 4. rang in Detail-Tabelle synchronisieren
|
||||
$sql = "SELECT ".$tables['fk_line']." as detail_id FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id;
|
||||
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||
$sql .= " AND ".$tables['fk_line']." IS NOT NULL";
|
||||
$sql .= " ORDER BY line_order";
|
||||
$resql = $db->query($sql);
|
||||
|
||||
$rang = 1;
|
||||
while ($obj = $db->fetch_object($resql)) {
|
||||
if ($obj->detail_id) {
|
||||
$sql_upd = "UPDATE ".MAIN_DB_PREFIX.$tables['lines_table']." SET rang = ".$rang." WHERE rowid = ".(int)$obj->detail_id;
|
||||
$db->query($sql_upd);
|
||||
$rang++;
|
||||
}
|
||||
}
|
||||
|
||||
subtotaltitle_debug_log('delete_textline: rang synchronisiert, ' . ($rang - 1) . ' Zeilen');
|
||||
|
||||
$db->commit();
|
||||
|
||||
echo json_encode(array('success' => true));
|
||||
|
|
@ -6,26 +6,44 @@ if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../mai
|
|||
if (!$res && file_exists("../../../../main.inc.php")) $res = @include "../../../../main.inc.php";
|
||||
if (!$res) die("Include of main fails");
|
||||
require_once __DIR__.'/../lib/subtotaltitle.lib.php';
|
||||
require_once __DIR__.'/../class/DocumentTypeHelper.class.php';
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$textline_id = GETPOST('textline_id', 'int');
|
||||
$text = GETPOST('text', 'restricthtml');
|
||||
|
||||
subtotaltitle_debug_log('🔄 edit_textline: id=' . $textline_id);
|
||||
subtotaltitle_debug_log('edit_textline: id=' . $textline_id);
|
||||
|
||||
if (!$textline_id || !$text) {
|
||||
echo json_encode(array('success' => false, 'error' => 'Missing parameters'));
|
||||
exit;
|
||||
}
|
||||
|
||||
// Hole erst fk_facturedet (falls Textzeile in Rechnung ist)
|
||||
$sql_get = "SELECT fk_facturedet FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
// Hole erst document_type und FK zur Detail-Tabelle
|
||||
$sql_get = "SELECT document_type, fk_facturedet, fk_propaldet, fk_commandedet FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
$sql_get .= " WHERE rowid = ".(int)$textline_id;
|
||||
$sql_get .= " AND line_type = 'text'";
|
||||
$resql = $db->query($sql_get);
|
||||
$obj = $db->fetch_object($resql);
|
||||
$fk_facturedet = $obj ? $obj->fk_facturedet : null;
|
||||
|
||||
if (!$obj) {
|
||||
echo json_encode(array('success' => false, 'error' => 'Textline not found'));
|
||||
exit;
|
||||
}
|
||||
|
||||
$docType = $obj->document_type ?: 'invoice';
|
||||
$tables = DocumentTypeHelper::getTableNames($docType);
|
||||
|
||||
// Ermittle FK zur Detail-Tabelle basierend auf Dokumenttyp
|
||||
$fk_detail = null;
|
||||
if ($docType == 'invoice' && $obj->fk_facturedet > 0) {
|
||||
$fk_detail = $obj->fk_facturedet;
|
||||
} elseif ($docType == 'propal' && $obj->fk_propaldet > 0) {
|
||||
$fk_detail = $obj->fk_propaldet;
|
||||
} elseif ($docType == 'order' && $obj->fk_commandedet > 0) {
|
||||
$fk_detail = $obj->fk_commandedet;
|
||||
}
|
||||
|
||||
// Update Manager-Tabelle
|
||||
$sql = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
|
|
@ -38,15 +56,15 @@ if (!$db->query($sql)) {
|
|||
exit;
|
||||
}
|
||||
|
||||
// Falls in facturedet vorhanden, dort auch updaten
|
||||
if ($fk_facturedet > 0) {
|
||||
$sql_fd = "UPDATE ".MAIN_DB_PREFIX."facturedet";
|
||||
// Falls in Detail-Tabelle vorhanden, dort auch updaten
|
||||
if ($fk_detail > 0 && $tables) {
|
||||
$sql_fd = "UPDATE ".MAIN_DB_PREFIX.$tables['lines_table'];
|
||||
$sql_fd .= " SET description = '".$db->escape($text)."'";
|
||||
$sql_fd .= " WHERE rowid = ".(int)$fk_facturedet;
|
||||
$sql_fd .= " WHERE rowid = ".(int)$fk_detail;
|
||||
$db->query($sql_fd);
|
||||
subtotaltitle_debug_log('✅ Textzeile + facturedet geändert');
|
||||
subtotaltitle_debug_log('Textzeile + Detail geaendert (docType='.$docType.')');
|
||||
} else {
|
||||
subtotaltitle_debug_log('✅ Textzeile geändert (nicht in facturedet)');
|
||||
subtotaltitle_debug_log('Textzeile geaendert (nicht in Detail-Tabelle)');
|
||||
}
|
||||
|
||||
echo json_encode(array('success' => true, 'synced_facturedet' => ($fk_facturedet > 0)));
|
||||
echo json_encode(array('success' => true, 'synced_detail' => ($fk_detail > 0)));
|
||||
|
|
|
|||
|
|
@ -2,28 +2,40 @@
|
|||
define('NOTOKENRENEWAL', 1);
|
||||
require '../../../main.inc.php';
|
||||
require_once __DIR__.'/../lib/subtotaltitle.lib.php';
|
||||
require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
|
||||
require_once __DIR__.'/../class/DocumentTypeHelper.class.php';
|
||||
|
||||
$line_ids_json = GETPOST('line_ids', 'alpha');
|
||||
$facture_id = GETPOST('facture_id', 'int');
|
||||
$document_id = GETPOST('facture_id', 'int'); // Kompatibilitaet: facture_id wird auch fuer andere Typen verwendet
|
||||
$docType = GETPOST('document_type', 'alpha');
|
||||
|
||||
$line_ids = json_decode($line_ids_json, true);
|
||||
|
||||
if (!is_array($line_ids) || count($line_ids) == 0 || !$facture_id) {
|
||||
if (!is_array($line_ids) || count($line_ids) == 0 || !$document_id) {
|
||||
echo json_encode(['success' => false, 'error' => 'Missing parameters']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Prüfe Rechnungsstatus
|
||||
$facture = new Facture($db);
|
||||
$facture->fetch($facture_id);
|
||||
|
||||
if ($facture->statut != Facture::STATUS_DRAFT) {
|
||||
echo json_encode(['success' => false, 'error' => 'Rechnung ist nicht im Entwurf']);
|
||||
// Hole die richtigen Tabellennamen fuer diesen Dokumenttyp
|
||||
$tables = DocumentTypeHelper::getTableNames($docType);
|
||||
if (!$tables) {
|
||||
echo json_encode(['success' => false, 'error' => 'Invalid document type']);
|
||||
exit;
|
||||
}
|
||||
|
||||
subtotaltitle_debug_log('🗑️ Massenlöschung: ' . count($line_ids) . ' Zeilen');
|
||||
// Pruefe Dokumentstatus
|
||||
$object = DocumentTypeHelper::loadDocument($docType, $document_id, $db);
|
||||
if (!$object) {
|
||||
echo json_encode(['success' => false, 'error' => 'Dokument nicht gefunden']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$isDraft = DocumentTypeHelper::isDraft($object, $docType);
|
||||
if (!$isDraft) {
|
||||
echo json_encode(['success' => false, 'error' => 'Dokument ist nicht im Entwurf']);
|
||||
exit;
|
||||
}
|
||||
|
||||
subtotaltitle_debug_log('Massenloeschung: ' . count($line_ids) . ' Zeilen, docType=' . $docType);
|
||||
|
||||
$db->begin();
|
||||
|
||||
|
|
@ -31,24 +43,25 @@ $deleted = 0;
|
|||
foreach ($line_ids as $line_id) {
|
||||
$line_id = (int)$line_id;
|
||||
|
||||
// Aus facturedet löschen
|
||||
$sql = "DELETE FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid = ".$line_id;
|
||||
// Aus Detail-Tabelle loeschen
|
||||
$sql = "DELETE FROM ".MAIN_DB_PREFIX.$tables['lines_table']." WHERE rowid = ".$line_id;
|
||||
if ($db->query($sql)) {
|
||||
$deleted++;
|
||||
subtotaltitle_debug_log('✅ Zeile gelöscht: ' . $line_id);
|
||||
subtotaltitle_debug_log('Zeile geloescht: ' . $line_id);
|
||||
}
|
||||
|
||||
// Aus Manager-Tabelle löschen
|
||||
$sql_manager = "DELETE FROM ".MAIN_DB_PREFIX."facture_lines_manager WHERE fk_facturedet = ".$line_id;
|
||||
// Aus Manager-Tabelle loeschen
|
||||
$sql_manager = "DELETE FROM ".MAIN_DB_PREFIX."facture_lines_manager WHERE ".$tables['fk_line']." = ".$line_id;
|
||||
$db->query($sql_manager);
|
||||
}
|
||||
|
||||
// Summen neu berechnen
|
||||
$facture->update_price(1);
|
||||
$object->update_price(1);
|
||||
|
||||
// line_order neu durchnummerieren
|
||||
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
$sql .= " WHERE fk_facture = ".(int)$facture_id;
|
||||
$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id;
|
||||
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||
$sql .= " ORDER BY line_order";
|
||||
$resql = $db->query($sql);
|
||||
|
||||
|
|
@ -60,21 +73,24 @@ while ($obj = $db->fetch_object($resql)) {
|
|||
}
|
||||
|
||||
// rang synchronisieren
|
||||
$sql = "SELECT fk_facturedet FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
$sql .= " WHERE fk_facture = ".(int)$facture_id;
|
||||
$sql .= " AND line_type = 'product'";
|
||||
$sql = "SELECT ".$tables['fk_line']." as detail_id FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id;
|
||||
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||
$sql .= " AND ".$tables['fk_line']." IS NOT NULL";
|
||||
$sql .= " ORDER BY line_order";
|
||||
$resql = $db->query($sql);
|
||||
|
||||
$rang = 1;
|
||||
while ($obj = $db->fetch_object($resql)) {
|
||||
$sql_upd = "UPDATE ".MAIN_DB_PREFIX."facturedet SET rang = ".$rang." WHERE rowid = ".(int)$obj->fk_facturedet;
|
||||
$db->query($sql_upd);
|
||||
$rang++;
|
||||
if ($obj->detail_id) {
|
||||
$sql_upd = "UPDATE ".MAIN_DB_PREFIX.$tables['lines_table']." SET rang = ".$rang." WHERE rowid = ".(int)$obj->detail_id;
|
||||
$db->query($sql_upd);
|
||||
$rang++;
|
||||
}
|
||||
}
|
||||
|
||||
$db->commit();
|
||||
|
||||
subtotaltitle_debug_log('🗑️ Massenlöschung abgeschlossen: ' . $deleted . ' von ' . count($line_ids));
|
||||
subtotaltitle_debug_log('Massenloeschung abgeschlossen: ' . $deleted . ' von ' . count($line_ids));
|
||||
|
||||
echo json_encode(['success' => true, 'deleted' => $deleted]);
|
||||
|
|
@ -122,4 +122,58 @@ class DocumentTypeHelper
|
|||
|
||||
return isset($contexts[$type]) ? $contexts[$type] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Laedt ein Dokument basierend auf Typ und ID
|
||||
*
|
||||
* @param string $type Dokumenttyp ('invoice', 'propal', 'order')
|
||||
* @param int $id Dokument-ID
|
||||
* @param DoliDB $db Datenbankverbindung
|
||||
* @return object|null Dolibarr Objekt oder null
|
||||
*/
|
||||
public static function loadDocument($type, $id, $db)
|
||||
{
|
||||
$object = null;
|
||||
|
||||
if ($type == 'invoice') {
|
||||
require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
|
||||
$object = new Facture($db);
|
||||
} elseif ($type == 'propal') {
|
||||
require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propal.class.php';
|
||||
$object = new Propal($db);
|
||||
} elseif ($type == 'order') {
|
||||
require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
|
||||
$object = new Commande($db);
|
||||
}
|
||||
|
||||
if ($object && $object->fetch($id) > 0) {
|
||||
return $object;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prueft ob ein Dokument im Entwurfsstatus ist
|
||||
*
|
||||
* @param object $object Dolibarr Objekt
|
||||
* @param string $type Dokumenttyp ('invoice', 'propal', 'order')
|
||||
* @return bool true wenn Entwurf, sonst false
|
||||
*/
|
||||
public static function isDraft($object, $type)
|
||||
{
|
||||
if (!$object) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verschiedene Dokumenttypen haben unterschiedliche Status-Felder
|
||||
if (isset($object->statut)) {
|
||||
return ($object->statut == 0);
|
||||
}
|
||||
if (isset($object->status)) {
|
||||
return ($object->status == 0);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,8 @@ class ActionsSubtotalTitle extends CommonHookActions
|
|||
global $db;
|
||||
$tables = DocumentTypeHelper::getTableNames($docType);
|
||||
if (!$tables) return "";
|
||||
return " WHERE ".$tableAlias.".".$tables['fk_parent']." = ".(int)$document_id." AND ".$tableAlias.".document_type = '".$db->escape($docType)."'";
|
||||
$prefix = $tableAlias ? $tableAlias."." : "";
|
||||
return " WHERE ".$prefix.$tables['fk_parent']." = ".(int)$document_id." AND ".$prefix."document_type = '".$db->escape($docType)."'";
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -141,6 +142,21 @@ class ActionsSubtotalTitle extends CommonHookActions
|
|||
// Lade Übersetzungen
|
||||
$langs->load('subtotaltitle@subtotaltitle');
|
||||
|
||||
// Prüfe ob Sections existieren (für Collapse-Buttons)
|
||||
global $db;
|
||||
$tables = DocumentTypeHelper::getTableNames($this->currentDocType);
|
||||
$hasSections = false;
|
||||
if ($tables && $object->id) {
|
||||
$sql_sec = "SELECT COUNT(*) as cnt FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
$sql_sec .= " WHERE ".$tables['fk_parent']." = ".(int)$object->id;
|
||||
$sql_sec .= " AND document_type = '".$db->escape($this->currentDocType)."'";
|
||||
$sql_sec .= " AND line_type = 'section'";
|
||||
$res_sec = $db->query($sql_sec);
|
||||
if ($res_sec && $obj_sec = $db->fetch_object($res_sec)) {
|
||||
$hasSections = ($obj_sec->cnt > 0);
|
||||
}
|
||||
}
|
||||
|
||||
// CSS
|
||||
$cssPath = dol_buildpath('/custom/subtotaltitle/css/subtotaltitle.css', 1);
|
||||
echo '<link rel="stylesheet" type="text/css" href="'.$cssPath.'">'."\n";
|
||||
|
|
@ -148,6 +164,9 @@ class ActionsSubtotalTitle extends CommonHookActions
|
|||
// Übersetzungen als JavaScript-Variablen bereitstellen
|
||||
echo '<script type="text/javascript">'."\n";
|
||||
echo 'var subtotalTitleIsDraft = '.($this->isDraft ? 'true' : 'false').';'."\n";
|
||||
echo 'var subtotalTitleHasSections = '.($hasSections ? 'true' : 'false').';'."\n";
|
||||
// Grip-Bild-Pfad für Drag&Drop (wie Dolibarr es macht)
|
||||
echo 'var subtotalTitleGripUrl = "'.DOL_URL_ROOT.'/theme/'.$conf->theme.'/img/grip.png";'."\n";
|
||||
echo 'var subtotalTitleLang = {'."\n";
|
||||
echo ' buttonCreateTextline: '.json_encode($langs->trans('ButtonCreateTextline')).','."\n";
|
||||
echo ' buttonToInvoice: '.json_encode($langs->trans('ButtonToInvoice')).','."\n";
|
||||
|
|
@ -230,14 +249,14 @@ class ActionsSubtotalTitle extends CommonHookActions
|
|||
// Sync-Buttons + Collapse-Buttons - rechts ausgerichtet
|
||||
echo '<script>$(document).ready(function() {';
|
||||
echo ' if ($(".sync-collapse-row").length === 0) {';
|
||||
echo ' var hasCollapse = $("tr.section-header").length > 0;';
|
||||
echo ' var buttons = \'<div class="sync-collapse-row" style="text-align:right; margin:5px 0;">\';';
|
||||
echo ' buttons += \'<a class="button" href="#" onclick="syncAllToFacturedet(); return false;" style="margin-left:5px;">\' + subtotalTitleLang.buttonToInvoice + \'</a>\';';
|
||||
echo ' buttons += \'<a class="button" href="#" onclick="removeAllFromFacturedet(); return false;" style="margin-left:5px;">\' + subtotalTitleLang.buttonFromInvoice + \'</a>\';';
|
||||
echo ' if (hasCollapse) {';
|
||||
echo ' buttons += \'<a class="button" href="#" onclick="expandAllSections(); return false;" style="margin-left:5px;">\' + subtotalTitleLang.buttonExpandAll + \'</a>\';';
|
||||
echo ' buttons += \'<a class="button" href="#" onclick="collapseAllSections(); return false;" style="margin-left:5px;">\' + subtotalTitleLang.buttonCollapseAll + \'</a>\';';
|
||||
echo ' }';
|
||||
// Collapse-Buttons nur wenn Sections existieren (aus PHP)
|
||||
if ($hasSections) {
|
||||
echo ' buttons += \'<a class="button" href="#" onclick="expandAllSections(); return false;" style="margin-left:5px;">\' + subtotalTitleLang.buttonExpandAll + \'</a>\';';
|
||||
echo ' buttons += \'<a class="button" href="#" onclick="collapseAllSections(); return false;" style="margin-left:5px;">\' + subtotalTitleLang.buttonCollapseAll + \'</a>\';';
|
||||
}
|
||||
echo ' buttons += \'</div>\';';
|
||||
echo ' $(".tabsAction").first().after(buttons);';
|
||||
echo ' }';
|
||||
|
|
@ -261,9 +280,68 @@ class ActionsSubtotalTitle extends CommonHookActions
|
|||
|
||||
/**
|
||||
* Overload the doActions function
|
||||
* Reagiert auf Lösch-Aktionen um die Manager-Tabelle zu aktualisieren
|
||||
*/
|
||||
public function doActions($parameters, &$object, &$action, $hookmanager)
|
||||
{
|
||||
global $db, $conf;
|
||||
|
||||
// Reagiere auf Zeilen-Löschung
|
||||
if ($action == 'confirm_deleteline' && !empty($object->id)) {
|
||||
$lineid = GETPOST('lineid', 'int');
|
||||
|
||||
if ($lineid > 0) {
|
||||
// Bestimme Dokumenttyp
|
||||
$docType = 'invoice';
|
||||
if (get_class($object) == 'Propal') {
|
||||
$docType = 'propal';
|
||||
} elseif (get_class($object) == 'Commande') {
|
||||
$docType = 'order';
|
||||
}
|
||||
|
||||
require_once __DIR__.'/DocumentTypeHelper.class.php';
|
||||
$tables = DocumentTypeHelper::getTableNames($docType);
|
||||
|
||||
if ($tables) {
|
||||
// Lösche aus Manager-Tabelle
|
||||
$sql = "DELETE FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
$sql .= " WHERE ".$tables['fk_line']." = ".(int)$lineid;
|
||||
$db->query($sql);
|
||||
|
||||
// Nummeriere line_order neu durch
|
||||
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$object->id;
|
||||
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||
$sql .= " ORDER BY line_order";
|
||||
$resql = $db->query($sql);
|
||||
|
||||
$new_order = 1;
|
||||
while ($obj = $db->fetch_object($resql)) {
|
||||
$sql_upd = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager SET line_order = ".$new_order." WHERE rowid = ".(int)$obj->rowid;
|
||||
$db->query($sql_upd);
|
||||
$new_order++;
|
||||
}
|
||||
|
||||
// Synchronisiere rang in Detail-Tabelle
|
||||
$sql = "SELECT rowid, ".$tables['fk_line']." as detail_id FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$object->id;
|
||||
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||
$sql .= " AND ".$tables['fk_line']." IS NOT NULL";
|
||||
$sql .= " ORDER BY line_order";
|
||||
$resql = $db->query($sql);
|
||||
|
||||
$rang = 1;
|
||||
while ($obj = $db->fetch_object($resql)) {
|
||||
if ($obj->detail_id) {
|
||||
$sql_upd = "UPDATE ".MAIN_DB_PREFIX.$tables['lines_table']." SET rang = ".$rang." WHERE rowid = ".(int)$obj->detail_id;
|
||||
$db->query($sql_upd);
|
||||
$rang++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -626,60 +704,55 @@ class ActionsSubtotalTitle extends CommonHookActions
|
|||
}
|
||||
}
|
||||
|
||||
// Hole ALLE Sections ZWISCHEN letztem rang und aktuellem rang
|
||||
$sql = "SELECT DISTINCT s.rowid, s.title, s.show_subtotal, s.collapsed, s.line_order, s.in_facturedet,";
|
||||
$sql .= " (SELECT MIN(fd.rang) FROM ".MAIN_DB_PREFIX."facture_lines_manager m2";
|
||||
$sql .= " INNER JOIN ".MAIN_DB_PREFIX.$tables['lines_table']." fd ON fd.rowid = m2.".$tables['fk_line'];
|
||||
$sql .= " WHERE m2.parent_section = s.rowid AND m2.document_type = '".$db->escape($docType)."') as first_product_rang";
|
||||
$sql .= " FROM ".MAIN_DB_PREFIX."facture_lines_manager s";
|
||||
$sql .= $this->getDocumentWhere($document_id, $docType, 's');
|
||||
$sql .= " AND s.line_type = 'section'";
|
||||
$sql .= " HAVING first_product_rang > ".(int)$last_rang[$doc_key];
|
||||
$sql .= " AND first_product_rang <= ".(int)$current_rang;
|
||||
$sql .= " ORDER BY first_product_rang";
|
||||
$resql = $db->query($sql);
|
||||
// Hole ALLE Sections und Textzeilen die VOR dieser Produktzeile kommen
|
||||
// Kombiniert nach line_order sortiert, damit Textzeilen VOR Sections erscheinen können
|
||||
$sql_combined = "SELECT rowid, title, line_type, line_order, show_subtotal, collapsed, in_facturedet,";
|
||||
$sql_combined .= " (SELECT MIN(fd.rang) FROM ".MAIN_DB_PREFIX."facture_lines_manager m2";
|
||||
$sql_combined .= " INNER JOIN ".MAIN_DB_PREFIX.$tables['lines_table']." fd ON fd.rowid = m2.".$tables['fk_line'];
|
||||
$sql_combined .= " WHERE m2.parent_section = ".MAIN_DB_PREFIX."facture_lines_manager.rowid";
|
||||
$sql_combined .= " AND m2.document_type = '".$db->escape($docType)."') as first_product_rang";
|
||||
$sql_combined .= " FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
$sql_combined .= $this->getDocumentWhere($document_id, $docType, '');
|
||||
$sql_combined .= " AND (line_type = 'section' OR line_type = 'text')";
|
||||
$sql_combined .= " AND line_order < ".(int)$current_line_order;
|
||||
$sql_combined .= " ORDER BY line_order";
|
||||
$resql_combined = $db->query($sql_combined);
|
||||
|
||||
while ($obj = $db->fetch_object($resql)) {
|
||||
if (!in_array($obj->rowid, self::$rendered_sections[$doc_key])) {
|
||||
$section = array(
|
||||
'section_id' => $obj->rowid,
|
||||
'title' => $obj->title,
|
||||
'show_subtotal' => $obj->show_subtotal,
|
||||
'collapsed' => $obj->collapsed,
|
||||
'in_facturedet' => $obj->in_facturedet
|
||||
);
|
||||
echo $this->renderSectionHeader($section);
|
||||
self::$rendered_sections[$doc_key][] = $obj->rowid;
|
||||
while ($obj = $db->fetch_object($resql_combined)) {
|
||||
if ($obj->line_type == 'section') {
|
||||
// Section nur rendern wenn first_product_rang passt
|
||||
if ($obj->first_product_rang > (int)$last_rang[$doc_key] && $obj->first_product_rang <= (int)$current_rang) {
|
||||
if (!in_array($obj->rowid, self::$rendered_sections[$doc_key])) {
|
||||
$section = array(
|
||||
'section_id' => $obj->rowid,
|
||||
'title' => $obj->title,
|
||||
'show_subtotal' => $obj->show_subtotal,
|
||||
'collapsed' => $obj->collapsed,
|
||||
'in_facturedet' => $obj->in_facturedet
|
||||
);
|
||||
echo $this->renderSectionHeader($section);
|
||||
self::$rendered_sections[$doc_key][] = $obj->rowid;
|
||||
|
||||
if ($this->debug) {
|
||||
error_log('[SubtotalTitle] ✅ Section "'.$obj->title.'" gerendert vor rang '.$current_rang);
|
||||
if ($this->debug) {
|
||||
error_log('[SubtotalTitle] ✅ Section "'.$obj->title.'" gerendert vor rang '.$current_rang);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rendere Textzeilen die VOR dieser Produktzeile kommen
|
||||
$sql_text = "SELECT rowid, title, line_order, in_facturedet FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||
$sql_text .= $this->getDocumentWhere($document_id, $docType);
|
||||
$sql_text .= " AND line_type = 'text'";
|
||||
$sql_text .= " AND line_order < ".(int)$current_line_order;
|
||||
$sql_text .= " ORDER BY line_order";
|
||||
$resql_text = $db->query($sql_text);
|
||||
|
||||
if ($resql_text) {
|
||||
while ($obj_text = $db->fetch_object($resql_text)) {
|
||||
$text_key = 'text_'.$obj_text->rowid;
|
||||
} elseif ($obj->line_type == 'text') {
|
||||
// Textzeile rendern
|
||||
$text_key = 'text_'.$obj->rowid;
|
||||
if (!in_array($text_key, self::$rendered_sections[$doc_key])) {
|
||||
$textline = array(
|
||||
'id' => $obj_text->rowid,
|
||||
'title' => $obj_text->title,
|
||||
'line_order' => $obj_text->line_order,
|
||||
'in_facturedet' => $obj_text->in_facturedet
|
||||
'id' => $obj->rowid,
|
||||
'title' => $obj->title,
|
||||
'line_order' => $obj->line_order,
|
||||
'in_facturedet' => $obj->in_facturedet
|
||||
);
|
||||
echo $this->renderTextLine($textline);
|
||||
self::$rendered_sections[$doc_key][] = $text_key;
|
||||
|
||||
if ($this->debug) {
|
||||
error_log('[SubtotalTitle] ✅ Textzeile "'.$obj_text->title.'" gerendert');
|
||||
error_log('[SubtotalTitle] ✅ Textzeile "'.$obj->title.'" gerendert');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -753,19 +826,23 @@ class ActionsSubtotalTitle extends CommonHookActions
|
|||
|
||||
$html = '<tr class="textline-row drag'.$in_class.'" data-textline-id="'.$textline['id'].'" data-line-order="'.$textline['line_order'].'">';
|
||||
|
||||
// Inhalt (colspan=10)
|
||||
$html .= '<td colspan="10" style="padding:8px; font-weight:bold;">';
|
||||
// Titel (colspan=6) - wie bei Sections
|
||||
$html .= '<td colspan="6" style="padding:8px; font-weight:bold;">';
|
||||
$html .= htmlspecialchars($textline['title']);
|
||||
$html .= '</td>';
|
||||
|
||||
// Sync-Checkbox (NEU!) - nur im Entwurfsstatus
|
||||
// Sync-Checkbox (colspan=2) - wie bei Sections
|
||||
$html .= '<td colspan="2" align="right">';
|
||||
if ($this->isDraft) {
|
||||
$html .= ' <label style="margin-left:15px;font-weight:normal;font-size:12px;" title="In Rechnung/PDF anzeigen">';
|
||||
$html .= '<label style="font-weight:normal;font-size:12px;" title="In Rechnung/PDF anzeigen">';
|
||||
$html .= '<input type="checkbox" class="sync-checkbox" data-line-id="'.$textline['id'].'" data-line-type="text" '.$sync_checked.' onclick="toggleFacturedetSync('.$textline['id'].', \'text\', this);">';
|
||||
$html .= ' 📄</label>';
|
||||
}
|
||||
|
||||
$html .= '</td>';
|
||||
|
||||
// Leer (colspan=2) - Platzhalter wie bei Sections für Move-Buttons
|
||||
$html .= '<td colspan="2"></td>';
|
||||
|
||||
// Edit (Spalte 11) - nur im Entwurfsstatus
|
||||
$html .= '<td class="linecoledit center">';
|
||||
if ($this->isDraft) {
|
||||
|
|
|
|||
|
|
@ -78,8 +78,11 @@ tr.textline-row {
|
|||
|
||||
tr.textline-row .linecolmove {
|
||||
cursor: move;
|
||||
min-width: 20px;
|
||||
}
|
||||
|
||||
/* Drag-Handle wird über JavaScript als background-image gesetzt (wie Dolibarr) */
|
||||
|
||||
tr.textline-row:hover {
|
||||
background-color: #f5f5f5 !important;
|
||||
}
|
||||
|
|
|
|||
BIN
img/grip.png
Normal file
BIN
img/grip.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 90 B |
|
|
@ -7,6 +7,185 @@ function debugLog(message) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt einen Dolibarr-styled Bestätigungsdialog
|
||||
* @param {string} title - Dialogtitel
|
||||
* @param {string} content - Dialoginhalt (HTML erlaubt)
|
||||
* @param {function} onConfirm - Callback bei Bestätigung
|
||||
* @param {string} confirmLabel - Text für Bestätigen-Button (optional)
|
||||
* @param {string} cancelLabel - Text für Abbrechen-Button (optional)
|
||||
*/
|
||||
function showConfirmDialog(title, content, onConfirm, confirmLabel, cancelLabel) {
|
||||
confirmLabel = confirmLabel || 'Ja';
|
||||
cancelLabel = cancelLabel || 'Abbrechen';
|
||||
|
||||
var dialogId = 'subtotal-confirm-dialog-' + Date.now();
|
||||
|
||||
// Entferne vorherige Dialoge
|
||||
$('.subtotal-confirm-dialog').remove();
|
||||
|
||||
var $dialog = $('<div/>', {
|
||||
id: dialogId,
|
||||
'class': 'subtotal-confirm-dialog',
|
||||
title: title
|
||||
}).appendTo('body');
|
||||
|
||||
$dialog.html(content);
|
||||
|
||||
$dialog.dialog({
|
||||
autoOpen: true,
|
||||
modal: true,
|
||||
width: 'auto',
|
||||
minWidth: 350,
|
||||
dialogClass: 'confirm-dialog-box',
|
||||
buttons: [
|
||||
{
|
||||
text: confirmLabel,
|
||||
'class': 'button-delete',
|
||||
style: 'background:#c00;color:#fff;',
|
||||
click: function() {
|
||||
$(this).dialog('close');
|
||||
if (typeof onConfirm === 'function') {
|
||||
onConfirm();
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
text: cancelLabel,
|
||||
'class': 'button-cancel',
|
||||
click: function() {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
],
|
||||
close: function() {
|
||||
$(this).dialog('destroy').remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt eine Dolibarr-styled Fehlermeldung
|
||||
* @param {string} message - Fehlermeldung
|
||||
*/
|
||||
function showErrorAlert(message) {
|
||||
var dialogId = 'subtotal-error-dialog-' + Date.now();
|
||||
|
||||
$('.subtotal-error-dialog').remove();
|
||||
|
||||
var $dialog = $('<div/>', {
|
||||
id: dialogId,
|
||||
'class': 'subtotal-error-dialog',
|
||||
title: 'Fehler'
|
||||
}).appendTo('body');
|
||||
|
||||
$dialog.html('<div class="error" style="padding:10px;">' + message + '</div>');
|
||||
|
||||
$dialog.dialog({
|
||||
autoOpen: true,
|
||||
modal: true,
|
||||
width: 'auto',
|
||||
minWidth: 300,
|
||||
dialogClass: 'confirm-dialog-box',
|
||||
buttons: [
|
||||
{
|
||||
text: 'OK',
|
||||
click: function() {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
],
|
||||
close: function() {
|
||||
$(this).dialog('destroy').remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt einen Dolibarr-styled Eingabedialog
|
||||
* @param {string} title - Dialogtitel
|
||||
* @param {string} label - Label für das Eingabefeld
|
||||
* @param {string} defaultValue - Vorausgefüllter Wert (optional)
|
||||
* @param {function} onConfirm - Callback bei Bestätigung, erhält den eingegebenen Wert
|
||||
* @param {string} confirmLabel - Text für Bestätigen-Button (optional)
|
||||
* @param {string} cancelLabel - Text für Abbrechen-Button (optional)
|
||||
*/
|
||||
function showInputDialog(title, label, defaultValue, onConfirm, confirmLabel, cancelLabel) {
|
||||
confirmLabel = confirmLabel || 'OK';
|
||||
cancelLabel = cancelLabel || 'Abbrechen';
|
||||
defaultValue = defaultValue || '';
|
||||
|
||||
var dialogId = 'subtotal-input-dialog-' + Date.now();
|
||||
var inputId = 'subtotal-input-' + Date.now();
|
||||
|
||||
// Entferne vorherige Dialoge
|
||||
$('.subtotal-input-dialog').remove();
|
||||
|
||||
var $dialog = $('<div/>', {
|
||||
id: dialogId,
|
||||
'class': 'subtotal-input-dialog',
|
||||
title: title
|
||||
}).appendTo('body');
|
||||
|
||||
var content = '<div style="padding:10px 0;">';
|
||||
content += '<label for="' + inputId + '" style="display:block;margin-bottom:8px;">' + label + '</label>';
|
||||
content += '<input type="text" id="' + inputId + '" class="flat minwidth300" style="width:100%;padding:8px;" value="' + defaultValue.replace(/"/g, '"') + '">';
|
||||
content += '</div>';
|
||||
|
||||
$dialog.html(content);
|
||||
|
||||
$dialog.dialog({
|
||||
autoOpen: true,
|
||||
modal: true,
|
||||
width: 400,
|
||||
dialogClass: 'confirm-dialog-box',
|
||||
open: function() {
|
||||
// Fokus auf Eingabefeld setzen
|
||||
var $input = $('#' + inputId);
|
||||
$input.focus().select();
|
||||
|
||||
// Enter-Taste zum Bestätigen
|
||||
$input.on('keypress', function(e) {
|
||||
if (e.which === 13) {
|
||||
var value = $(this).val().trim();
|
||||
if (value) {
|
||||
$dialog.dialog('close');
|
||||
if (typeof onConfirm === 'function') {
|
||||
onConfirm(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
text: confirmLabel,
|
||||
'class': 'button-save',
|
||||
style: 'background:#0077b3;color:#fff;',
|
||||
click: function() {
|
||||
var value = $('#' + inputId).val().trim();
|
||||
if (value) {
|
||||
$(this).dialog('close');
|
||||
if (typeof onConfirm === 'function') {
|
||||
onConfirm(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
text: cancelLabel,
|
||||
'class': 'button-cancel',
|
||||
click: function() {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
],
|
||||
close: function() {
|
||||
$(this).dialog('destroy').remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Seite neu laden ohne POST-Warnung (GET-Request)
|
||||
*/
|
||||
|
|
@ -101,32 +280,39 @@ function getFactureId() {
|
|||
*/
|
||||
function createNewSection() {
|
||||
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
||||
var title = prompt(lang.sectionName || 'Neue Positionsgruppe - Name eingeben:');
|
||||
if (!title) return;
|
||||
|
||||
var docInfo = getDocumentInfo();
|
||||
if (!docInfo.id) {
|
||||
alert(lang.errorLoadingSections || 'Fehler: Keine Dokument-ID gefunden');
|
||||
showErrorAlert(lang.errorLoadingSections || 'Fehler: Keine Dokument-ID gefunden');
|
||||
return;
|
||||
}
|
||||
|
||||
debugLog('Erstelle Section: ' + title + ' für ' + docInfo.type + ' ID ' + docInfo.id);
|
||||
showInputDialog(
|
||||
lang.sectionCreate || 'Positionsgruppe erstellen',
|
||||
lang.sectionName || 'Name der Positionsgruppe:',
|
||||
'',
|
||||
function(title) {
|
||||
debugLog('Erstelle Section: ' + title + ' für ' + docInfo.type + ' ID ' + docInfo.id);
|
||||
|
||||
$.post('/dolibarr/custom/subtotaltitle/ajax/create_section.php', {
|
||||
facture_id: docInfo.id,
|
||||
document_type: docInfo.type,
|
||||
title: title
|
||||
}, function(response) {
|
||||
debugLog('Section erstellt: ' + JSON.stringify(response));
|
||||
if (response.success) {
|
||||
window.location.href = window.location.pathname + window.location.search;
|
||||
} else {
|
||||
alert((lang.errorSavingSection || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||
}
|
||||
}, 'json').fail(function(xhr, status, error) {
|
||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||
alert((lang.errorSavingSection || 'Fehler beim Erstellen') + ': ' + error);
|
||||
});
|
||||
$.post('/dolibarr/custom/subtotaltitle/ajax/create_section.php', {
|
||||
facture_id: docInfo.id,
|
||||
document_type: docInfo.type,
|
||||
title: title
|
||||
}, function(response) {
|
||||
debugLog('Section erstellt: ' + JSON.stringify(response));
|
||||
if (response.success) {
|
||||
window.location.href = window.location.pathname + window.location.search;
|
||||
} else {
|
||||
showErrorAlert((lang.errorSavingSection || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||
}
|
||||
}, 'json').fail(function(xhr, status, error) {
|
||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||
showErrorAlert((lang.errorSavingSection || 'Fehler beim Erstellen') + ': ' + error);
|
||||
});
|
||||
},
|
||||
lang.buttonSave || 'Erstellen',
|
||||
lang.buttonCancel || 'Abbrechen'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -163,25 +349,32 @@ function moveSection(sectionId, direction) {
|
|||
*/
|
||||
function renameSection(sectionId, currentTitle) {
|
||||
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
||||
var newTitle = prompt(lang.sectionName || 'Positionsgruppe umbenennen:', currentTitle);
|
||||
if (!newTitle) return;
|
||||
|
||||
debugLog('✏️ Benenne Section ' + sectionId + ' um zu: ' + newTitle);
|
||||
showInputDialog(
|
||||
lang.buttonEdit || 'Positionsgruppe umbenennen',
|
||||
lang.sectionName || 'Name der Positionsgruppe:',
|
||||
currentTitle || '',
|
||||
function(newTitle) {
|
||||
debugLog('✏️ Benenne Section ' + sectionId + ' um zu: ' + newTitle);
|
||||
|
||||
$.post('/dolibarr/custom/subtotaltitle/ajax/rename_section.php', {
|
||||
section_id: sectionId,
|
||||
title: newTitle
|
||||
}, function(response) {
|
||||
debugLog('Rename response: ' + JSON.stringify(response));
|
||||
if (response.success) {
|
||||
window.location.href = window.location.pathname + window.location.search;
|
||||
} else {
|
||||
alert((lang.errorSavingSection || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||
}
|
||||
}, 'json').fail(function(xhr, status, error) {
|
||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||
alert((lang.errorSavingSection || 'Fehler beim Umbenennen') + ': ' + error);
|
||||
});
|
||||
$.post('/dolibarr/custom/subtotaltitle/ajax/rename_section.php', {
|
||||
section_id: sectionId,
|
||||
title: newTitle
|
||||
}, function(response) {
|
||||
debugLog('Rename response: ' + JSON.stringify(response));
|
||||
if (response.success) {
|
||||
window.location.href = window.location.pathname + window.location.search;
|
||||
} else {
|
||||
showErrorAlert((lang.errorSavingSection || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||
}
|
||||
}, 'json').fail(function(xhr, status, error) {
|
||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||
showErrorAlert((lang.errorSavingSection || 'Fehler beim Umbenennen') + ': ' + error);
|
||||
});
|
||||
},
|
||||
lang.buttonSave || 'Speichern',
|
||||
lang.buttonCancel || 'Abbrechen'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -189,26 +382,32 @@ function renameSection(sectionId, currentTitle) {
|
|||
*/
|
||||
function deleteSection(sectionId) {
|
||||
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
||||
if (!confirm(lang.confirmDeleteSection || 'Leere Positionsgruppe löschen?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
debugLog('🗑️ Lösche leere Section ' + sectionId);
|
||||
showConfirmDialog(
|
||||
'Positionsgruppe löschen',
|
||||
lang.confirmDeleteSection || 'Leere Positionsgruppe löschen?',
|
||||
function() {
|
||||
debugLog('🗑️ Lösche leere Section ' + sectionId);
|
||||
|
||||
$.post('/dolibarr/custom/subtotaltitle/ajax/delete_section.php', {
|
||||
section_id: sectionId,
|
||||
force: 0 // Nur leere löschen
|
||||
}, function(response) {
|
||||
debugLog('Delete response: ' + JSON.stringify(response));
|
||||
if (response.success) {
|
||||
window.location.href = window.location.pathname + window.location.search;
|
||||
} else {
|
||||
alert((lang.errorDeletingSection || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||
}
|
||||
}, 'json').fail(function(xhr, status, error) {
|
||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||
alert((lang.errorDeletingSection || 'Fehler beim Löschen') + ': ' + error);
|
||||
});
|
||||
$.post('/dolibarr/custom/subtotaltitle/ajax/delete_section.php', {
|
||||
section_id: sectionId,
|
||||
force: 0,
|
||||
document_type: getDocumentType()
|
||||
}, function(response) {
|
||||
debugLog('Delete response: ' + JSON.stringify(response));
|
||||
if (response.success) {
|
||||
window.location.href = window.location.pathname + window.location.search;
|
||||
} else {
|
||||
showErrorAlert((lang.errorDeletingSection || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||
}
|
||||
}, 'json').fail(function(xhr, status, error) {
|
||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||
showErrorAlert((lang.errorDeletingSection || 'Fehler beim Löschen') + ': ' + error);
|
||||
});
|
||||
},
|
||||
'Ja, löschen',
|
||||
'Abbrechen'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -220,33 +419,44 @@ function deleteSectionForce(sectionId) {
|
|||
var productCount = $row.data('product-count');
|
||||
var productIds = $row.data('product-ids') || [];
|
||||
|
||||
var msg1 = (lang.confirmDeleteSectionForce || '⚠️ ACHTUNG!\n\nWollen Sie wirklich die Positionsgruppe\nUND alle %s enthaltenen Produkte löschen?\n\nDiese Aktion kann nicht rückgängig gemacht werden!').replace('%s', productCount);
|
||||
if (!confirm(msg1)) {
|
||||
return;
|
||||
}
|
||||
var msg1 = (lang.confirmDeleteSectionForce || 'Wollen Sie wirklich die Positionsgruppe UND alle %s enthaltenen Produkte löschen?').replace('%s', productCount);
|
||||
|
||||
var msg2 = (lang.confirmDeleteSectionForce2 || 'Sind Sie WIRKLICH sicher?\n\n%s Produkte werden unwiderruflich gelöscht!').replace('%s', productCount);
|
||||
if (!confirm(msg2)) {
|
||||
return;
|
||||
}
|
||||
showConfirmDialog(
|
||||
'Achtung - Positionsgruppe löschen',
|
||||
'<div style="color:#c00;"><strong>WARNUNG!</strong></div><br>' + msg1 + '<br><br><em>Diese Aktion kann nicht rückgängig gemacht werden!</em>',
|
||||
function() {
|
||||
var msg2 = (lang.confirmDeleteSectionForce2 || 'Sind Sie WIRKLICH sicher? %s Produkte werden unwiderruflich gelöscht!').replace('%s', productCount);
|
||||
|
||||
debugLog('🔴 Force-Delete Section ' + sectionId + ' mit Produkten: ' + JSON.stringify(productIds));
|
||||
showConfirmDialog(
|
||||
'Letzte Warnung',
|
||||
'<div style="color:#c00;font-weight:bold;">' + msg2 + '</div>',
|
||||
function() {
|
||||
debugLog('Force-Delete Section ' + sectionId + ' mit Produkten: ' + JSON.stringify(productIds));
|
||||
|
||||
$.post('/dolibarr/custom/subtotaltitle/ajax/delete_section.php', {
|
||||
section_id: sectionId,
|
||||
force: 1,
|
||||
product_ids: JSON.stringify(productIds)
|
||||
}, function(response) {
|
||||
debugLog('Force-Delete response: ' + JSON.stringify(response));
|
||||
if (response.success) {
|
||||
window.location.href = window.location.pathname + window.location.search;
|
||||
} else {
|
||||
alert((lang.errorDeletingSection || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||
}
|
||||
}, 'json').fail(function(xhr, status, error) {
|
||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||
alert((lang.errorDeletingSection || 'Fehler beim Löschen') + ': ' + error);
|
||||
});
|
||||
$.post('/dolibarr/custom/subtotaltitle/ajax/delete_section.php', {
|
||||
section_id: sectionId,
|
||||
force: 1,
|
||||
product_ids: JSON.stringify(productIds),
|
||||
document_type: getDocumentType()
|
||||
}, function(response) {
|
||||
debugLog('Force-Delete response: ' + JSON.stringify(response));
|
||||
if (response.success) {
|
||||
window.location.href = window.location.pathname + window.location.search;
|
||||
} else {
|
||||
showErrorAlert((lang.errorDeletingSection || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||
}
|
||||
}, 'json').fail(function(xhr, status, error) {
|
||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||
showErrorAlert((lang.errorDeletingSection || 'Fehler beim Löschen') + ': ' + error);
|
||||
});
|
||||
},
|
||||
'Endgültig löschen',
|
||||
'Abbrechen'
|
||||
);
|
||||
},
|
||||
'Ja, löschen',
|
||||
'Abbrechen'
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Fügt leere Sections an die richtige Stelle in der Tabelle ein
|
||||
|
|
@ -971,6 +1181,16 @@ function reinitTableDnD() {
|
|||
var $table = $('#tablelines');
|
||||
if ($table.length && typeof $.fn.tableDnD !== 'undefined') {
|
||||
|
||||
// Grip-Hintergrundbild für alle tdlineupdown Elemente setzen (wie Dolibarr es macht)
|
||||
// Das ist nötig für dynamisch hinzugefügte Zeilen
|
||||
// Verwende die von PHP bereitgestellte URL (identisch zu Dolibarr's ajaxrow.tpl.php)
|
||||
if (typeof subtotalTitleGripUrl !== 'undefined') {
|
||||
$(".tdlineupdown").css("background-image", 'url(' + subtotalTitleGripUrl + ')');
|
||||
$(".tdlineupdown").css("background-repeat", "no-repeat");
|
||||
$(".tdlineupdown").css("background-position", "center center");
|
||||
debugLog('🖼️ Grip-Bild gesetzt: ' + subtotalTitleGripUrl);
|
||||
}
|
||||
|
||||
// Neu initialisieren
|
||||
$table.tableDnD({
|
||||
onDragClass: 'myDragClass',
|
||||
|
|
@ -990,6 +1210,12 @@ function reinitTableDnD() {
|
|||
}
|
||||
});
|
||||
|
||||
// Hover-Effekt für Drag-Handle (wie Dolibarr es macht)
|
||||
$(".tdlineupdown").off("mouseenter mouseleave").hover(
|
||||
function() { $(this).addClass('showDragHandle'); },
|
||||
function() { $(this).removeClass('showDragHandle'); }
|
||||
);
|
||||
|
||||
debugLog('✅ tableDnD neu initialisiert');
|
||||
}
|
||||
}
|
||||
|
|
@ -1063,32 +1289,39 @@ function insertTextLine(textline) {
|
|||
*/
|
||||
function createTextLine() {
|
||||
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
||||
var text = prompt(lang.textlineContent || 'Text eingeben:');
|
||||
if (!text) return;
|
||||
|
||||
var docInfo = getDocumentInfo();
|
||||
if (!docInfo.id) {
|
||||
alert(lang.errorLoadingSections || 'Fehler: Keine Dokument-ID gefunden');
|
||||
showErrorAlert(lang.errorLoadingSections || 'Fehler: Keine Dokument-ID gefunden');
|
||||
return;
|
||||
}
|
||||
|
||||
debugLog('Erstelle Textzeile für ' + docInfo.type + ' ID ' + docInfo.id);
|
||||
showInputDialog(
|
||||
lang.buttonCreateTextline || 'Textzeile erstellen',
|
||||
lang.textlineContent || 'Text eingeben:',
|
||||
'',
|
||||
function(text) {
|
||||
debugLog('Erstelle Textzeile für ' + docInfo.type + ' ID ' + docInfo.id);
|
||||
|
||||
$.post('/dolibarr/custom/subtotaltitle/ajax/create_textline.php', {
|
||||
facture_id: docInfo.id,
|
||||
document_type: docInfo.type,
|
||||
text: text
|
||||
}, function(response) {
|
||||
debugLog('Textzeile erstellt: ' + JSON.stringify(response));
|
||||
if (response.success) {
|
||||
window.location.href = window.location.pathname + window.location.search;
|
||||
} else {
|
||||
alert((lang.errorSavingTextline || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||
}
|
||||
}, 'json').fail(function(xhr, status, error) {
|
||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||
alert((lang.errorSavingTextline || 'Fehler beim Erstellen') + ': ' + error);
|
||||
});
|
||||
$.post('/dolibarr/custom/subtotaltitle/ajax/create_textline.php', {
|
||||
facture_id: docInfo.id,
|
||||
document_type: docInfo.type,
|
||||
text: text
|
||||
}, function(response) {
|
||||
debugLog('Textzeile erstellt: ' + JSON.stringify(response));
|
||||
if (response.success) {
|
||||
window.location.href = window.location.pathname + window.location.search;
|
||||
} else {
|
||||
showErrorAlert((lang.errorSavingTextline || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||
}
|
||||
}, 'json').fail(function(xhr, status, error) {
|
||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||
showErrorAlert((lang.errorSavingTextline || 'Fehler beim Erstellen') + ': ' + error);
|
||||
});
|
||||
},
|
||||
lang.buttonSave || 'Erstellen',
|
||||
lang.buttonCancel || 'Abbrechen'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1096,25 +1329,32 @@ function createTextLine() {
|
|||
*/
|
||||
function editTextLine(textlineId, currentText) {
|
||||
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
||||
var newText = prompt(lang.textlineContent || 'Text bearbeiten:', currentText || '');
|
||||
if (!newText) return;
|
||||
|
||||
debugLog('✏️ Bearbeite Textzeile ' + textlineId);
|
||||
showInputDialog(
|
||||
lang.buttonEdit || 'Textzeile bearbeiten',
|
||||
lang.textlineContent || 'Text:',
|
||||
currentText || '',
|
||||
function(newText) {
|
||||
debugLog('✏️ Bearbeite Textzeile ' + textlineId);
|
||||
|
||||
$.post('/dolibarr/custom/subtotaltitle/ajax/edit_textline.php', {
|
||||
textline_id: textlineId,
|
||||
text: newText
|
||||
}, function(response) {
|
||||
debugLog('Edit response: ' + JSON.stringify(response));
|
||||
if (response.success) {
|
||||
window.location.href = window.location.pathname + window.location.search;
|
||||
} else {
|
||||
alert((lang.errorSavingTextline || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||
}
|
||||
}, 'json').fail(function(xhr, status, error) {
|
||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||
alert((lang.errorSavingTextline || 'Fehler beim Bearbeiten') + ': ' + error);
|
||||
});
|
||||
$.post('/dolibarr/custom/subtotaltitle/ajax/edit_textline.php', {
|
||||
textline_id: textlineId,
|
||||
text: newText
|
||||
}, function(response) {
|
||||
debugLog('Edit response: ' + JSON.stringify(response));
|
||||
if (response.success) {
|
||||
window.location.href = window.location.pathname + window.location.search;
|
||||
} else {
|
||||
showErrorAlert((lang.errorSavingTextline || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||
}
|
||||
}, 'json').fail(function(xhr, status, error) {
|
||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||
showErrorAlert((lang.errorSavingTextline || 'Fehler beim Bearbeiten') + ': ' + error);
|
||||
});
|
||||
},
|
||||
lang.buttonSave || 'Speichern',
|
||||
lang.buttonCancel || 'Abbrechen'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1122,25 +1362,31 @@ function editTextLine(textlineId, currentText) {
|
|||
*/
|
||||
function deleteTextLine(textlineId) {
|
||||
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
||||
if (!confirm(lang.confirmDeleteTextline || 'Textzeile wirklich löschen?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
debugLog('🗑️ Lösche Textzeile ' + textlineId);
|
||||
showConfirmDialog(
|
||||
'Textzeile löschen',
|
||||
lang.confirmDeleteTextline || 'Textzeile wirklich löschen?',
|
||||
function() {
|
||||
debugLog('Lösche Textzeile ' + textlineId);
|
||||
|
||||
$.post('/dolibarr/custom/subtotaltitle/ajax/delete_textline.php', {
|
||||
textline_id: textlineId
|
||||
}, function(response) {
|
||||
debugLog('Delete response: ' + JSON.stringify(response));
|
||||
if (response.success) {
|
||||
window.location.href = window.location.pathname + window.location.search;
|
||||
} else {
|
||||
alert((lang.errorDeletingTextline || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||
}
|
||||
}, 'json').fail(function(xhr, status, error) {
|
||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||
alert((lang.errorDeletingTextline || 'Fehler beim Löschen') + ': ' + error);
|
||||
});
|
||||
$.post('/dolibarr/custom/subtotaltitle/ajax/delete_textline.php', {
|
||||
textline_id: textlineId,
|
||||
document_type: getDocumentType()
|
||||
}, function(response) {
|
||||
debugLog('Delete response: ' + JSON.stringify(response));
|
||||
if (response.success) {
|
||||
window.location.href = window.location.pathname + window.location.search;
|
||||
} else {
|
||||
showErrorAlert((lang.errorDeletingTextline || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||
}
|
||||
}, 'json').fail(function(xhr, status, error) {
|
||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||
showErrorAlert((lang.errorDeletingTextline || 'Fehler beim Löschen') + ': ' + error);
|
||||
});
|
||||
},
|
||||
'Ja, löschen',
|
||||
'Abbrechen'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1148,27 +1394,32 @@ function deleteTextLine(textlineId) {
|
|||
*/
|
||||
function removeFromSection(productId) {
|
||||
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
||||
if (!confirm(lang.confirmRemoveFromSection || 'Produkt aus Positionsgruppe entfernen?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var docType = getDocumentType();
|
||||
debugLog('🔓 Entferne Produkt ' + productId + ' aus Section (docType: ' + docType + ')');
|
||||
showConfirmDialog(
|
||||
'Aus Positionsgruppe entfernen',
|
||||
lang.confirmRemoveFromSection || 'Produkt aus Positionsgruppe entfernen?',
|
||||
function() {
|
||||
var docType = getDocumentType();
|
||||
debugLog('Entferne Produkt ' + productId + ' aus Section (docType: ' + docType + ')');
|
||||
|
||||
$.post('/dolibarr/custom/subtotaltitle/ajax/remove_from_section.php', {
|
||||
product_id: productId,
|
||||
document_type: docType
|
||||
}, function(response) {
|
||||
debugLog('Remove response: ' + JSON.stringify(response));
|
||||
if (response.success) {
|
||||
safeReload();
|
||||
} else {
|
||||
alert((lang.errorReordering || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||
}
|
||||
}, 'json').fail(function(xhr, status, error) {
|
||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||
alert((lang.errorReordering || 'Fehler') + ': ' + error);
|
||||
});
|
||||
$.post('/dolibarr/custom/subtotaltitle/ajax/remove_from_section.php', {
|
||||
product_id: productId,
|
||||
document_type: docType
|
||||
}, function(response) {
|
||||
debugLog('Remove response: ' + JSON.stringify(response));
|
||||
if (response.success) {
|
||||
safeReload();
|
||||
} else {
|
||||
showErrorAlert((lang.errorReordering || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||
}
|
||||
}, 'json').fail(function(xhr, status, error) {
|
||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||
showErrorAlert((lang.errorReordering || 'Fehler') + ': ' + error);
|
||||
});
|
||||
},
|
||||
'Ja, entfernen',
|
||||
'Abbrechen'
|
||||
);
|
||||
}
|
||||
|
||||
function toggleMassDelete() {
|
||||
|
|
@ -1186,11 +1437,11 @@ function toggleMassDelete() {
|
|||
}
|
||||
});
|
||||
|
||||
// Buttons NACH dem Massenlösch-Button einfügen
|
||||
// Buttons NACH dem Massenlösch-Button einfügen (Ausgewählte löschen ganz rechts)
|
||||
$('#btnMassDelete').after(
|
||||
'<a id="btnMassDoDelete" class="butActionDelete" href="#" onclick="deleteMassSelected(); return false;" style="background:#c00;color:#fff;margin-left:5px;">Ausgewählte löschen</a>' +
|
||||
'<a id="btnMassCancel" class="butAction" href="#" onclick="toggleMassDelete(); return false;">Abbrechen</a>' +
|
||||
'<a id="btnMassSelectAll" class="butAction" href="#" onclick="selectAllLines(); return false;">Alle auswählen</a>' +
|
||||
'<a id="btnMassCancel" class="butAction" href="#" onclick="toggleMassDelete(); return false;">Abbrechen</a>'
|
||||
'<a id="btnMassDoDelete" class="butActionDelete" href="#" onclick="deleteMassSelected(); return false;" style="background:#c00;color:#fff;">Ausgewählte löschen</a>'
|
||||
);
|
||||
|
||||
// Original-Button verstecken
|
||||
|
|
@ -1217,30 +1468,31 @@ function deleteMassSelected() {
|
|||
});
|
||||
|
||||
if (selectedIds.length === 0) {
|
||||
alert(lang.noLinesSelected || 'Keine Zeilen ausgewählt!');
|
||||
showErrorAlert(lang.noLinesSelected || 'Keine Zeilen ausgewählt!');
|
||||
return;
|
||||
}
|
||||
|
||||
var msg1 = (lang.confirmDeleteLines || 'Wirklich %s Zeilen löschen?').replace('%s', selectedIds.length);
|
||||
if (!confirm(msg1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var msg2 = (lang.confirmDeleteLinesWarning || 'LETZTE WARNUNG: %s Zeilen werden UNWIDERRUFLICH gelöscht!').replace('%s', selectedIds.length);
|
||||
if (!confirm(msg2)) {
|
||||
return;
|
||||
}
|
||||
showConfirmDialog('Zeilen löschen', msg1, function() {
|
||||
// Erste Bestätigung OK - zweite Warnung zeigen
|
||||
var msg2 = (lang.confirmDeleteLinesWarning || 'LETZTE WARNUNG: %s Zeilen werden UNWIDERRUFLICH gelöscht!').replace('%s', selectedIds.length);
|
||||
|
||||
$.post('/dolibarr/custom/subtotaltitle/ajax/mass_delete.php', {
|
||||
line_ids: JSON.stringify(selectedIds),
|
||||
facture_id: getFactureId()
|
||||
}, function(response) {
|
||||
if (response.success) {
|
||||
safeReload();
|
||||
} else {
|
||||
alert((lang.errorDeletingSection || 'Fehler') + ': ' + (response.error || 'Unbekannt'));
|
||||
}
|
||||
}, 'json');
|
||||
showConfirmDialog('Letzte Warnung', '<div style="color:#c00;font-weight:bold;">' + msg2 + '</div>', function() {
|
||||
// Zweite Bestätigung OK - jetzt löschen
|
||||
$.post('/dolibarr/custom/subtotaltitle/ajax/mass_delete.php', {
|
||||
line_ids: JSON.stringify(selectedIds),
|
||||
facture_id: getFactureId(),
|
||||
document_type: getDocumentType()
|
||||
}, function(response) {
|
||||
if (response.success) {
|
||||
safeReload();
|
||||
} else {
|
||||
showErrorAlert((lang.errorDeletingSection || 'Fehler') + ': ' + (response.error || 'Unbekannt'));
|
||||
}
|
||||
}, 'json');
|
||||
}, 'Endgültig löschen', 'Abbrechen');
|
||||
}, 'Ja, löschen', 'Abbrechen');
|
||||
}
|
||||
|
||||
function getFactureId() {
|
||||
|
|
@ -1396,35 +1648,38 @@ function linkToNearestSection(lineId) {
|
|||
}
|
||||
|
||||
if (!targetSection) {
|
||||
alert('Fehler: Keine passende Section gefunden');
|
||||
showErrorAlert('Keine passende Positionsgruppe gefunden');
|
||||
return;
|
||||
}
|
||||
|
||||
// Bestätigung
|
||||
if (!confirm('Produkt zur Positionsgruppe "' + targetSection.title + '" hinzufügen?')) {
|
||||
return;
|
||||
}
|
||||
showConfirmDialog(
|
||||
'Zur Positionsgruppe hinzufügen',
|
||||
'Produkt zur Positionsgruppe "' + targetSection.title + '" hinzufügen?',
|
||||
function() {
|
||||
debugLog(' AJAX Call: add_to_section.php');
|
||||
|
||||
debugLog(' → AJAX Call: add_to_section.php');
|
||||
// AJAX Call zum Backend
|
||||
$.post('/dolibarr/custom/subtotaltitle/ajax/add_to_section.php', {
|
||||
line_id: lineId,
|
||||
section_id: targetSection.id,
|
||||
document_id: docInfo.id,
|
||||
document_type: docInfo.type
|
||||
}, function(response) {
|
||||
debugLog(' Response: ' + JSON.stringify(response));
|
||||
|
||||
// AJAX Call zum Backend
|
||||
$.post('/dolibarr/custom/subtotaltitle/ajax/add_to_section.php', {
|
||||
line_id: lineId,
|
||||
section_id: targetSection.id,
|
||||
document_id: docInfo.id,
|
||||
document_type: docInfo.type
|
||||
}, function(response) {
|
||||
debugLog(' → Response: ' + JSON.stringify(response));
|
||||
|
||||
if (response.success) {
|
||||
// Reload Page
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert('Fehler: ' + (response.error || 'Unbekannter Fehler'));
|
||||
}
|
||||
}, 'json').fail(function(xhr, status, error) {
|
||||
console.error('AJAX Fehler:', status, error);
|
||||
console.error('Response:', xhr.responseText);
|
||||
alert('Fehler beim Verknüpfen: ' + xhr.responseText);
|
||||
});
|
||||
if (response.success) {
|
||||
window.location.reload();
|
||||
} else {
|
||||
showErrorAlert('Fehler: ' + (response.error || 'Unbekannter Fehler'));
|
||||
}
|
||||
}, 'json').fail(function(xhr, status, error) {
|
||||
console.error('AJAX Fehler:', status, error);
|
||||
console.error('Response:', xhr.responseText);
|
||||
showErrorAlert('Fehler beim Verknüpfen: ' + xhr.responseText);
|
||||
});
|
||||
},
|
||||
'Ja, hinzufügen',
|
||||
'Abbrechen'
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,13 +38,13 @@ function syncToFacturedet(lineId, lineType) {
|
|||
updateSyncCheckbox(lineId, true);
|
||||
debugLog('✅ Zeile zu Rechnung hinzugefügt');
|
||||
} else {
|
||||
alert((lang.errorSyncing || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||
showErrorAlert((lang.errorSyncing || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||
// Checkbox zurücksetzen
|
||||
$('.sync-checkbox[data-line-id="' + lineId + '"]').prop('checked', false);
|
||||
}
|
||||
}, 'json').fail(function(xhr, status, error) {
|
||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||
alert((lang.errorSyncing || 'Fehler beim Synchronisieren') + ': ' + error);
|
||||
showErrorAlert((lang.errorSyncing || 'Fehler beim Synchronisieren') + ': ' + error);
|
||||
$('.sync-checkbox[data-line-id="' + lineId + '"]').prop('checked', false);
|
||||
});
|
||||
}
|
||||
|
|
@ -54,36 +54,42 @@ function syncToFacturedet(lineId, lineType) {
|
|||
*/
|
||||
function removeFromFacturedet(lineId, lineType) {
|
||||
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
||||
if (!confirm(lang.confirmRemoveLine || 'Zeile aus der Rechnung entfernen?\n\nDie Zeile bleibt in der Positionsgruppen-Verwaltung erhalten.')) {
|
||||
// Checkbox zurücksetzen
|
||||
$('.sync-checkbox[data-line-id="' + lineId + '"]').prop('checked', true);
|
||||
return;
|
||||
}
|
||||
|
||||
debugLog('📥 Remove from facturedet: ' + lineType + ' #' + lineId);
|
||||
showConfirmDialog(
|
||||
'Aus Rechnung entfernen',
|
||||
lang.confirmRemoveLine || 'Zeile aus der Rechnung entfernen?<br><br><em>Die Zeile bleibt in der Positionsgruppen-Verwaltung erhalten.</em>',
|
||||
function() {
|
||||
debugLog('📥 Remove from facturedet: ' + lineType + ' #' + lineId);
|
||||
|
||||
var docType = getDocumentTypeForSync();
|
||||
debugLog('Document type: ' + docType);
|
||||
var docType = getDocumentTypeForSync();
|
||||
debugLog('Document type: ' + docType);
|
||||
|
||||
$.post('/dolibarr/custom/subtotaltitle/ajax/sync_to_facturedet.php', {
|
||||
action: 'remove',
|
||||
line_id: lineId,
|
||||
line_type: lineType,
|
||||
document_type: docType
|
||||
}, function(response) {
|
||||
debugLog('Remove response: ' + JSON.stringify(response));
|
||||
if (response.success) {
|
||||
updateSyncCheckbox(lineId, false);
|
||||
debugLog('✅ Zeile aus Rechnung entfernt');
|
||||
} else {
|
||||
alert((lang.errorSyncing || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||
$('.sync-checkbox[data-line-id="' + lineId + '"]').prop('checked', true);
|
||||
}
|
||||
}, 'json').fail(function(xhr, status, error) {
|
||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||
alert((lang.errorSyncing || 'Fehler') + ': ' + error);
|
||||
$('.sync-checkbox[data-line-id="' + lineId + '"]').prop('checked', true);
|
||||
});
|
||||
$.post('/dolibarr/custom/subtotaltitle/ajax/sync_to_facturedet.php', {
|
||||
action: 'remove',
|
||||
line_id: lineId,
|
||||
line_type: lineType,
|
||||
document_type: docType
|
||||
}, function(response) {
|
||||
debugLog('Remove response: ' + JSON.stringify(response));
|
||||
if (response.success) {
|
||||
updateSyncCheckbox(lineId, false);
|
||||
debugLog('✅ Zeile aus Rechnung entfernt');
|
||||
} else {
|
||||
showErrorAlert((lang.errorSyncing || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||
$('.sync-checkbox[data-line-id="' + lineId + '"]').prop('checked', true);
|
||||
}
|
||||
}, 'json').fail(function(xhr, status, error) {
|
||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||
showErrorAlert((lang.errorSyncing || 'Fehler') + ': ' + error);
|
||||
$('.sync-checkbox[data-line-id="' + lineId + '"]').prop('checked', true);
|
||||
});
|
||||
},
|
||||
'Ja, entfernen',
|
||||
'Abbrechen'
|
||||
);
|
||||
|
||||
// Checkbox zurücksetzen bis Bestätigung
|
||||
$('.sync-checkbox[data-line-id="' + lineId + '"]').prop('checked', true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -119,56 +125,58 @@ function updateSyncCheckbox(lineId, isInFacturedet) {
|
|||
*/
|
||||
function syncAllToFacturedet() {
|
||||
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
||||
if (!confirm(lang.confirmSyncAll || 'Alle Positionsgruppen-Elemente (Sections, Textzeilen, Zwischensummen) zur Rechnung hinzufügen?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
debugLog('📤 Sync ALL to facturedet...');
|
||||
|
||||
var $unchecked = $('.sync-checkbox:not(:checked)');
|
||||
var total = $unchecked.length;
|
||||
var done = 0;
|
||||
var errors = 0;
|
||||
|
||||
if (total === 0) {
|
||||
alert(lang.allElementsAlreadyInInvoice || 'Alle Elemente sind bereits in der Rechnung.');
|
||||
showErrorAlert(lang.allElementsAlreadyInInvoice || 'Alle Elemente sind bereits in der Rechnung.');
|
||||
return;
|
||||
}
|
||||
|
||||
var docType = getDocumentTypeForSync();
|
||||
showConfirmDialog(
|
||||
'Alle zur Rechnung hinzufügen',
|
||||
lang.confirmSyncAll || 'Alle Positionsgruppen-Elemente (Sections, Textzeilen, Zwischensummen) zur Rechnung hinzufügen?',
|
||||
function() {
|
||||
debugLog('📤 Sync ALL to facturedet...');
|
||||
|
||||
$unchecked.each(function() {
|
||||
var lineId = $(this).data('line-id');
|
||||
var lineType = $(this).data('line-type');
|
||||
var done = 0;
|
||||
var errors = 0;
|
||||
var docType = getDocumentTypeForSync();
|
||||
|
||||
$.post('/dolibarr/custom/subtotaltitle/ajax/sync_to_facturedet.php', {
|
||||
action: 'add',
|
||||
line_id: lineId,
|
||||
line_type: lineType,
|
||||
document_type: docType
|
||||
}, function(response) {
|
||||
done++;
|
||||
if (response.success) {
|
||||
updateSyncCheckbox(lineId, true);
|
||||
} else {
|
||||
errors++;
|
||||
}
|
||||
if (done >= total) {
|
||||
debugLog('✅ Sync abgeschlossen: ' + (total - errors) + ' erfolgreich, ' + errors + ' Fehler');
|
||||
if (errors > 0) {
|
||||
var msg = (lang.elementsAddedWithErrors || '%s von %s Elementen hinzugefügt.\n%s Fehler aufgetreten.')
|
||||
.replace('%s', total - errors).replace('%s', total).replace('%s', errors);
|
||||
alert(msg);
|
||||
} else {
|
||||
var msg = (lang.elementsAddedToInvoice || '%s Elemente zur Rechnung hinzugefügt.').replace('%s', total);
|
||||
alert(msg);
|
||||
}
|
||||
}
|
||||
}, 'json').fail(function() {
|
||||
done++;
|
||||
errors++;
|
||||
});
|
||||
});
|
||||
$unchecked.each(function() {
|
||||
var lineId = $(this).data('line-id');
|
||||
var lineType = $(this).data('line-type');
|
||||
|
||||
$.post('/dolibarr/custom/subtotaltitle/ajax/sync_to_facturedet.php', {
|
||||
action: 'add',
|
||||
line_id: lineId,
|
||||
line_type: lineType,
|
||||
document_type: docType
|
||||
}, function(response) {
|
||||
done++;
|
||||
if (response.success) {
|
||||
updateSyncCheckbox(lineId, true);
|
||||
} else {
|
||||
errors++;
|
||||
}
|
||||
if (done >= total) {
|
||||
debugLog('✅ Sync abgeschlossen: ' + (total - errors) + ' erfolgreich, ' + errors + ' Fehler');
|
||||
if (errors > 0) {
|
||||
showErrorAlert((total - errors) + ' von ' + total + ' Elementen hinzugefügt. ' + errors + ' Fehler aufgetreten.');
|
||||
} else {
|
||||
safeReload();
|
||||
}
|
||||
}
|
||||
}, 'json').fail(function() {
|
||||
done++;
|
||||
errors++;
|
||||
});
|
||||
});
|
||||
},
|
||||
'Ja, hinzufügen',
|
||||
'Abbrechen'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -176,56 +184,58 @@ function syncAllToFacturedet() {
|
|||
*/
|
||||
function removeAllFromFacturedet() {
|
||||
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
||||
if (!confirm(lang.confirmRemoveAll || 'ALLE Positionsgruppen-Elemente aus der Rechnung entfernen?\n\nDie Elemente bleiben in der Verwaltung erhalten.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
debugLog('📥 Remove ALL from facturedet...');
|
||||
|
||||
var $checked = $('.sync-checkbox:checked');
|
||||
var total = $checked.length;
|
||||
var done = 0;
|
||||
var errors = 0;
|
||||
|
||||
if (total === 0) {
|
||||
alert(lang.noElementsInInvoice || 'Keine Elemente in der Rechnung vorhanden.');
|
||||
showErrorAlert(lang.noElementsInInvoice || 'Keine Elemente in der Rechnung vorhanden.');
|
||||
return;
|
||||
}
|
||||
|
||||
var docType = getDocumentTypeForSync();
|
||||
showConfirmDialog(
|
||||
'Alle aus Rechnung entfernen',
|
||||
(lang.confirmRemoveAll || 'ALLE Positionsgruppen-Elemente aus der Rechnung entfernen?') + '<br><br><em>Die Elemente bleiben in der Verwaltung erhalten.</em>',
|
||||
function() {
|
||||
debugLog('📥 Remove ALL from facturedet...');
|
||||
|
||||
$checked.each(function() {
|
||||
var lineId = $(this).data('line-id');
|
||||
var lineType = $(this).data('line-type');
|
||||
var done = 0;
|
||||
var errors = 0;
|
||||
var docType = getDocumentTypeForSync();
|
||||
|
||||
$.post('/dolibarr/custom/subtotaltitle/ajax/sync_to_facturedet.php', {
|
||||
action: 'remove',
|
||||
line_id: lineId,
|
||||
line_type: lineType,
|
||||
document_type: docType
|
||||
}, function(response) {
|
||||
done++;
|
||||
if (response.success) {
|
||||
updateSyncCheckbox(lineId, false);
|
||||
} else {
|
||||
errors++;
|
||||
}
|
||||
if (done >= total) {
|
||||
debugLog('✅ Remove abgeschlossen: ' + (total - errors) + ' erfolgreich, ' + errors + ' Fehler');
|
||||
if (errors > 0) {
|
||||
var msg = (lang.elementsRemovedWithErrors || '%s von %s Elementen entfernt.\n%s Fehler aufgetreten.')
|
||||
.replace('%s', total - errors).replace('%s', total).replace('%s', errors);
|
||||
alert(msg);
|
||||
} else {
|
||||
var msg = (lang.elementsRemovedFromInvoice || '%s Elemente aus Rechnung entfernt.').replace('%s', total);
|
||||
alert(msg);
|
||||
}
|
||||
}
|
||||
}, 'json').fail(function() {
|
||||
done++;
|
||||
errors++;
|
||||
});
|
||||
});
|
||||
$checked.each(function() {
|
||||
var lineId = $(this).data('line-id');
|
||||
var lineType = $(this).data('line-type');
|
||||
|
||||
$.post('/dolibarr/custom/subtotaltitle/ajax/sync_to_facturedet.php', {
|
||||
action: 'remove',
|
||||
line_id: lineId,
|
||||
line_type: lineType,
|
||||
document_type: docType
|
||||
}, function(response) {
|
||||
done++;
|
||||
if (response.success) {
|
||||
updateSyncCheckbox(lineId, false);
|
||||
} else {
|
||||
errors++;
|
||||
}
|
||||
if (done >= total) {
|
||||
debugLog('✅ Remove abgeschlossen: ' + (total - errors) + ' erfolgreich, ' + errors + ' Fehler');
|
||||
if (errors > 0) {
|
||||
showErrorAlert((total - errors) + ' von ' + total + ' Elementen entfernt. ' + errors + ' Fehler aufgetreten.');
|
||||
} else {
|
||||
safeReload();
|
||||
}
|
||||
}
|
||||
}, 'json').fail(function() {
|
||||
done++;
|
||||
errors++;
|
||||
});
|
||||
});
|
||||
},
|
||||
'Ja, alle entfernen',
|
||||
'Abbrechen'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
-- Fix Foreign Key Constraints für Multi-Document-Support
|
||||
-- Diese Felder sollten NULL sein können, da eine Section entweder zu einer
|
||||
-- Rechnung ODER einem Angebot ODER einem Auftrag gehört, aber nie zu allen gleichzeitig.
|
||||
|
||||
-- 1. Foreign Key Constraints temporär entfernen
|
||||
ALTER TABLE llx_facture_lines_manager DROP FOREIGN KEY IF EXISTS `1`;
|
||||
ALTER TABLE llx_facture_lines_manager DROP FOREIGN KEY IF EXISTS `2`;
|
||||
ALTER TABLE llx_facture_lines_manager DROP FOREIGN KEY IF EXISTS `3`;
|
||||
ALTER TABLE llx_facture_lines_manager DROP FOREIGN KEY IF EXISTS `fk_facture_lines_manager_facture`;
|
||||
ALTER TABLE llx_facture_lines_manager DROP FOREIGN KEY IF EXISTS `fk_facture_lines_manager_propal`;
|
||||
ALTER TABLE llx_facture_lines_manager DROP FOREIGN KEY IF EXISTS `fk_facture_lines_manager_commande`;
|
||||
|
||||
-- 2. Felder auf NULL ändern
|
||||
ALTER TABLE llx_facture_lines_manager
|
||||
MODIFY COLUMN fk_facture INT NULL DEFAULT NULL,
|
||||
MODIFY COLUMN fk_propal INT NULL DEFAULT NULL,
|
||||
MODIFY COLUMN fk_commande INT NULL DEFAULT NULL;
|
||||
|
||||
-- 3. Foreign Key Constraints wieder hinzufügen (mit ON DELETE CASCADE)
|
||||
ALTER TABLE llx_facture_lines_manager
|
||||
ADD CONSTRAINT fk_facture_lines_manager_facture
|
||||
FOREIGN KEY (fk_facture) REFERENCES llx_facture(rowid) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE llx_facture_lines_manager
|
||||
ADD CONSTRAINT fk_facture_lines_manager_propal
|
||||
FOREIGN KEY (fk_propal) REFERENCES llx_propal(rowid) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE llx_facture_lines_manager
|
||||
ADD CONSTRAINT fk_facture_lines_manager_commande
|
||||
FOREIGN KEY (fk_commande) REFERENCES llx_commande(rowid) ON DELETE CASCADE;
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
-- Copyright (C) 2026 Eduard Wisch
|
||||
--
|
||||
-- This program is free software: you can redistribute it and/or modify
|
||||
-- it under the terms of the GNU General Public License as published by
|
||||
-- the Free Software Foundation, either version 3 of the License, or
|
||||
-- (at your option) any later version.
|
||||
--
|
||||
-- This program is distributed in the hope that it will be useful,
|
||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
-- GNU General Public License for more details.
|
||||
--
|
||||
-- You should have received a copy of the GNU General Public License
|
||||
-- along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
--
|
||||
-- Tabelle für Verwaltung von Rechnungs-, Angebots- und Auftragszeilen
|
||||
--
|
||||
|
||||
CREATE TABLE IF NOT EXISTS llx_facture_lines_manager (
|
||||
rowid INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
|
||||
-- Dokumenttyp und Referenzen
|
||||
document_type VARCHAR(20) DEFAULT 'invoice' NOT NULL,
|
||||
fk_facture INT(11) DEFAULT NULL,
|
||||
fk_propal INT(11) DEFAULT NULL,
|
||||
fk_commande INT(11) DEFAULT NULL,
|
||||
|
||||
-- Zeilentyp: 'section', 'text', 'subtotal', 'product'
|
||||
line_type VARCHAR(20) NOT NULL,
|
||||
|
||||
-- Referenzen auf Detailzeilen
|
||||
fk_facturedet INT(11) DEFAULT NULL,
|
||||
fk_propaldet INT(11) DEFAULT NULL,
|
||||
fk_commandedet INT(11) DEFAULT NULL,
|
||||
|
||||
-- Section-Informationen
|
||||
parent_section INT(11) DEFAULT NULL,
|
||||
title VARCHAR(255) DEFAULT NULL,
|
||||
line_order INT(11) DEFAULT 0,
|
||||
show_subtotal TINYINT(1) DEFAULT 0,
|
||||
collapsed TINYINT(1) DEFAULT 0,
|
||||
in_facturedet TINYINT(1) DEFAULT 0,
|
||||
|
||||
-- Timestamps
|
||||
date_creation DATETIME NOT NULL,
|
||||
tms TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
-- Indizes
|
||||
INDEX idx_fk_facture (fk_facture),
|
||||
INDEX idx_fk_propal (fk_propal),
|
||||
INDEX idx_fk_commande (fk_commande),
|
||||
INDEX idx_fk_facturedet (fk_facturedet),
|
||||
INDEX idx_fk_propaldet (fk_propaldet),
|
||||
INDEX idx_fk_commandedet (fk_commandedet),
|
||||
INDEX idx_document_type (document_type),
|
||||
INDEX idx_line_type (line_type),
|
||||
INDEX idx_parent_section (parent_section),
|
||||
INDEX idx_line_order (line_order)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
-- Copyright (C) 2026 Eduard Wisch
|
||||
--
|
||||
-- Haupttabelle für Verwaltung von Rechnungs-, Angebots- und Auftragszeilen
|
||||
-- Diese Datei wird beim Modul-Upgrade ausgeführt
|
||||
--
|
||||
|
||||
-- Prüfen ob Spalten existieren und hinzufügen falls nicht
|
||||
SET @exist := (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'llx_facture_lines_manager'
|
||||
AND column_name = 'document_type');
|
||||
|
||||
SET @sqlstmt := IF(@exist = 0,
|
||||
'ALTER TABLE llx_facture_lines_manager ADD COLUMN document_type VARCHAR(20) DEFAULT ''invoice'' NOT NULL AFTER fk_facture',
|
||||
'SELECT ''Column document_type already exists'' as msg');
|
||||
PREPARE stmt FROM @sqlstmt;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- fk_propal hinzufügen
|
||||
SET @exist := (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'llx_facture_lines_manager'
|
||||
AND column_name = 'fk_propal');
|
||||
|
||||
SET @sqlstmt := IF(@exist = 0,
|
||||
'ALTER TABLE llx_facture_lines_manager ADD COLUMN fk_propal INT(11) DEFAULT NULL AFTER document_type',
|
||||
'SELECT ''Column fk_propal already exists'' as msg');
|
||||
PREPARE stmt FROM @sqlstmt;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- fk_commande hinzufügen
|
||||
SET @exist := (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'llx_facture_lines_manager'
|
||||
AND column_name = 'fk_commande');
|
||||
|
||||
SET @sqlstmt := IF(@exist = 0,
|
||||
'ALTER TABLE llx_facture_lines_manager ADD COLUMN fk_commande INT(11) DEFAULT NULL AFTER fk_propal',
|
||||
'SELECT ''Column fk_commande already exists'' as msg');
|
||||
PREPARE stmt FROM @sqlstmt;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- fk_propaldet hinzufügen
|
||||
SET @exist := (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'llx_facture_lines_manager'
|
||||
AND column_name = 'fk_propaldet');
|
||||
|
||||
SET @sqlstmt := IF(@exist = 0,
|
||||
'ALTER TABLE llx_facture_lines_manager ADD COLUMN fk_propaldet INT(11) DEFAULT NULL AFTER fk_facturedet',
|
||||
'SELECT ''Column fk_propaldet already exists'' as msg');
|
||||
PREPARE stmt FROM @sqlstmt;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- fk_commandedet hinzufügen
|
||||
SET @exist := (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'llx_facture_lines_manager'
|
||||
AND column_name = 'fk_commandedet');
|
||||
|
||||
SET @sqlstmt := IF(@exist = 0,
|
||||
'ALTER TABLE llx_facture_lines_manager ADD COLUMN fk_commandedet INT(11) DEFAULT NULL AFTER fk_propaldet',
|
||||
'SELECT ''Column fk_commandedet already exists'' as msg');
|
||||
PREPARE stmt FROM @sqlstmt;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- Bestehende Daten aktualisieren
|
||||
UPDATE llx_facture_lines_manager
|
||||
SET document_type = 'invoice'
|
||||
WHERE fk_facture IS NOT NULL AND (document_type IS NULL OR document_type = '');
|
||||
|
||||
-- Indizes hinzufügen falls sie nicht existieren
|
||||
SET @exist := (SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'llx_facture_lines_manager'
|
||||
AND index_name = 'idx_fk_propal');
|
||||
|
||||
SET @sqlstmt := IF(@exist = 0,
|
||||
'ALTER TABLE llx_facture_lines_manager ADD INDEX idx_fk_propal (fk_propal)',
|
||||
'SELECT ''Index idx_fk_propal already exists'' as msg');
|
||||
PREPARE stmt FROM @sqlstmt;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @exist := (SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'llx_facture_lines_manager'
|
||||
AND index_name = 'idx_fk_commande');
|
||||
|
||||
SET @sqlstmt := IF(@exist = 0,
|
||||
'ALTER TABLE llx_facture_lines_manager ADD INDEX idx_fk_commande (fk_commande)',
|
||||
'SELECT ''Index idx_fk_commande already exists'' as msg');
|
||||
PREPARE stmt FROM @sqlstmt;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @exist := (SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'llx_facture_lines_manager'
|
||||
AND index_name = 'idx_fk_propaldet');
|
||||
|
||||
SET @sqlstmt := IF(@exist = 0,
|
||||
'ALTER TABLE llx_facture_lines_manager ADD INDEX idx_fk_propaldet (fk_propaldet)',
|
||||
'SELECT ''Index idx_fk_propaldet already exists'' as msg');
|
||||
PREPARE stmt FROM @sqlstmt;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @exist := (SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'llx_facture_lines_manager'
|
||||
AND index_name = 'idx_fk_commandedet');
|
||||
|
||||
SET @sqlstmt := IF(@exist = 0,
|
||||
'ALTER TABLE llx_facture_lines_manager ADD INDEX idx_fk_commandedet (fk_commandedet)',
|
||||
'SELECT ''Index idx_fk_commandedet already exists'' as msg');
|
||||
PREPARE stmt FROM @sqlstmt;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @exist := (SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = 'llx_facture_lines_manager'
|
||||
AND index_name = 'idx_document_type');
|
||||
|
||||
SET @sqlstmt := IF(@exist = 0,
|
||||
'ALTER TABLE llx_facture_lines_manager ADD INDEX idx_document_type (document_type)',
|
||||
'SELECT ''Index idx_document_type already exists'' as msg');
|
||||
PREPARE stmt FROM @sqlstmt;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
Loading…
Reference in a new issue