- output_location (Räumlichkeit): Neues Textfeld am Abgang für Raum/Ort des Verbrauchers. DB-Migration, Backend (AJAX), Frontend (Website + PWA), Anzeige im Schaltplan (kursiv) und in PDF-Tabellen. - Verteilungs-Tabellen: Kundenansicht (A4, Nr/Verbraucher/Räumlichkeit) und Technikeransicht (A4, R.Klem/FI/Nr/Verbraucher/Räumlichkeit/Typ) im Leitungslaufplan-PDF. Gruppiert nach Feld/Reihe mit automatischem Seitenumbruch. - Bundled-Terminals Checkbox: Im Website-Abgang-Dialog (war vorher nur PWA). - PWA: Diverse Verbesserungen, Service Worker v12.4, Connection-Modal erweitert. - Typ-Flags: has_product auch für Gebäudetypen, Equipment-Typ Erweiterungen. - CLAUDE.md + Doku aktualisiert. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
391 lines
10 KiB
PHP
Executable file
391 lines
10 KiB
PHP
Executable file
<?php
|
|
/* Copyright (C) 2026 Alles Watt lauft
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
/**
|
|
* Class AnlageAccessory
|
|
* Verwaltet Zubehör/Ersatzteile für Anlagen-Elemente
|
|
*/
|
|
class AnlageAccessory extends CommonObject
|
|
{
|
|
public $element = 'anlageaccessory';
|
|
public $table_element = 'kundenkarte_anlage_accessory';
|
|
|
|
public $fk_anlage;
|
|
public $fk_product;
|
|
public $qty;
|
|
public $rang;
|
|
public $note;
|
|
|
|
public $date_creation;
|
|
public $fk_user_creat;
|
|
|
|
// Geladene Objekte (aus JOIN)
|
|
public $product_ref;
|
|
public $product_label;
|
|
public $product_price;
|
|
public $product_fk_unit;
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param DoliDB $db Database handler
|
|
*/
|
|
public function __construct($db)
|
|
{
|
|
$this->db = $db;
|
|
}
|
|
|
|
/**
|
|
* Zubehör erstellen
|
|
*
|
|
* @param User $user Benutzer
|
|
* @return int <0 bei Fehler, ID bei Erfolg
|
|
*/
|
|
public function create($user)
|
|
{
|
|
$error = 0;
|
|
$now = dol_now();
|
|
|
|
if (empty($this->fk_anlage) || empty($this->fk_product)) {
|
|
$this->error = 'ErrorMissingParameters';
|
|
return -1;
|
|
}
|
|
|
|
// Prüfen ob bereits vorhanden
|
|
if ($this->alreadyExists($this->fk_anlage, $this->fk_product)) {
|
|
$this->error = 'ErrorAccessoryAlreadyExists';
|
|
return -2;
|
|
}
|
|
|
|
$this->db->begin();
|
|
|
|
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
|
|
$sql .= "fk_anlage, fk_product, qty, rang, note,";
|
|
$sql .= " date_creation, fk_user_creat";
|
|
$sql .= ") VALUES (";
|
|
$sql .= ((int) $this->fk_anlage);
|
|
$sql .= ", ".((int) $this->fk_product);
|
|
$sql .= ", ".((float) ($this->qty > 0 ? $this->qty : 1));
|
|
$sql .= ", ".((int) $this->rang);
|
|
$sql .= ", ".($this->note ? "'".$this->db->escape($this->note)."'" : "NULL");
|
|
$sql .= ", '".$this->db->idate($now)."'";
|
|
$sql .= ", ".((int) $user->id);
|
|
$sql .= ")";
|
|
|
|
$resql = $this->db->query($sql);
|
|
if (!$resql) {
|
|
$error++;
|
|
$this->errors[] = "Error ".$this->db->lasterror();
|
|
}
|
|
|
|
if (!$error) {
|
|
$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
|
|
}
|
|
|
|
if ($error) {
|
|
$this->db->rollback();
|
|
return -1 * $error;
|
|
} else {
|
|
$this->db->commit();
|
|
return $this->id;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Zubehör laden
|
|
*
|
|
* @param int $id ID
|
|
* @return int <0 bei Fehler, 0 nicht gefunden, >0 OK
|
|
*/
|
|
public function fetch($id)
|
|
{
|
|
$sql = "SELECT a.*, p.ref as product_ref, p.label as product_label, p.price as product_price, p.fk_unit as product_fk_unit";
|
|
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as a";
|
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON a.fk_product = p.rowid";
|
|
$sql .= " WHERE a.rowid = ".((int) $id);
|
|
|
|
$resql = $this->db->query($sql);
|
|
if ($resql) {
|
|
if ($this->db->num_rows($resql)) {
|
|
$obj = $this->db->fetch_object($resql);
|
|
$this->id = $obj->rowid;
|
|
$this->fk_anlage = $obj->fk_anlage;
|
|
$this->fk_product = $obj->fk_product;
|
|
$this->qty = $obj->qty;
|
|
$this->rang = $obj->rang;
|
|
$this->note = $obj->note;
|
|
$this->date_creation = $this->db->jdate($obj->date_creation);
|
|
$this->fk_user_creat = $obj->fk_user_creat;
|
|
$this->product_ref = $obj->product_ref;
|
|
$this->product_label = $obj->product_label;
|
|
$this->product_price = $obj->product_price;
|
|
$this->product_fk_unit = $obj->product_fk_unit;
|
|
$this->db->free($resql);
|
|
return 1;
|
|
} else {
|
|
$this->db->free($resql);
|
|
return 0;
|
|
}
|
|
} else {
|
|
$this->error = $this->db->lasterror();
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Zubehör aktualisieren
|
|
*
|
|
* @param User $user Benutzer
|
|
* @return int <0 bei Fehler, >0 OK
|
|
*/
|
|
public function update($user)
|
|
{
|
|
$error = 0;
|
|
|
|
$this->db->begin();
|
|
|
|
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
|
|
$sql .= " qty = ".((float) $this->qty);
|
|
$sql .= ", rang = ".((int) $this->rang);
|
|
$sql .= ", note = ".($this->note ? "'".$this->db->escape($this->note)."'" : "NULL");
|
|
$sql .= " WHERE rowid = ".((int) $this->id);
|
|
|
|
$resql = $this->db->query($sql);
|
|
if (!$resql) {
|
|
$error++;
|
|
$this->errors[] = "Error ".$this->db->lasterror();
|
|
}
|
|
|
|
if ($error) {
|
|
$this->db->rollback();
|
|
return -1 * $error;
|
|
} else {
|
|
$this->db->commit();
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Zubehör löschen
|
|
*
|
|
* @param User $user Benutzer
|
|
* @return int <0 bei Fehler, >0 OK
|
|
*/
|
|
public function delete($user)
|
|
{
|
|
$this->db->begin();
|
|
|
|
$sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element;
|
|
$sql .= " WHERE rowid = ".((int) $this->id);
|
|
|
|
$resql = $this->db->query($sql);
|
|
if (!$resql) {
|
|
$this->db->rollback();
|
|
$this->error = $this->db->lasterror();
|
|
return -1;
|
|
}
|
|
|
|
$this->db->commit();
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Alle Zubehörteile einer Anlage laden
|
|
*
|
|
* @param int $anlageId Anlage-ID
|
|
* @return array Array von AnlageAccessory-Objekten
|
|
*/
|
|
public function fetchAllByAnlage($anlageId)
|
|
{
|
|
$results = array();
|
|
|
|
$sql = "SELECT a.*, p.ref as product_ref, p.label as product_label, p.price as product_price, p.fk_unit as product_fk_unit";
|
|
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as a";
|
|
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON a.fk_product = p.rowid";
|
|
$sql .= " WHERE a.fk_anlage = ".((int) $anlageId);
|
|
$sql .= " ORDER BY a.rang ASC, a.rowid ASC";
|
|
|
|
$resql = $this->db->query($sql);
|
|
if ($resql) {
|
|
while ($obj = $this->db->fetch_object($resql)) {
|
|
$acc = new AnlageAccessory($this->db);
|
|
$acc->id = $obj->rowid;
|
|
$acc->fk_anlage = $obj->fk_anlage;
|
|
$acc->fk_product = $obj->fk_product;
|
|
$acc->qty = $obj->qty;
|
|
$acc->rang = $obj->rang;
|
|
$acc->note = $obj->note;
|
|
$acc->date_creation = $this->db->jdate($obj->date_creation);
|
|
$acc->fk_user_creat = $obj->fk_user_creat;
|
|
$acc->product_ref = $obj->product_ref;
|
|
$acc->product_label = $obj->product_label;
|
|
$acc->product_price = $obj->product_price;
|
|
$acc->product_fk_unit = $obj->product_fk_unit;
|
|
$results[] = $acc;
|
|
}
|
|
$this->db->free($resql);
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
/**
|
|
* Prüfen ob Produkt bereits als Zubehör zugeordnet ist
|
|
*
|
|
* @param int $anlageId Anlage-ID
|
|
* @param int $productId Produkt-ID
|
|
* @return bool true wenn bereits vorhanden
|
|
*/
|
|
public function alreadyExists($anlageId, $productId)
|
|
{
|
|
$sql = "SELECT COUNT(*) as cnt FROM ".MAIN_DB_PREFIX.$this->table_element;
|
|
$sql .= " WHERE fk_anlage = ".((int) $anlageId);
|
|
$sql .= " AND fk_product = ".((int) $productId);
|
|
|
|
$resql = $this->db->query($sql);
|
|
if ($resql) {
|
|
$obj = $this->db->fetch_object($resql);
|
|
return ($obj->cnt > 0);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Lieferantenbestellung aus ausgewählten Zubehörteilen erstellen
|
|
*
|
|
* @param User $user Benutzer
|
|
* @param int $supplierId Lieferanten-ID (fournisseur)
|
|
* @param int $anlageId Anlage-ID
|
|
* @param array $selectedIds Array von Accessory-IDs
|
|
* @param array $quantities Optional: ID => Menge
|
|
* @return int Bestell-ID bei Erfolg, <0 bei Fehler
|
|
*/
|
|
public function generateSupplierOrder($user, $supplierId, $anlageId, $selectedIds, $quantities = array())
|
|
{
|
|
global $conf, $langs, $mysoc;
|
|
|
|
require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
|
|
require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
|
|
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
|
|
|
|
if (empty($selectedIds)) {
|
|
$this->error = 'NoProductsSelected';
|
|
return -1;
|
|
}
|
|
|
|
// Lieferant laden
|
|
$supplier = new Societe($this->db);
|
|
if ($supplier->fetch($supplierId) <= 0) {
|
|
$this->error = 'ErrorLoadingSupplier';
|
|
return -1;
|
|
}
|
|
|
|
// Zubehör der Anlage laden
|
|
$accessories = $this->fetchAllByAnlage($anlageId);
|
|
if (!is_array($accessories) || empty($accessories)) {
|
|
$this->error = 'NoAccessoriesFound';
|
|
return -2;
|
|
}
|
|
|
|
// Ausgewählte filtern
|
|
$toAdd = array();
|
|
foreach ($accessories as $acc) {
|
|
if (in_array($acc->id, $selectedIds)) {
|
|
$qty = isset($quantities[$acc->id]) ? (float) $quantities[$acc->id] : $acc->qty;
|
|
if ($qty > 0) {
|
|
$toAdd[] = array(
|
|
'product_id' => $acc->fk_product,
|
|
'qty' => $qty
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (empty($toAdd)) {
|
|
$this->error = 'NoValidProductsToAdd';
|
|
return -2;
|
|
}
|
|
|
|
// Lieferantenbestellung erstellen
|
|
$order = new CommandeFournisseur($this->db);
|
|
$order->socid = $supplierId;
|
|
$order->date = dol_now();
|
|
$order->note_private = $langs->trans('OrderGeneratedFromAccessories');
|
|
|
|
$this->db->begin();
|
|
|
|
$result = $order->create($user);
|
|
if ($result <= 0) {
|
|
$this->error = $order->error;
|
|
$this->errors = $order->errors;
|
|
$this->db->rollback();
|
|
return -3;
|
|
}
|
|
|
|
// Produkte hinzufügen
|
|
foreach ($toAdd as $item) {
|
|
$product = new Product($this->db);
|
|
$product->fetch($item['product_id']);
|
|
|
|
// MwSt-Satz ermitteln (Lieferant = Verkäufer, eigene Firma = Käufer)
|
|
$tva_tx = get_default_tva($supplier, $mysoc, $product->id);
|
|
$localtax1_tx = get_default_localtax($supplier, $mysoc, 1, $product->id);
|
|
$localtax2_tx = get_default_localtax($supplier, $mysoc, 2, $product->id);
|
|
|
|
// Lieferantenpreis ermitteln
|
|
$fournPrice = $product->price;
|
|
$fournPriceId = 0;
|
|
$fournRef = '';
|
|
$sqlFourn = "SELECT rowid, price as fourn_price, ref_fourn";
|
|
$sqlFourn .= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price";
|
|
$sqlFourn .= " WHERE fk_product = ".((int) $product->id);
|
|
$sqlFourn .= " AND fk_soc = ".((int) $supplierId);
|
|
$sqlFourn .= " ORDER BY price ASC LIMIT 1";
|
|
$resFourn = $this->db->query($sqlFourn);
|
|
if ($resFourn && $this->db->num_rows($resFourn) > 0) {
|
|
$objFourn = $this->db->fetch_object($resFourn);
|
|
$fournPrice = $objFourn->fourn_price;
|
|
$fournPriceId = $objFourn->rowid;
|
|
$fournRef = $objFourn->ref_fourn;
|
|
}
|
|
|
|
$lineResult = $order->addline(
|
|
$product->label, // Beschreibung
|
|
$fournPrice, // Preis HT
|
|
$item['qty'], // Menge
|
|
$tva_tx, // MwSt
|
|
$localtax1_tx, // Lokale Steuer 1
|
|
$localtax2_tx, // Lokale Steuer 2
|
|
$product->id, // Produkt-ID
|
|
$fournPriceId, // Lieferantenpreis-ID
|
|
$fournRef, // Lieferanten-Referenz
|
|
0, // Rabatt
|
|
'HT', // Preis-Basis
|
|
0, // Preis TTC
|
|
0, // Typ (0=Produkt)
|
|
0, // Info bits
|
|
false, // notrigger
|
|
null, // Startdatum
|
|
null, // Enddatum
|
|
array(), // Optionen
|
|
$product->fk_unit // Einheit
|
|
);
|
|
|
|
if ($lineResult < 0) {
|
|
$this->error = $order->error;
|
|
$this->errors = $order->errors;
|
|
$this->db->rollback();
|
|
return -4;
|
|
}
|
|
}
|
|
|
|
$this->db->commit();
|
|
return $order->id;
|
|
}
|
|
}
|