kundenkarte/ajax/bom_generator.php
data 06f8bc8fde Version 3.5.0 - Drag & Drop Sortierung, Duplicate-Key-Fix
- Drag & Drop Sortierung im Anlagenbaum (Geschwister-Ebene)
- UNIQUE KEY uk_kundenkarte_societe_system um fk_contact erweitert
- Automatische DB-Migration beim Modul-Aktivieren
- Visueller Abstand zwischen Root-Elementen

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 21:05:13 +01:00

250 lines
8.2 KiB
PHP
Executable file

<?php
/* Copyright (C) 2026 Alles Watt lauft
*
* AJAX endpoint for Bill of Materials (Stückliste) generation from schematic
*/
if (!defined('NOTOKENRENEWAL')) define('NOTOKENRENEWAL', '1');
if (!defined('NOREQUIREMENU')) define('NOREQUIREMENU', '1');
if (!defined('NOREQUIREHTML')) define('NOREQUIREHTML', '1');
if (!defined('NOREQUIREAJAX')) define('NOREQUIREAJAX', '1');
$res = 0;
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");
header('Content-Type: application/json; charset=UTF-8');
$langs->loadLangs(array('kundenkarte@kundenkarte', 'products'));
$action = GETPOST('action', 'aZ09');
$anlageId = GETPOSTINT('anlage_id');
$response = array('success' => false, 'error' => '');
// Security check
if (!$user->hasRight('kundenkarte', 'read')) {
$response['error'] = $langs->trans('ErrorPermissionDenied');
echo json_encode($response);
exit;
}
switch ($action) {
case 'generate':
// Generate BOM from all equipment in this installation (anlage)
if ($anlageId <= 0) {
$response['error'] = $langs->trans('ErrorRecordNotFound');
break;
}
// Get all equipment for this anlage through carriers and panels
$sql = "SELECT e.rowid as equipment_id, e.label as equipment_label, e.width_te, e.fk_product,";
$sql .= " et.rowid as type_id, et.ref as type_ref, et.label as type_label, et.fk_product as type_product,";
$sql .= " p.rowid as product_id, p.ref as product_ref, p.label as product_label, p.price, p.tva_tx,";
$sql .= " c.label as carrier_label, c.rowid as carrier_id,";
$sql .= " pan.label as panel_label, pan.rowid as panel_id";
$sql .= " FROM ".MAIN_DB_PREFIX."kundenkarte_equipment e";
$sql .= " JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment_carrier c ON e.fk_carrier = c.rowid";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment_panel pan ON c.fk_panel = pan.rowid";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment_type et ON e.fk_equipment_type = et.rowid";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product p ON COALESCE(e.fk_product, et.fk_product) = p.rowid";
$sql .= " WHERE (pan.fk_anlage = ".((int) $anlageId)." OR c.fk_anlage = ".((int) $anlageId).")";
$sql .= " AND e.status = 1";
$sql .= " ORDER BY pan.position ASC, c.position ASC, e.position_te ASC";
$resql = $db->query($sql);
if (!$resql) {
$response['error'] = $db->lasterror();
break;
}
$items = array();
$summary = array(); // Grouped by product
while ($obj = $db->fetch_object($resql)) {
$item = array(
'equipment_id' => $obj->equipment_id,
'equipment_label' => $obj->equipment_label ?: $obj->type_label,
'type_ref' => $obj->type_ref,
'type_label' => $obj->type_label,
'width_te' => $obj->width_te,
'carrier_label' => $obj->carrier_label,
'panel_label' => $obj->panel_label,
'product_id' => $obj->product_id,
'product_ref' => $obj->product_ref,
'product_label' => $obj->product_label,
'price' => $obj->price,
'tva_tx' => $obj->tva_tx
);
$items[] = $item;
// Group by product for summary
if ($obj->product_id) {
$key = $obj->product_id;
if (!isset($summary[$key])) {
$summary[$key] = array(
'product_id' => $obj->product_id,
'product_ref' => $obj->product_ref,
'product_label' => $obj->product_label,
'price' => $obj->price,
'tva_tx' => $obj->tva_tx,
'quantity' => 0,
'total' => 0
);
}
$summary[$key]['quantity']++;
$summary[$key]['total'] = $summary[$key]['quantity'] * $summary[$key]['price'];
} else {
// Group by type if no product linked
$key = 'type_'.$obj->type_id;
if (!isset($summary[$key])) {
$summary[$key] = array(
'product_id' => null,
'product_ref' => $obj->type_ref,
'product_label' => $obj->type_label.' (kein Produkt)',
'price' => 0,
'tva_tx' => 0,
'quantity' => 0,
'total' => 0
);
}
$summary[$key]['quantity']++;
}
}
$db->free($resql);
// Also include busbar types (connections with is_rail = 1)
$sql = "SELECT conn.rowid as connection_id, conn.rail_phases, conn.rail_start_te, conn.rail_end_te,";
$sql .= " bt.rowid as busbar_type_id, bt.ref as busbar_ref, bt.label as busbar_label, bt.fk_product,";
$sql .= " p.rowid as product_id, p.ref as product_ref, p.label as product_label, p.price, p.tva_tx,";
$sql .= " c.label as carrier_label, pan.label as panel_label";
$sql .= " FROM ".MAIN_DB_PREFIX."kundenkarte_equipment_connection conn";
$sql .= " JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment_carrier c ON conn.fk_carrier = c.rowid";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_equipment_panel pan ON c.fk_panel = pan.rowid";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."kundenkarte_busbar_type bt ON conn.fk_busbar_type = bt.rowid";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product p ON bt.fk_product = p.rowid";
$sql .= " WHERE (pan.fk_anlage = ".((int) $anlageId)." OR c.fk_anlage = ".((int) $anlageId).")";
$sql .= " AND conn.is_rail = 1";
$sql .= " AND conn.status = 1";
$resql = $db->query($sql);
if ($resql) {
while ($obj = $db->fetch_object($resql)) {
// Calculate busbar length in TE
$lengthTE = max(1, intval($obj->rail_end_te) - intval($obj->rail_start_te) + 1);
$item = array(
'equipment_id' => 'busbar_'.$obj->connection_id,
'equipment_label' => $obj->busbar_label ?: 'Sammelschiene '.$obj->rail_phases,
'type_ref' => $obj->busbar_ref ?: 'BUSBAR',
'type_label' => 'Sammelschiene',
'width_te' => $lengthTE,
'carrier_label' => $obj->carrier_label,
'panel_label' => $obj->panel_label,
'product_id' => $obj->product_id,
'product_ref' => $obj->product_ref,
'product_label' => $obj->product_label,
'price' => $obj->price,
'tva_tx' => $obj->tva_tx
);
$items[] = $item;
// Add to summary
if ($obj->product_id) {
$key = $obj->product_id;
if (!isset($summary[$key])) {
$summary[$key] = array(
'product_id' => $obj->product_id,
'product_ref' => $obj->product_ref,
'product_label' => $obj->product_label,
'price' => $obj->price,
'tva_tx' => $obj->tva_tx,
'quantity' => 0,
'total' => 0
);
}
$summary[$key]['quantity']++;
$summary[$key]['total'] = $summary[$key]['quantity'] * $summary[$key]['price'];
}
}
$db->free($resql);
}
// Calculate totals
$totalQuantity = 0;
$totalPrice = 0;
foreach ($summary as $s) {
$totalQuantity += $s['quantity'];
$totalPrice += $s['total'];
}
$response['success'] = true;
$response['items'] = $items;
$response['summary'] = array_values($summary);
$response['total_quantity'] = $totalQuantity;
$response['total_price'] = $totalPrice;
break;
case 'create_order':
// Create a Dolibarr order from the BOM
if (!$user->hasRight('kundenkarte', 'write')) {
$response['error'] = $langs->trans('ErrorPermissionDenied');
break;
}
$socid = GETPOSTINT('socid');
$productData = GETPOST('products', 'array');
if ($socid <= 0 || empty($productData)) {
$response['error'] = $langs->trans('ErrorMissingParameters');
break;
}
require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
$order = new Commande($db);
$order->socid = $socid;
$order->date_commande = dol_now();
$order->note_private = 'Generiert aus Schaltplan-Stückliste';
$order->source = 1; // Web
$result = $order->create($user);
if ($result <= 0) {
$response['error'] = $order->error ?: 'Fehler beim Erstellen der Bestellung';
break;
}
// Add lines
$lineErrors = 0;
foreach ($productData as $prod) {
$productId = intval($prod['product_id']);
$qty = floatval($prod['quantity']);
if ($productId <= 0 || $qty <= 0) continue;
$result = $order->addline(
'', // Description (auto from product)
0, // Unit price (auto from product)
$qty,
0, // TVA rate (auto)
0, 0, // Remise
$productId
);
if ($result < 0) {
$lineErrors++;
}
}
$response['success'] = true;
$response['order_id'] = $order->id;
$response['order_ref'] = $order->ref;
$response['line_errors'] = $lineErrors;
break;
default:
$response['error'] = 'Unknown action';
}
echo json_encode($response);