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';
|
$fk_commande = ($docType === 'order') ? (int)$facture_id : 'NULL';
|
||||||
|
|
||||||
$sql = "INSERT INTO ".MAIN_DB_PREFIX."facture_lines_manager";
|
$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 .= " (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.", NOW())";
|
$sql .= " VALUES (".$fk_facture.", ".$fk_propal.", ".$fk_commande.", '".$db->escape($docType)."', 'text', '".$db->escape($text)."', ".$next_order.", 1, NOW())";
|
||||||
|
|
||||||
if ($db->query($sql)) {
|
if ($db->query($sql)) {
|
||||||
$new_id = $db->last_insert_id(MAIN_DB_PREFIX."facture_lines_manager");
|
$new_id = $db->last_insert_id(MAIN_DB_PREFIX."facture_lines_manager");
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,31 @@
|
||||||
<?php
|
<?php
|
||||||
define('NOTOKENRENEWAL', 1);
|
define('NOTOKENRENEWAL', 1);
|
||||||
require '../../../main.inc.php';
|
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__.'/../lib/subtotaltitle.lib.php';
|
||||||
|
require_once __DIR__.'/../class/DocumentTypeHelper.class.php';
|
||||||
|
|
||||||
global $user;
|
global $user;
|
||||||
|
|
||||||
$section_id = GETPOST('section_id', 'int');
|
$section_id = GETPOST('section_id', 'int');
|
||||||
$force = GETPOST('force', '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) {
|
if (!$section_id) {
|
||||||
echo json_encode(['success' => false, 'error' => 'Missing parameters']);
|
echo json_encode(['success' => false, 'error' => 'Missing parameters']);
|
||||||
exit;
|
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
|
// 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 .= " WHERE rowid = ".(int)$section_id;
|
||||||
$sql .= " AND line_type = 'section'";
|
$sql .= " AND line_type = 'section'";
|
||||||
$resql = $db->query($sql);
|
$resql = $db->query($sql);
|
||||||
|
|
@ -28,55 +36,61 @@ if (!$resql || $db->num_rows($resql) == 0) {
|
||||||
}
|
}
|
||||||
|
|
||||||
$section = $db->fetch_object($resql);
|
$section = $db->fetch_object($resql);
|
||||||
$facture_id = $section->fk_facture;
|
$document_id = $section->doc_id;
|
||||||
|
|
||||||
// 2. Prüfe Rechnungsstatus
|
// 2. Pruefe Dokumentstatus
|
||||||
$facture = new Facture($db);
|
$object = DocumentTypeHelper::loadDocument($docType, $document_id, $db);
|
||||||
$facture->fetch($facture_id);
|
if (!$object) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Dokument nicht gefunden']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
if ($force && $facture->statut != Facture::STATUS_DRAFT) {
|
$isDraft = DocumentTypeHelper::isDraft($object, $docType);
|
||||||
echo json_encode(['success' => false, 'error' => 'Rechnung ist nicht im Entwurf']);
|
if ($force && !$isDraft) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Dokument ist nicht im Entwurf']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Hole Produkt-IDs DIREKT aus DB
|
// 3. Hole Produkt-IDs DIREKT aus DB
|
||||||
$product_ids = [];
|
$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 .= " WHERE parent_section = ".(int)$section_id;
|
||||||
$sql_products .= " AND line_type = 'product'";
|
$sql_products .= " AND line_type = 'product'";
|
||||||
$res_products = $db->query($sql_products);
|
$res_products = $db->query($sql_products);
|
||||||
|
|
||||||
while ($prod = $db->fetch_object($res_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);
|
$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();
|
$db->begin();
|
||||||
|
|
||||||
// 4. Force-Delete: Produkte aus Rechnung löschen
|
// 4. Force-Delete: Produkte aus Dokument loeschen
|
||||||
if ($force && $product_count > 0) {
|
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) {
|
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);
|
$res_del = $db->query($sql_del_line);
|
||||||
|
|
||||||
if ($res_del) {
|
if ($res_del) {
|
||||||
subtotaltitle_debug_log('✅ facturedet gelöscht: ' . $line_id);
|
subtotaltitle_debug_log('Detail geloescht: ' . $line_id);
|
||||||
} else {
|
} 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 = "DELETE FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
$sql_del .= " WHERE parent_section = ".(int)$section_id;
|
$sql_del .= " WHERE parent_section = ".(int)$section_id;
|
||||||
$sql_del .= " AND line_type = 'product'";
|
$sql_del .= " AND line_type = 'product'";
|
||||||
$db->query($sql_del);
|
$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) {
|
} else if (!$force) {
|
||||||
// Ohne force: Produkte nur freigeben
|
// Ohne force: Produkte nur freigeben
|
||||||
|
|
@ -86,35 +100,34 @@ if ($force && $product_count > 0) {
|
||||||
$sql .= " AND line_type = 'product'";
|
$sql .= " AND line_type = 'product'";
|
||||||
$db->query($sql);
|
$db->query($sql);
|
||||||
|
|
||||||
subtotaltitle_debug_log('🔓 ' . $product_count . ' Produkte freigegeben');
|
subtotaltitle_debug_log($product_count . ' Produkte freigegeben');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== NEU: SUBTOTAL LÖSCHEN ==========
|
// ========== SUBTOTAL LOESCHEN ==========
|
||||||
// Hole Subtotal dieser Section (falls vorhanden)
|
$sql_subtotal = "SELECT rowid, ".$tables['fk_line']." as detail_id FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
$sql_subtotal = "SELECT rowid, fk_facturedet FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
|
||||||
$sql_subtotal .= " WHERE parent_section = ".(int)$section_id;
|
$sql_subtotal .= " WHERE parent_section = ".(int)$section_id;
|
||||||
$sql_subtotal .= " AND line_type = 'subtotal'";
|
$sql_subtotal .= " AND line_type = 'subtotal'";
|
||||||
$res_subtotal = $db->query($sql_subtotal);
|
$res_subtotal = $db->query($sql_subtotal);
|
||||||
|
|
||||||
if ($obj_sub = $db->fetch_object($res_subtotal)) {
|
if ($obj_sub = $db->fetch_object($res_subtotal)) {
|
||||||
// Falls Subtotal in facturedet ist, dort auch löschen
|
// Falls Subtotal in Detail-Tabelle ist, dort auch loeschen
|
||||||
if ($obj_sub->fk_facturedet > 0) {
|
if ($obj_sub->detail_id > 0) {
|
||||||
$sql_del_fd = "DELETE FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid = ".(int)$obj_sub->fk_facturedet;
|
$sql_del_fd = "DELETE FROM ".MAIN_DB_PREFIX.$tables['lines_table']." WHERE rowid = ".(int)$obj_sub->detail_id;
|
||||||
$db->query($sql_del_fd);
|
$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;
|
$sql_del_sub = "DELETE FROM ".MAIN_DB_PREFIX."facture_lines_manager WHERE rowid = ".(int)$obj_sub->rowid;
|
||||||
$db->query($sql_del_sub);
|
$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 ==========
|
// ========== VERWAISTE SUBTOTALS AUFRAEUMEN ==========
|
||||||
// Finde alle Subtotals in dieser Rechnung, deren parent_section nicht mehr existiert
|
$sql_orphans = "SELECT s.rowid, s.".$tables['fk_line']." as detail_id, s.parent_section
|
||||||
$sql_orphans = "SELECT s.rowid, s.fk_facturedet, s.parent_section
|
|
||||||
FROM ".MAIN_DB_PREFIX."facture_lines_manager s
|
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.line_type = 'subtotal'
|
||||||
AND s.parent_section IS NOT NULL
|
AND s.parent_section IS NOT NULL
|
||||||
AND NOT EXISTS (
|
AND NOT EXISTS (
|
||||||
|
|
@ -126,36 +139,33 @@ $res_orphans = $db->query($sql_orphans);
|
||||||
|
|
||||||
$orphan_count = 0;
|
$orphan_count = 0;
|
||||||
while ($orphan = $db->fetch_object($res_orphans)) {
|
while ($orphan = $db->fetch_object($res_orphans)) {
|
||||||
// Aus facturedet löschen (falls vorhanden)
|
if ($orphan->detail_id > 0) {
|
||||||
if ($orphan->fk_facturedet > 0) {
|
$sql_del_orphan_fd = "DELETE FROM ".MAIN_DB_PREFIX.$tables['lines_table']." WHERE rowid = ".(int)$orphan->detail_id;
|
||||||
$sql_del_orphan_fd = "DELETE FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid = ".(int)$orphan->fk_facturedet;
|
|
||||||
$db->query($sql_del_orphan_fd);
|
$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;
|
$sql_del_orphan = "DELETE FROM ".MAIN_DB_PREFIX."facture_lines_manager WHERE rowid = ".(int)$orphan->rowid;
|
||||||
$db->query($sql_del_orphan);
|
$db->query($sql_del_orphan);
|
||||||
subtotaltitle_debug_log('🧹 Verwaistes Subtotal aus Manager gelöscht: ' . $orphan->rowid);
|
|
||||||
$orphan_count++;
|
$orphan_count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($orphan_count > 0) {
|
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 = "DELETE FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
$sql .= " WHERE rowid = ".(int)$section_id;
|
$sql .= " WHERE rowid = ".(int)$section_id;
|
||||||
$db->query($sql);
|
$db->query($sql);
|
||||||
|
|
||||||
// Rechnungstotale neu berechnen (nach allen Löschungen)
|
// Dokumenttotale neu berechnen
|
||||||
$facture->update_price(1);
|
$object->update_price(1);
|
||||||
|
|
||||||
// 6. Neuordnen
|
// 6. Neuordnen
|
||||||
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
$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";
|
$sql .= " ORDER BY line_order";
|
||||||
$resql = $db->query($sql);
|
$resql = $db->query($sql);
|
||||||
|
|
||||||
|
|
@ -169,20 +179,23 @@ while ($obj = $db->fetch_object($resql)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. Sync rang
|
// 7. Sync rang
|
||||||
$sql = "SELECT fk_facturedet FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
$sql = "SELECT ".$tables['fk_line']." as detail_id FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
$sql .= " WHERE fk_facture = ".(int)$facture_id;
|
$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id;
|
||||||
$sql .= " AND line_type = 'product'";
|
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||||
|
$sql .= " AND ".$tables['fk_line']." IS NOT NULL";
|
||||||
$sql .= " ORDER BY line_order";
|
$sql .= " ORDER BY line_order";
|
||||||
$resql = $db->query($sql);
|
$resql = $db->query($sql);
|
||||||
|
|
||||||
$rang = 1;
|
$rang = 1;
|
||||||
while ($obj = $db->fetch_object($resql)) {
|
while ($obj = $db->fetch_object($resql)) {
|
||||||
$sql_upd = "UPDATE ".MAIN_DB_PREFIX."facturedet";
|
if ($obj->detail_id) {
|
||||||
|
$sql_upd = "UPDATE ".MAIN_DB_PREFIX.$tables['lines_table'];
|
||||||
$sql_upd .= " SET rang = ".$rang;
|
$sql_upd .= " SET rang = ".$rang;
|
||||||
$sql_upd .= " WHERE rowid = ".(int)$obj->fk_facturedet;
|
$sql_upd .= " WHERE rowid = ".(int)$obj->detail_id;
|
||||||
$db->query($sql_upd);
|
$db->query($sql_upd);
|
||||||
$rang++;
|
$rang++;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$db->commit();
|
$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 && file_exists("../../../../main.inc.php")) $res = @include "../../../../main.inc.php";
|
||||||
if (!$res) die("Include of main fails");
|
if (!$res) die("Include of main fails");
|
||||||
require_once __DIR__.'/../lib/subtotaltitle.lib.php';
|
require_once __DIR__.'/../lib/subtotaltitle.lib.php';
|
||||||
|
require_once __DIR__.'/../class/DocumentTypeHelper.class.php';
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
$textline_id = GETPOST('textline_id', 'int');
|
$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) {
|
if (!$textline_id) {
|
||||||
echo json_encode(array('success' => false, 'error' => 'Missing parameters'));
|
echo json_encode(array('success' => false, 'error' => 'Missing parameters'));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Hole facture_id BEVOR wir löschen
|
// Hole die richtigen Tabellennamen fuer diesen Dokumenttyp
|
||||||
$sql = "SELECT fk_facture FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
$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;
|
$sql .= " WHERE rowid = ".(int)$textline_id;
|
||||||
$resql = $db->query($sql);
|
$resql = $db->query($sql);
|
||||||
|
|
||||||
|
|
@ -29,31 +51,54 @@ if (!$resql || $db->num_rows($resql) == 0) {
|
||||||
}
|
}
|
||||||
|
|
||||||
$obj = $db->fetch_object($resql);
|
$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 = "DELETE FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
$sql .= " WHERE rowid = ".(int)$textline_id;
|
$sql .= " WHERE rowid = ".(int)$textline_id;
|
||||||
$sql .= " AND line_type = 'text'";
|
$sql .= " AND line_type = 'text'";
|
||||||
|
|
||||||
if (!$db->query($sql)) {
|
if (!$db->query($sql)) {
|
||||||
|
$db->rollback();
|
||||||
echo json_encode(array('success' => false, 'error' => $db->lasterror()));
|
echo json_encode(array('success' => false, 'error' => $db->lasterror()));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Lücken schließen
|
// 3. line_order neu durchnummerieren
|
||||||
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager
|
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
WHERE fk_facture = ".(int)$facture_id."
|
$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id;
|
||||||
ORDER BY line_order";
|
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||||
|
$sql .= " ORDER BY line_order";
|
||||||
$resql = $db->query($sql);
|
$resql = $db->query($sql);
|
||||||
|
|
||||||
$new_order = 1;
|
$new_order = 1;
|
||||||
while ($obj = $db->fetch_object($resql)) {
|
while ($obj = $db->fetch_object($resql)) {
|
||||||
$sql_upd = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager
|
$sql_upd = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager SET line_order = ".$new_order." WHERE rowid = ".(int)$obj->rowid;
|
||||||
SET line_order = ".$new_order."
|
|
||||||
WHERE rowid = ".(int)$obj->rowid;
|
|
||||||
$db->query($sql_upd);
|
$db->query($sql_upd);
|
||||||
$new_order++;
|
$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));
|
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 && file_exists("../../../../main.inc.php")) $res = @include "../../../../main.inc.php";
|
||||||
if (!$res) die("Include of main fails");
|
if (!$res) die("Include of main fails");
|
||||||
require_once __DIR__.'/../lib/subtotaltitle.lib.php';
|
require_once __DIR__.'/../lib/subtotaltitle.lib.php';
|
||||||
|
require_once __DIR__.'/../class/DocumentTypeHelper.class.php';
|
||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
$textline_id = GETPOST('textline_id', 'int');
|
$textline_id = GETPOST('textline_id', 'int');
|
||||||
$text = GETPOST('text', 'restricthtml');
|
$text = GETPOST('text', 'restricthtml');
|
||||||
|
|
||||||
subtotaltitle_debug_log('🔄 edit_textline: id=' . $textline_id);
|
subtotaltitle_debug_log('edit_textline: id=' . $textline_id);
|
||||||
|
|
||||||
if (!$textline_id || !$text) {
|
if (!$textline_id || !$text) {
|
||||||
echo json_encode(array('success' => false, 'error' => 'Missing parameters'));
|
echo json_encode(array('success' => false, 'error' => 'Missing parameters'));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hole erst fk_facturedet (falls Textzeile in Rechnung ist)
|
// Hole erst document_type und FK zur Detail-Tabelle
|
||||||
$sql_get = "SELECT fk_facturedet FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
$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 .= " WHERE rowid = ".(int)$textline_id;
|
||||||
$sql_get .= " AND line_type = 'text'";
|
$sql_get .= " AND line_type = 'text'";
|
||||||
$resql = $db->query($sql_get);
|
$resql = $db->query($sql_get);
|
||||||
$obj = $db->fetch_object($resql);
|
$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
|
// Update Manager-Tabelle
|
||||||
$sql = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager";
|
$sql = "UPDATE ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
|
|
@ -38,15 +56,15 @@ if (!$db->query($sql)) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Falls in facturedet vorhanden, dort auch updaten
|
// Falls in Detail-Tabelle vorhanden, dort auch updaten
|
||||||
if ($fk_facturedet > 0) {
|
if ($fk_detail > 0 && $tables) {
|
||||||
$sql_fd = "UPDATE ".MAIN_DB_PREFIX."facturedet";
|
$sql_fd = "UPDATE ".MAIN_DB_PREFIX.$tables['lines_table'];
|
||||||
$sql_fd .= " SET description = '".$db->escape($text)."'";
|
$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);
|
$db->query($sql_fd);
|
||||||
subtotaltitle_debug_log('✅ Textzeile + facturedet geändert');
|
subtotaltitle_debug_log('Textzeile + Detail geaendert (docType='.$docType.')');
|
||||||
} else {
|
} 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);
|
define('NOTOKENRENEWAL', 1);
|
||||||
require '../../../main.inc.php';
|
require '../../../main.inc.php';
|
||||||
require_once __DIR__.'/../lib/subtotaltitle.lib.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');
|
$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);
|
$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']);
|
echo json_encode(['success' => false, 'error' => 'Missing parameters']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prüfe Rechnungsstatus
|
// Hole die richtigen Tabellennamen fuer diesen Dokumenttyp
|
||||||
$facture = new Facture($db);
|
$tables = DocumentTypeHelper::getTableNames($docType);
|
||||||
$facture->fetch($facture_id);
|
if (!$tables) {
|
||||||
|
echo json_encode(['success' => false, 'error' => 'Invalid document type']);
|
||||||
if ($facture->statut != Facture::STATUS_DRAFT) {
|
|
||||||
echo json_encode(['success' => false, 'error' => 'Rechnung ist nicht im Entwurf']);
|
|
||||||
exit;
|
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();
|
$db->begin();
|
||||||
|
|
||||||
|
|
@ -31,24 +43,25 @@ $deleted = 0;
|
||||||
foreach ($line_ids as $line_id) {
|
foreach ($line_ids as $line_id) {
|
||||||
$line_id = (int)$line_id;
|
$line_id = (int)$line_id;
|
||||||
|
|
||||||
// Aus facturedet löschen
|
// Aus Detail-Tabelle loeschen
|
||||||
$sql = "DELETE FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid = ".$line_id;
|
$sql = "DELETE FROM ".MAIN_DB_PREFIX.$tables['lines_table']." WHERE rowid = ".$line_id;
|
||||||
if ($db->query($sql)) {
|
if ($db->query($sql)) {
|
||||||
$deleted++;
|
$deleted++;
|
||||||
subtotaltitle_debug_log('✅ Zeile gelöscht: ' . $line_id);
|
subtotaltitle_debug_log('Zeile geloescht: ' . $line_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aus Manager-Tabelle löschen
|
// Aus Manager-Tabelle loeschen
|
||||||
$sql_manager = "DELETE FROM ".MAIN_DB_PREFIX."facture_lines_manager WHERE fk_facturedet = ".$line_id;
|
$sql_manager = "DELETE FROM ".MAIN_DB_PREFIX."facture_lines_manager WHERE ".$tables['fk_line']." = ".$line_id;
|
||||||
$db->query($sql_manager);
|
$db->query($sql_manager);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Summen neu berechnen
|
// Summen neu berechnen
|
||||||
$facture->update_price(1);
|
$object->update_price(1);
|
||||||
|
|
||||||
// line_order neu durchnummerieren
|
// line_order neu durchnummerieren
|
||||||
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
$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";
|
$sql .= " ORDER BY line_order";
|
||||||
$resql = $db->query($sql);
|
$resql = $db->query($sql);
|
||||||
|
|
||||||
|
|
@ -60,21 +73,24 @@ while ($obj = $db->fetch_object($resql)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// rang synchronisieren
|
// rang synchronisieren
|
||||||
$sql = "SELECT fk_facturedet FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
$sql = "SELECT ".$tables['fk_line']." as detail_id FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
$sql .= " WHERE fk_facture = ".(int)$facture_id;
|
$sql .= " WHERE ".$tables['fk_parent']." = ".(int)$document_id;
|
||||||
$sql .= " AND line_type = 'product'";
|
$sql .= " AND document_type = '".$db->escape($docType)."'";
|
||||||
|
$sql .= " AND ".$tables['fk_line']." IS NOT NULL";
|
||||||
$sql .= " ORDER BY line_order";
|
$sql .= " ORDER BY line_order";
|
||||||
$resql = $db->query($sql);
|
$resql = $db->query($sql);
|
||||||
|
|
||||||
$rang = 1;
|
$rang = 1;
|
||||||
while ($obj = $db->fetch_object($resql)) {
|
while ($obj = $db->fetch_object($resql)) {
|
||||||
$sql_upd = "UPDATE ".MAIN_DB_PREFIX."facturedet SET rang = ".$rang." WHERE rowid = ".(int)$obj->fk_facturedet;
|
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);
|
$db->query($sql_upd);
|
||||||
$rang++;
|
$rang++;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$db->commit();
|
$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]);
|
echo json_encode(['success' => true, 'deleted' => $deleted]);
|
||||||
|
|
@ -122,4 +122,58 @@ class DocumentTypeHelper
|
||||||
|
|
||||||
return isset($contexts[$type]) ? $contexts[$type] : '';
|
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;
|
global $db;
|
||||||
$tables = DocumentTypeHelper::getTableNames($docType);
|
$tables = DocumentTypeHelper::getTableNames($docType);
|
||||||
if (!$tables) return "";
|
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
|
// Lade Übersetzungen
|
||||||
$langs->load('subtotaltitle@subtotaltitle');
|
$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
|
// CSS
|
||||||
$cssPath = dol_buildpath('/custom/subtotaltitle/css/subtotaltitle.css', 1);
|
$cssPath = dol_buildpath('/custom/subtotaltitle/css/subtotaltitle.css', 1);
|
||||||
echo '<link rel="stylesheet" type="text/css" href="'.$cssPath.'">'."\n";
|
echo '<link rel="stylesheet" type="text/css" href="'.$cssPath.'">'."\n";
|
||||||
|
|
@ -148,6 +164,9 @@ class ActionsSubtotalTitle extends CommonHookActions
|
||||||
// Übersetzungen als JavaScript-Variablen bereitstellen
|
// Übersetzungen als JavaScript-Variablen bereitstellen
|
||||||
echo '<script type="text/javascript">'."\n";
|
echo '<script type="text/javascript">'."\n";
|
||||||
echo 'var subtotalTitleIsDraft = '.($this->isDraft ? 'true' : 'false').';'."\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 'var subtotalTitleLang = {'."\n";
|
||||||
echo ' buttonCreateTextline: '.json_encode($langs->trans('ButtonCreateTextline')).','."\n";
|
echo ' buttonCreateTextline: '.json_encode($langs->trans('ButtonCreateTextline')).','."\n";
|
||||||
echo ' buttonToInvoice: '.json_encode($langs->trans('ButtonToInvoice')).','."\n";
|
echo ' buttonToInvoice: '.json_encode($langs->trans('ButtonToInvoice')).','."\n";
|
||||||
|
|
@ -230,14 +249,14 @@ class ActionsSubtotalTitle extends CommonHookActions
|
||||||
// Sync-Buttons + Collapse-Buttons - rechts ausgerichtet
|
// Sync-Buttons + Collapse-Buttons - rechts ausgerichtet
|
||||||
echo '<script>$(document).ready(function() {';
|
echo '<script>$(document).ready(function() {';
|
||||||
echo ' if ($(".sync-collapse-row").length === 0) {';
|
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 ' 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="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 ' buttons += \'<a class="button" href="#" onclick="removeAllFromFacturedet(); return false;" style="margin-left:5px;">\' + subtotalTitleLang.buttonFromInvoice + \'</a>\';';
|
||||||
echo ' if (hasCollapse) {';
|
// 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="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 += \'<a class="button" href="#" onclick="collapseAllSections(); return false;" style="margin-left:5px;">\' + subtotalTitleLang.buttonCollapseAll + \'</a>\';';
|
||||||
echo ' }';
|
}
|
||||||
echo ' buttons += \'</div>\';';
|
echo ' buttons += \'</div>\';';
|
||||||
echo ' $(".tabsAction").first().after(buttons);';
|
echo ' $(".tabsAction").first().after(buttons);';
|
||||||
echo ' }';
|
echo ' }';
|
||||||
|
|
@ -261,9 +280,68 @@ class ActionsSubtotalTitle extends CommonHookActions
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overload the doActions function
|
* Overload the doActions function
|
||||||
|
* Reagiert auf Lösch-Aktionen um die Manager-Tabelle zu aktualisieren
|
||||||
*/
|
*/
|
||||||
public function doActions($parameters, &$object, &$action, $hookmanager)
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -626,20 +704,24 @@ class ActionsSubtotalTitle extends CommonHookActions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hole ALLE Sections ZWISCHEN letztem rang und aktuellem rang
|
// Hole ALLE Sections und Textzeilen die VOR dieser Produktzeile kommen
|
||||||
$sql = "SELECT DISTINCT s.rowid, s.title, s.show_subtotal, s.collapsed, s.line_order, s.in_facturedet,";
|
// Kombiniert nach line_order sortiert, damit Textzeilen VOR Sections erscheinen können
|
||||||
$sql .= " (SELECT MIN(fd.rang) FROM ".MAIN_DB_PREFIX."facture_lines_manager m2";
|
$sql_combined = "SELECT rowid, title, line_type, line_order, show_subtotal, collapsed, in_facturedet,";
|
||||||
$sql .= " INNER JOIN ".MAIN_DB_PREFIX.$tables['lines_table']." fd ON fd.rowid = m2.".$tables['fk_line'];
|
$sql_combined .= " (SELECT MIN(fd.rang) FROM ".MAIN_DB_PREFIX."facture_lines_manager m2";
|
||||||
$sql .= " WHERE m2.parent_section = s.rowid AND m2.document_type = '".$db->escape($docType)."') as first_product_rang";
|
$sql_combined .= " INNER JOIN ".MAIN_DB_PREFIX.$tables['lines_table']." fd ON fd.rowid = m2.".$tables['fk_line'];
|
||||||
$sql .= " FROM ".MAIN_DB_PREFIX."facture_lines_manager s";
|
$sql_combined .= " WHERE m2.parent_section = ".MAIN_DB_PREFIX."facture_lines_manager.rowid";
|
||||||
$sql .= $this->getDocumentWhere($document_id, $docType, 's');
|
$sql_combined .= " AND m2.document_type = '".$db->escape($docType)."') as first_product_rang";
|
||||||
$sql .= " AND s.line_type = 'section'";
|
$sql_combined .= " FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
||||||
$sql .= " HAVING first_product_rang > ".(int)$last_rang[$doc_key];
|
$sql_combined .= $this->getDocumentWhere($document_id, $docType, '');
|
||||||
$sql .= " AND first_product_rang <= ".(int)$current_rang;
|
$sql_combined .= " AND (line_type = 'section' OR line_type = 'text')";
|
||||||
$sql .= " ORDER BY first_product_rang";
|
$sql_combined .= " AND line_order < ".(int)$current_line_order;
|
||||||
$resql = $db->query($sql);
|
$sql_combined .= " ORDER BY line_order";
|
||||||
|
$resql_combined = $db->query($sql_combined);
|
||||||
|
|
||||||
while ($obj = $db->fetch_object($resql)) {
|
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])) {
|
if (!in_array($obj->rowid, self::$rendered_sections[$doc_key])) {
|
||||||
$section = array(
|
$section = array(
|
||||||
'section_id' => $obj->rowid,
|
'section_id' => $obj->rowid,
|
||||||
|
|
@ -656,30 +738,21 @@ class ActionsSubtotalTitle extends CommonHookActions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} elseif ($obj->line_type == 'text') {
|
||||||
// Rendere Textzeilen die VOR dieser Produktzeile kommen
|
// Textzeile rendern
|
||||||
$sql_text = "SELECT rowid, title, line_order, in_facturedet FROM ".MAIN_DB_PREFIX."facture_lines_manager";
|
$text_key = 'text_'.$obj->rowid;
|
||||||
$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;
|
|
||||||
if (!in_array($text_key, self::$rendered_sections[$doc_key])) {
|
if (!in_array($text_key, self::$rendered_sections[$doc_key])) {
|
||||||
$textline = array(
|
$textline = array(
|
||||||
'id' => $obj_text->rowid,
|
'id' => $obj->rowid,
|
||||||
'title' => $obj_text->title,
|
'title' => $obj->title,
|
||||||
'line_order' => $obj_text->line_order,
|
'line_order' => $obj->line_order,
|
||||||
'in_facturedet' => $obj_text->in_facturedet
|
'in_facturedet' => $obj->in_facturedet
|
||||||
);
|
);
|
||||||
echo $this->renderTextLine($textline);
|
echo $this->renderTextLine($textline);
|
||||||
self::$rendered_sections[$doc_key][] = $text_key;
|
self::$rendered_sections[$doc_key][] = $text_key;
|
||||||
|
|
||||||
if ($this->debug) {
|
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'].'">';
|
$html = '<tr class="textline-row drag'.$in_class.'" data-textline-id="'.$textline['id'].'" data-line-order="'.$textline['line_order'].'">';
|
||||||
|
|
||||||
// Inhalt (colspan=10)
|
// Titel (colspan=6) - wie bei Sections
|
||||||
$html .= '<td colspan="10" style="padding:8px; font-weight:bold;">';
|
$html .= '<td colspan="6" style="padding:8px; font-weight:bold;">';
|
||||||
$html .= htmlspecialchars($textline['title']);
|
$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) {
|
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 .= '<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 .= ' 📄</label>';
|
||||||
}
|
}
|
||||||
|
|
||||||
$html .= '</td>';
|
$html .= '</td>';
|
||||||
|
|
||||||
|
// Leer (colspan=2) - Platzhalter wie bei Sections für Move-Buttons
|
||||||
|
$html .= '<td colspan="2"></td>';
|
||||||
|
|
||||||
// Edit (Spalte 11) - nur im Entwurfsstatus
|
// Edit (Spalte 11) - nur im Entwurfsstatus
|
||||||
$html .= '<td class="linecoledit center">';
|
$html .= '<td class="linecoledit center">';
|
||||||
if ($this->isDraft) {
|
if ($this->isDraft) {
|
||||||
|
|
|
||||||
|
|
@ -78,8 +78,11 @@ tr.textline-row {
|
||||||
|
|
||||||
tr.textline-row .linecolmove {
|
tr.textline-row .linecolmove {
|
||||||
cursor: move;
|
cursor: move;
|
||||||
|
min-width: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Drag-Handle wird über JavaScript als background-image gesetzt (wie Dolibarr) */
|
||||||
|
|
||||||
tr.textline-row:hover {
|
tr.textline-row:hover {
|
||||||
background-color: #f5f5f5 !important;
|
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)
|
* Seite neu laden ohne POST-Warnung (GET-Request)
|
||||||
*/
|
*/
|
||||||
|
|
@ -101,15 +280,18 @@ function getFactureId() {
|
||||||
*/
|
*/
|
||||||
function createNewSection() {
|
function createNewSection() {
|
||||||
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
||||||
var title = prompt(lang.sectionName || 'Neue Positionsgruppe - Name eingeben:');
|
|
||||||
if (!title) return;
|
|
||||||
|
|
||||||
var docInfo = getDocumentInfo();
|
var docInfo = getDocumentInfo();
|
||||||
if (!docInfo.id) {
|
if (!docInfo.id) {
|
||||||
alert(lang.errorLoadingSections || 'Fehler: Keine Dokument-ID gefunden');
|
showErrorAlert(lang.errorLoadingSections || 'Fehler: Keine Dokument-ID gefunden');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showInputDialog(
|
||||||
|
lang.sectionCreate || 'Positionsgruppe erstellen',
|
||||||
|
lang.sectionName || 'Name der Positionsgruppe:',
|
||||||
|
'',
|
||||||
|
function(title) {
|
||||||
debugLog('Erstelle Section: ' + title + ' für ' + docInfo.type + ' ID ' + docInfo.id);
|
debugLog('Erstelle Section: ' + title + ' für ' + docInfo.type + ' ID ' + docInfo.id);
|
||||||
|
|
||||||
$.post('/dolibarr/custom/subtotaltitle/ajax/create_section.php', {
|
$.post('/dolibarr/custom/subtotaltitle/ajax/create_section.php', {
|
||||||
|
|
@ -121,12 +303,16 @@ function createNewSection() {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
window.location.href = window.location.pathname + window.location.search;
|
window.location.href = window.location.pathname + window.location.search;
|
||||||
} else {
|
} else {
|
||||||
alert((lang.errorSavingSection || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
showErrorAlert((lang.errorSavingSection || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||||
}
|
}
|
||||||
}, 'json').fail(function(xhr, status, error) {
|
}, 'json').fail(function(xhr, status, error) {
|
||||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||||
alert((lang.errorSavingSection || 'Fehler beim Erstellen') + ': ' + error);
|
showErrorAlert((lang.errorSavingSection || 'Fehler beim Erstellen') + ': ' + error);
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
lang.buttonSave || 'Erstellen',
|
||||||
|
lang.buttonCancel || 'Abbrechen'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -163,9 +349,12 @@ function moveSection(sectionId, direction) {
|
||||||
*/
|
*/
|
||||||
function renameSection(sectionId, currentTitle) {
|
function renameSection(sectionId, currentTitle) {
|
||||||
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
||||||
var newTitle = prompt(lang.sectionName || 'Positionsgruppe umbenennen:', currentTitle);
|
|
||||||
if (!newTitle) return;
|
|
||||||
|
|
||||||
|
showInputDialog(
|
||||||
|
lang.buttonEdit || 'Positionsgruppe umbenennen',
|
||||||
|
lang.sectionName || 'Name der Positionsgruppe:',
|
||||||
|
currentTitle || '',
|
||||||
|
function(newTitle) {
|
||||||
debugLog('✏️ Benenne Section ' + sectionId + ' um zu: ' + newTitle);
|
debugLog('✏️ Benenne Section ' + sectionId + ' um zu: ' + newTitle);
|
||||||
|
|
||||||
$.post('/dolibarr/custom/subtotaltitle/ajax/rename_section.php', {
|
$.post('/dolibarr/custom/subtotaltitle/ajax/rename_section.php', {
|
||||||
|
|
@ -176,12 +365,16 @@ function renameSection(sectionId, currentTitle) {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
window.location.href = window.location.pathname + window.location.search;
|
window.location.href = window.location.pathname + window.location.search;
|
||||||
} else {
|
} else {
|
||||||
alert((lang.errorSavingSection || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
showErrorAlert((lang.errorSavingSection || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||||
}
|
}
|
||||||
}, 'json').fail(function(xhr, status, error) {
|
}, 'json').fail(function(xhr, status, error) {
|
||||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||||
alert((lang.errorSavingSection || 'Fehler beim Umbenennen') + ': ' + 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) {
|
function deleteSection(sectionId) {
|
||||||
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
||||||
if (!confirm(lang.confirmDeleteSection || 'Leere Positionsgruppe löschen?')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
showConfirmDialog(
|
||||||
|
'Positionsgruppe löschen',
|
||||||
|
lang.confirmDeleteSection || 'Leere Positionsgruppe löschen?',
|
||||||
|
function() {
|
||||||
debugLog('🗑️ Lösche leere Section ' + sectionId);
|
debugLog('🗑️ Lösche leere Section ' + sectionId);
|
||||||
|
|
||||||
$.post('/dolibarr/custom/subtotaltitle/ajax/delete_section.php', {
|
$.post('/dolibarr/custom/subtotaltitle/ajax/delete_section.php', {
|
||||||
section_id: sectionId,
|
section_id: sectionId,
|
||||||
force: 0 // Nur leere löschen
|
force: 0,
|
||||||
|
document_type: getDocumentType()
|
||||||
}, function(response) {
|
}, function(response) {
|
||||||
debugLog('Delete response: ' + JSON.stringify(response));
|
debugLog('Delete response: ' + JSON.stringify(response));
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
window.location.href = window.location.pathname + window.location.search;
|
window.location.href = window.location.pathname + window.location.search;
|
||||||
} else {
|
} else {
|
||||||
alert((lang.errorDeletingSection || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
showErrorAlert((lang.errorDeletingSection || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||||
}
|
}
|
||||||
}, 'json').fail(function(xhr, status, error) {
|
}, 'json').fail(function(xhr, status, error) {
|
||||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||||
alert((lang.errorDeletingSection || 'Fehler beim Löschen') + ': ' + 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 productCount = $row.data('product-count');
|
||||||
var productIds = $row.data('product-ids') || [];
|
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);
|
var msg1 = (lang.confirmDeleteSectionForce || 'Wollen Sie wirklich die Positionsgruppe UND alle %s enthaltenen Produkte löschen?').replace('%s', productCount);
|
||||||
if (!confirm(msg1)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var msg2 = (lang.confirmDeleteSectionForce2 || 'Sind Sie WIRKLICH sicher?\n\n%s Produkte werden unwiderruflich gelöscht!').replace('%s', productCount);
|
showConfirmDialog(
|
||||||
if (!confirm(msg2)) {
|
'Achtung - Positionsgruppe löschen',
|
||||||
return;
|
'<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', {
|
$.post('/dolibarr/custom/subtotaltitle/ajax/delete_section.php', {
|
||||||
section_id: sectionId,
|
section_id: sectionId,
|
||||||
force: 1,
|
force: 1,
|
||||||
product_ids: JSON.stringify(productIds)
|
product_ids: JSON.stringify(productIds),
|
||||||
|
document_type: getDocumentType()
|
||||||
}, function(response) {
|
}, function(response) {
|
||||||
debugLog('Force-Delete response: ' + JSON.stringify(response));
|
debugLog('Force-Delete response: ' + JSON.stringify(response));
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
window.location.href = window.location.pathname + window.location.search;
|
window.location.href = window.location.pathname + window.location.search;
|
||||||
} else {
|
} else {
|
||||||
alert((lang.errorDeletingSection || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
showErrorAlert((lang.errorDeletingSection || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||||
}
|
}
|
||||||
}, 'json').fail(function(xhr, status, error) {
|
}, 'json').fail(function(xhr, status, error) {
|
||||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||||
alert((lang.errorDeletingSection || 'Fehler beim Löschen') + ': ' + 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
|
* Fügt leere Sections an die richtige Stelle in der Tabelle ein
|
||||||
|
|
@ -971,6 +1181,16 @@ function reinitTableDnD() {
|
||||||
var $table = $('#tablelines');
|
var $table = $('#tablelines');
|
||||||
if ($table.length && typeof $.fn.tableDnD !== 'undefined') {
|
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
|
// Neu initialisieren
|
||||||
$table.tableDnD({
|
$table.tableDnD({
|
||||||
onDragClass: 'myDragClass',
|
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');
|
debugLog('✅ tableDnD neu initialisiert');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1063,15 +1289,18 @@ function insertTextLine(textline) {
|
||||||
*/
|
*/
|
||||||
function createTextLine() {
|
function createTextLine() {
|
||||||
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
||||||
var text = prompt(lang.textlineContent || 'Text eingeben:');
|
|
||||||
if (!text) return;
|
|
||||||
|
|
||||||
var docInfo = getDocumentInfo();
|
var docInfo = getDocumentInfo();
|
||||||
if (!docInfo.id) {
|
if (!docInfo.id) {
|
||||||
alert(lang.errorLoadingSections || 'Fehler: Keine Dokument-ID gefunden');
|
showErrorAlert(lang.errorLoadingSections || 'Fehler: Keine Dokument-ID gefunden');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showInputDialog(
|
||||||
|
lang.buttonCreateTextline || 'Textzeile erstellen',
|
||||||
|
lang.textlineContent || 'Text eingeben:',
|
||||||
|
'',
|
||||||
|
function(text) {
|
||||||
debugLog('Erstelle Textzeile für ' + docInfo.type + ' ID ' + docInfo.id);
|
debugLog('Erstelle Textzeile für ' + docInfo.type + ' ID ' + docInfo.id);
|
||||||
|
|
||||||
$.post('/dolibarr/custom/subtotaltitle/ajax/create_textline.php', {
|
$.post('/dolibarr/custom/subtotaltitle/ajax/create_textline.php', {
|
||||||
|
|
@ -1083,12 +1312,16 @@ function createTextLine() {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
window.location.href = window.location.pathname + window.location.search;
|
window.location.href = window.location.pathname + window.location.search;
|
||||||
} else {
|
} else {
|
||||||
alert((lang.errorSavingTextline || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
showErrorAlert((lang.errorSavingTextline || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||||
}
|
}
|
||||||
}, 'json').fail(function(xhr, status, error) {
|
}, 'json').fail(function(xhr, status, error) {
|
||||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||||
alert((lang.errorSavingTextline || 'Fehler beim Erstellen') + ': ' + error);
|
showErrorAlert((lang.errorSavingTextline || 'Fehler beim Erstellen') + ': ' + error);
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
lang.buttonSave || 'Erstellen',
|
||||||
|
lang.buttonCancel || 'Abbrechen'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1096,9 +1329,12 @@ function createTextLine() {
|
||||||
*/
|
*/
|
||||||
function editTextLine(textlineId, currentText) {
|
function editTextLine(textlineId, currentText) {
|
||||||
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
||||||
var newText = prompt(lang.textlineContent || 'Text bearbeiten:', currentText || '');
|
|
||||||
if (!newText) return;
|
|
||||||
|
|
||||||
|
showInputDialog(
|
||||||
|
lang.buttonEdit || 'Textzeile bearbeiten',
|
||||||
|
lang.textlineContent || 'Text:',
|
||||||
|
currentText || '',
|
||||||
|
function(newText) {
|
||||||
debugLog('✏️ Bearbeite Textzeile ' + textlineId);
|
debugLog('✏️ Bearbeite Textzeile ' + textlineId);
|
||||||
|
|
||||||
$.post('/dolibarr/custom/subtotaltitle/ajax/edit_textline.php', {
|
$.post('/dolibarr/custom/subtotaltitle/ajax/edit_textline.php', {
|
||||||
|
|
@ -1109,12 +1345,16 @@ function editTextLine(textlineId, currentText) {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
window.location.href = window.location.pathname + window.location.search;
|
window.location.href = window.location.pathname + window.location.search;
|
||||||
} else {
|
} else {
|
||||||
alert((lang.errorSavingTextline || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
showErrorAlert((lang.errorSavingTextline || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||||
}
|
}
|
||||||
}, 'json').fail(function(xhr, status, error) {
|
}, 'json').fail(function(xhr, status, error) {
|
||||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||||
alert((lang.errorSavingTextline || 'Fehler beim Bearbeiten') + ': ' + 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) {
|
function deleteTextLine(textlineId) {
|
||||||
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
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', {
|
$.post('/dolibarr/custom/subtotaltitle/ajax/delete_textline.php', {
|
||||||
textline_id: textlineId
|
textline_id: textlineId,
|
||||||
|
document_type: getDocumentType()
|
||||||
}, function(response) {
|
}, function(response) {
|
||||||
debugLog('Delete response: ' + JSON.stringify(response));
|
debugLog('Delete response: ' + JSON.stringify(response));
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
window.location.href = window.location.pathname + window.location.search;
|
window.location.href = window.location.pathname + window.location.search;
|
||||||
} else {
|
} else {
|
||||||
alert((lang.errorDeletingTextline || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
showErrorAlert((lang.errorDeletingTextline || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||||
}
|
}
|
||||||
}, 'json').fail(function(xhr, status, error) {
|
}, 'json').fail(function(xhr, status, error) {
|
||||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||||
alert((lang.errorDeletingTextline || 'Fehler beim Löschen') + ': ' + error);
|
showErrorAlert((lang.errorDeletingTextline || 'Fehler beim Löschen') + ': ' + error);
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
'Ja, löschen',
|
||||||
|
'Abbrechen'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1148,12 +1394,13 @@ function deleteTextLine(textlineId) {
|
||||||
*/
|
*/
|
||||||
function removeFromSection(productId) {
|
function removeFromSection(productId) {
|
||||||
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
||||||
if (!confirm(lang.confirmRemoveFromSection || 'Produkt aus Positionsgruppe entfernen?')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
showConfirmDialog(
|
||||||
|
'Aus Positionsgruppe entfernen',
|
||||||
|
lang.confirmRemoveFromSection || 'Produkt aus Positionsgruppe entfernen?',
|
||||||
|
function() {
|
||||||
var docType = getDocumentType();
|
var docType = getDocumentType();
|
||||||
debugLog('🔓 Entferne Produkt ' + productId + ' aus Section (docType: ' + docType + ')');
|
debugLog('Entferne Produkt ' + productId + ' aus Section (docType: ' + docType + ')');
|
||||||
|
|
||||||
$.post('/dolibarr/custom/subtotaltitle/ajax/remove_from_section.php', {
|
$.post('/dolibarr/custom/subtotaltitle/ajax/remove_from_section.php', {
|
||||||
product_id: productId,
|
product_id: productId,
|
||||||
|
|
@ -1163,12 +1410,16 @@ function removeFromSection(productId) {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
safeReload();
|
safeReload();
|
||||||
} else {
|
} else {
|
||||||
alert((lang.errorReordering || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
showErrorAlert((lang.errorReordering || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||||
}
|
}
|
||||||
}, 'json').fail(function(xhr, status, error) {
|
}, 'json').fail(function(xhr, status, error) {
|
||||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||||
alert((lang.errorReordering || 'Fehler') + ': ' + error);
|
showErrorAlert((lang.errorReordering || 'Fehler') + ': ' + error);
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
'Ja, entfernen',
|
||||||
|
'Abbrechen'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleMassDelete() {
|
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(
|
$('#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="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
|
// Original-Button verstecken
|
||||||
|
|
@ -1217,30 +1468,31 @@ function deleteMassSelected() {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (selectedIds.length === 0) {
|
if (selectedIds.length === 0) {
|
||||||
alert(lang.noLinesSelected || 'Keine Zeilen ausgewählt!');
|
showErrorAlert(lang.noLinesSelected || 'Keine Zeilen ausgewählt!');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var msg1 = (lang.confirmDeleteLines || 'Wirklich %s Zeilen löschen?').replace('%s', selectedIds.length);
|
var msg1 = (lang.confirmDeleteLines || 'Wirklich %s Zeilen löschen?').replace('%s', selectedIds.length);
|
||||||
if (!confirm(msg1)) {
|
|
||||||
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);
|
var msg2 = (lang.confirmDeleteLinesWarning || 'LETZTE WARNUNG: %s Zeilen werden UNWIDERRUFLICH gelöscht!').replace('%s', selectedIds.length);
|
||||||
if (!confirm(msg2)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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', {
|
$.post('/dolibarr/custom/subtotaltitle/ajax/mass_delete.php', {
|
||||||
line_ids: JSON.stringify(selectedIds),
|
line_ids: JSON.stringify(selectedIds),
|
||||||
facture_id: getFactureId()
|
facture_id: getFactureId(),
|
||||||
|
document_type: getDocumentType()
|
||||||
}, function(response) {
|
}, function(response) {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
safeReload();
|
safeReload();
|
||||||
} else {
|
} else {
|
||||||
alert((lang.errorDeletingSection || 'Fehler') + ': ' + (response.error || 'Unbekannt'));
|
showErrorAlert((lang.errorDeletingSection || 'Fehler') + ': ' + (response.error || 'Unbekannt'));
|
||||||
}
|
}
|
||||||
}, 'json');
|
}, 'json');
|
||||||
|
}, 'Endgültig löschen', 'Abbrechen');
|
||||||
|
}, 'Ja, löschen', 'Abbrechen');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFactureId() {
|
function getFactureId() {
|
||||||
|
|
@ -1396,16 +1648,16 @@ function linkToNearestSection(lineId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!targetSection) {
|
if (!targetSection) {
|
||||||
alert('Fehler: Keine passende Section gefunden');
|
showErrorAlert('Keine passende Positionsgruppe gefunden');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bestätigung
|
// Bestätigung
|
||||||
if (!confirm('Produkt zur Positionsgruppe "' + targetSection.title + '" hinzufügen?')) {
|
showConfirmDialog(
|
||||||
return;
|
'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
|
// AJAX Call zum Backend
|
||||||
$.post('/dolibarr/custom/subtotaltitle/ajax/add_to_section.php', {
|
$.post('/dolibarr/custom/subtotaltitle/ajax/add_to_section.php', {
|
||||||
|
|
@ -1414,17 +1666,20 @@ function linkToNearestSection(lineId) {
|
||||||
document_id: docInfo.id,
|
document_id: docInfo.id,
|
||||||
document_type: docInfo.type
|
document_type: docInfo.type
|
||||||
}, function(response) {
|
}, function(response) {
|
||||||
debugLog(' → Response: ' + JSON.stringify(response));
|
debugLog(' Response: ' + JSON.stringify(response));
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
// Reload Page
|
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
} else {
|
} else {
|
||||||
alert('Fehler: ' + (response.error || 'Unbekannter Fehler'));
|
showErrorAlert('Fehler: ' + (response.error || 'Unbekannter Fehler'));
|
||||||
}
|
}
|
||||||
}, 'json').fail(function(xhr, status, error) {
|
}, 'json').fail(function(xhr, status, error) {
|
||||||
console.error('AJAX Fehler:', status, error);
|
console.error('AJAX Fehler:', status, error);
|
||||||
console.error('Response:', xhr.responseText);
|
console.error('Response:', xhr.responseText);
|
||||||
alert('Fehler beim Verknüpfen: ' + xhr.responseText);
|
showErrorAlert('Fehler beim Verknüpfen: ' + xhr.responseText);
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
'Ja, hinzufügen',
|
||||||
|
'Abbrechen'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,13 +38,13 @@ function syncToFacturedet(lineId, lineType) {
|
||||||
updateSyncCheckbox(lineId, true);
|
updateSyncCheckbox(lineId, true);
|
||||||
debugLog('✅ Zeile zu Rechnung hinzugefügt');
|
debugLog('✅ Zeile zu Rechnung hinzugefügt');
|
||||||
} else {
|
} else {
|
||||||
alert((lang.errorSyncing || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
showErrorAlert((lang.errorSyncing || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||||
// Checkbox zurücksetzen
|
// Checkbox zurücksetzen
|
||||||
$('.sync-checkbox[data-line-id="' + lineId + '"]').prop('checked', false);
|
$('.sync-checkbox[data-line-id="' + lineId + '"]').prop('checked', false);
|
||||||
}
|
}
|
||||||
}, 'json').fail(function(xhr, status, error) {
|
}, 'json').fail(function(xhr, status, error) {
|
||||||
debugLog('AJAX Fehler: ' + 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);
|
$('.sync-checkbox[data-line-id="' + lineId + '"]').prop('checked', false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -54,12 +54,11 @@ function syncToFacturedet(lineId, lineType) {
|
||||||
*/
|
*/
|
||||||
function removeFromFacturedet(lineId, lineType) {
|
function removeFromFacturedet(lineId, lineType) {
|
||||||
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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);
|
debugLog('📥 Remove from facturedet: ' + lineType + ' #' + lineId);
|
||||||
|
|
||||||
var docType = getDocumentTypeForSync();
|
var docType = getDocumentTypeForSync();
|
||||||
|
|
@ -76,14 +75,21 @@ function removeFromFacturedet(lineId, lineType) {
|
||||||
updateSyncCheckbox(lineId, false);
|
updateSyncCheckbox(lineId, false);
|
||||||
debugLog('✅ Zeile aus Rechnung entfernt');
|
debugLog('✅ Zeile aus Rechnung entfernt');
|
||||||
} else {
|
} else {
|
||||||
alert((lang.errorSyncing || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
showErrorAlert((lang.errorSyncing || 'Fehler') + ': ' + (response.error || 'Unbekannter Fehler'));
|
||||||
$('.sync-checkbox[data-line-id="' + lineId + '"]').prop('checked', true);
|
$('.sync-checkbox[data-line-id="' + lineId + '"]').prop('checked', true);
|
||||||
}
|
}
|
||||||
}, 'json').fail(function(xhr, status, error) {
|
}, 'json').fail(function(xhr, status, error) {
|
||||||
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
debugLog('AJAX Fehler: ' + status + ' ' + error);
|
||||||
alert((lang.errorSyncing || 'Fehler') + ': ' + error);
|
showErrorAlert((lang.errorSyncing || 'Fehler') + ': ' + error);
|
||||||
$('.sync-checkbox[data-line-id="' + lineId + '"]').prop('checked', true);
|
$('.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,22 +125,23 @@ function updateSyncCheckbox(lineId, isInFacturedet) {
|
||||||
*/
|
*/
|
||||||
function syncAllToFacturedet() {
|
function syncAllToFacturedet() {
|
||||||
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
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 $unchecked = $('.sync-checkbox:not(:checked)');
|
||||||
var total = $unchecked.length;
|
var total = $unchecked.length;
|
||||||
var done = 0;
|
|
||||||
var errors = 0;
|
|
||||||
|
|
||||||
if (total === 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showConfirmDialog(
|
||||||
|
'Alle zur Rechnung hinzufügen',
|
||||||
|
lang.confirmSyncAll || 'Alle Positionsgruppen-Elemente (Sections, Textzeilen, Zwischensummen) zur Rechnung hinzufügen?',
|
||||||
|
function() {
|
||||||
|
debugLog('📤 Sync ALL to facturedet...');
|
||||||
|
|
||||||
|
var done = 0;
|
||||||
|
var errors = 0;
|
||||||
var docType = getDocumentTypeForSync();
|
var docType = getDocumentTypeForSync();
|
||||||
|
|
||||||
$unchecked.each(function() {
|
$unchecked.each(function() {
|
||||||
|
|
@ -156,12 +163,9 @@ function syncAllToFacturedet() {
|
||||||
if (done >= total) {
|
if (done >= total) {
|
||||||
debugLog('✅ Sync abgeschlossen: ' + (total - errors) + ' erfolgreich, ' + errors + ' Fehler');
|
debugLog('✅ Sync abgeschlossen: ' + (total - errors) + ' erfolgreich, ' + errors + ' Fehler');
|
||||||
if (errors > 0) {
|
if (errors > 0) {
|
||||||
var msg = (lang.elementsAddedWithErrors || '%s von %s Elementen hinzugefügt.\n%s Fehler aufgetreten.')
|
showErrorAlert((total - errors) + ' von ' + total + ' Elementen hinzugefügt. ' + errors + ' Fehler aufgetreten.');
|
||||||
.replace('%s', total - errors).replace('%s', total).replace('%s', errors);
|
|
||||||
alert(msg);
|
|
||||||
} else {
|
} else {
|
||||||
var msg = (lang.elementsAddedToInvoice || '%s Elemente zur Rechnung hinzugefügt.').replace('%s', total);
|
safeReload();
|
||||||
alert(msg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 'json').fail(function() {
|
}, 'json').fail(function() {
|
||||||
|
|
@ -169,6 +173,10 @@ function syncAllToFacturedet() {
|
||||||
errors++;
|
errors++;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
'Ja, hinzufügen',
|
||||||
|
'Abbrechen'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -176,22 +184,23 @@ function syncAllToFacturedet() {
|
||||||
*/
|
*/
|
||||||
function removeAllFromFacturedet() {
|
function removeAllFromFacturedet() {
|
||||||
var lang = (typeof subtotalTitleLang !== 'undefined') ? subtotalTitleLang : {};
|
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 $checked = $('.sync-checkbox:checked');
|
||||||
var total = $checked.length;
|
var total = $checked.length;
|
||||||
var done = 0;
|
|
||||||
var errors = 0;
|
|
||||||
|
|
||||||
if (total === 0) {
|
if (total === 0) {
|
||||||
alert(lang.noElementsInInvoice || 'Keine Elemente in der Rechnung vorhanden.');
|
showErrorAlert(lang.noElementsInInvoice || 'Keine Elemente in der Rechnung vorhanden.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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...');
|
||||||
|
|
||||||
|
var done = 0;
|
||||||
|
var errors = 0;
|
||||||
var docType = getDocumentTypeForSync();
|
var docType = getDocumentTypeForSync();
|
||||||
|
|
||||||
$checked.each(function() {
|
$checked.each(function() {
|
||||||
|
|
@ -213,12 +222,9 @@ function removeAllFromFacturedet() {
|
||||||
if (done >= total) {
|
if (done >= total) {
|
||||||
debugLog('✅ Remove abgeschlossen: ' + (total - errors) + ' erfolgreich, ' + errors + ' Fehler');
|
debugLog('✅ Remove abgeschlossen: ' + (total - errors) + ' erfolgreich, ' + errors + ' Fehler');
|
||||||
if (errors > 0) {
|
if (errors > 0) {
|
||||||
var msg = (lang.elementsRemovedWithErrors || '%s von %s Elementen entfernt.\n%s Fehler aufgetreten.')
|
showErrorAlert((total - errors) + ' von ' + total + ' Elementen entfernt. ' + errors + ' Fehler aufgetreten.');
|
||||||
.replace('%s', total - errors).replace('%s', total).replace('%s', errors);
|
|
||||||
alert(msg);
|
|
||||||
} else {
|
} else {
|
||||||
var msg = (lang.elementsRemovedFromInvoice || '%s Elemente aus Rechnung entfernt.').replace('%s', total);
|
safeReload();
|
||||||
alert(msg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 'json').fail(function() {
|
}, 'json').fail(function() {
|
||||||
|
|
@ -226,6 +232,10 @@ function removeAllFromFacturedet() {
|
||||||
errors++;
|
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