kundenkarte/class/favoriteproduct.class.php
2026-01-31 08:18:54 +01:00

802 lines
22 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 FavoriteProduct
* Manages favorite products for customers
*/
class FavoriteProduct extends CommonObject
{
public $element = 'favoriteproduct';
public $table_element = 'kundenkarte_favorite_products';
public $fk_soc;
public $fk_contact;
public $fk_product;
public $qty;
public $rang;
public $note;
public $active;
public $date_creation;
public $fk_user_creat;
public $fk_user_modif;
// Loaded objects
public $product;
public $societe;
/**
* Constructor
*
* @param DoliDB $db Database handler
*/
public function __construct($db)
{
$this->db = $db;
}
/**
* Create object in database
*
* @param User $user User that creates
* @param bool $notrigger false=launch triggers, true=disable triggers
* @return int Return integer <0 if KO, Id of created object if OK
*/
public function create($user, $notrigger = false)
{
global $conf;
$error = 0;
$now = dol_now();
// Check parameters
if (empty($this->fk_soc) || empty($this->fk_product)) {
$this->error = 'ErrorMissingParameters';
return -1;
}
// Check if already exists
if ($this->alreadyExists($this->fk_soc, $this->fk_product, $this->fk_contact)) {
$this->error = 'ErrorRecordAlreadyExists';
return -2;
}
$this->db->begin();
// Get max rang for this customer/contact to add at end of list
$maxRang = 0;
$sqlRang = "SELECT MAX(rang) as maxrang FROM ".MAIN_DB_PREFIX.$this->table_element;
$sqlRang .= " WHERE fk_soc = ".((int) $this->fk_soc);
if ($this->fk_contact > 0) {
$sqlRang .= " AND fk_contact = ".((int) $this->fk_contact);
} else {
$sqlRang .= " AND (fk_contact IS NULL OR fk_contact = 0)";
}
$sqlRang .= " AND entity = ".((int) $conf->entity);
$resRang = $this->db->query($sqlRang);
if ($resRang) {
$objRang = $this->db->fetch_object($resRang);
$maxRang = ($objRang->maxrang !== null) ? ((int) $objRang->maxrang + 1) : 0;
}
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
$sql .= "entity, fk_soc, fk_contact, fk_product, qty, rang, note, active, date_creation, fk_user_creat";
$sql .= ") VALUES (";
$sql .= ((int) $conf->entity);
$sql .= ", ".((int) $this->fk_soc);
$sql .= ", ".($this->fk_contact > 0 ? ((int) $this->fk_contact) : "NULL");
$sql .= ", ".((int) $this->fk_product);
$sql .= ", ".((float) ($this->qty > 0 ? $this->qty : 1));
$sql .= ", ".((int) $maxRang);
$sql .= ", ".($this->note ? "'".$this->db->escape($this->note)."'" : "NULL");
$sql .= ", ".((int) ($this->active !== null ? $this->active : 1));
$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);
$this->date_creation = $now;
$this->fk_user_creat = $user->id;
}
if ($error) {
$this->db->rollback();
return -1 * $error;
} else {
$this->db->commit();
return $this->id;
}
}
/**
* Load object from database
*
* @param int $id ID of record
* @return int Return integer <0 if KO, 0 if not found, >0 if OK
*/
public function fetch($id)
{
global $conf;
$sql = "SELECT rowid, entity, fk_soc, fk_contact, fk_product, qty, rang, note, active,";
$sql .= " date_creation, tms, fk_user_creat, fk_user_modif";
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
$sql .= " WHERE rowid = ".((int) $id);
$sql .= " AND entity = ".((int) $conf->entity);
$resql = $this->db->query($sql);
if ($resql) {
if ($this->db->num_rows($resql)) {
$obj = $this->db->fetch_object($resql);
$this->id = $obj->rowid;
$this->entity = $obj->entity;
$this->fk_soc = $obj->fk_soc;
$this->fk_contact = $obj->fk_contact;
$this->fk_product = $obj->fk_product;
$this->qty = $obj->qty;
$this->rang = $obj->rang;
$this->note = $obj->note;
$this->active = $obj->active;
$this->date_creation = $this->db->jdate($obj->date_creation);
$this->tms = $this->db->jdate($obj->tms);
$this->fk_user_creat = $obj->fk_user_creat;
$this->fk_user_modif = $obj->fk_user_modif;
$this->db->free($resql);
return 1;
} else {
$this->db->free($resql);
return 0;
}
} else {
$this->error = $this->db->lasterror();
return -1;
}
}
/**
* Update object in database
*
* @param User $user User that modifies
* @param bool $notrigger false=launch triggers, true=disable triggers
* @return int Return integer <0 if KO, >0 if OK
*/
public function update($user, $notrigger = false)
{
$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 .= ", active = ".((int) $this->active);
$sql .= ", fk_user_modif = ".((int) $user->id);
$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;
}
}
/**
* Delete object in database
*
* @param User $user User that deletes
* @param bool $notrigger false=launch triggers, true=disable triggers
* @return int Return integer <0 if KO, >0 if OK
*/
public function delete($user, $notrigger = false)
{
$error = 0;
$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) {
$error++;
$this->errors[] = "Error ".$this->db->lasterror();
}
if ($error) {
$this->db->rollback();
return -1 * $error;
} else {
$this->db->commit();
return 1;
}
}
/**
* Check if a product is already a favorite for a customer/contact
*
* @param int $socid Customer ID
* @param int $productid Product ID
* @param int $contactid Contact ID (optional)
* @return bool
*/
public function alreadyExists($socid, $productid, $contactid = 0)
{
global $conf;
$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX.$this->table_element;
$sql .= " WHERE fk_soc = ".((int) $socid);
$sql .= " AND fk_product = ".((int) $productid);
if ($contactid > 0) {
$sql .= " AND fk_contact = ".((int) $contactid);
} else {
$sql .= " AND (fk_contact IS NULL OR fk_contact = 0)";
}
$sql .= " AND entity = ".((int) $conf->entity);
$resql = $this->db->query($sql);
if ($resql) {
return ($this->db->num_rows($resql) > 0);
}
return false;
}
/**
* Get all favorite products for a customer
*
* @param int $socid Customer ID
* @param int $activeonly Only active favorites
* @return array|int Array of FavoriteProduct objects or -1 if error
*/
public function fetchAllBySociete($socid, $activeonly = 1)
{
global $conf;
$results = array();
$sql = "SELECT fp.rowid, fp.fk_soc, fp.fk_product, fp.qty, fp.rang, fp.note, fp.active,";
$sql .= " p.ref as product_ref, p.label as product_label, p.price, p.price_ttc, p.tva_tx";
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as fp";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON fp.fk_product = p.rowid";
$sql .= " WHERE fp.fk_soc = ".((int) $socid);
// Only thirdparty-level favorites, not contact-specific
$sql .= " AND (fp.fk_contact IS NULL OR fp.fk_contact = 0)";
$sql .= " AND fp.entity = ".((int) $conf->entity);
if ($activeonly) {
$sql .= " AND fp.active = 1";
}
$sql .= " ORDER BY fp.rang ASC, p.label ASC";
$resql = $this->db->query($sql);
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$fav = new FavoriteProduct($this->db);
$fav->id = $obj->rowid;
$fav->fk_soc = $obj->fk_soc;
$fav->fk_product = $obj->fk_product;
$fav->qty = $obj->qty;
$fav->rang = $obj->rang;
$fav->note = $obj->note;
$fav->active = $obj->active;
// Product info
$fav->product_ref = $obj->product_ref;
$fav->product_label = $obj->product_label;
$fav->product_price = $obj->price;
$fav->product_price_ttc = $obj->price_ttc;
$fav->product_tva_tx = $obj->tva_tx;
$results[] = $fav;
}
$this->db->free($resql);
return $results;
} else {
$this->error = $this->db->lasterror();
return -1;
}
}
/**
* Move a favorite product up in the list
*
* @param int $id Favorite ID to move
* @param int $socid Customer ID
* @return int 1 if OK, <0 if KO
*/
public function moveUp($id, $socid)
{
return $this->movePosition($id, $socid, 'up');
}
/**
* Move a favorite product down in the list
*
* @param int $id Favorite ID to move
* @param int $socid Customer ID
* @return int 1 if OK, <0 if KO
*/
public function moveDown($id, $socid)
{
return $this->movePosition($id, $socid, 'down');
}
/**
* Move a favorite product position
*
* @param int $id Favorite ID to move
* @param int $socid Customer ID
* @param string $direction 'up' or 'down'
* @return int 1 if OK, <0 if KO
*/
private function movePosition($id, $socid, $direction)
{
global $conf;
// Get all favorites ordered by rang
$sql = "SELECT rowid, rang FROM ".MAIN_DB_PREFIX.$this->table_element;
$sql .= " WHERE fk_soc = ".((int) $socid);
$sql .= " AND entity = ".((int) $conf->entity);
$sql .= " ORDER BY rang ASC, rowid ASC";
$resql = $this->db->query($sql);
if (!$resql) {
$this->error = $this->db->lasterror();
return -1;
}
$items = array();
$currentIndex = -1;
$i = 0;
while ($obj = $this->db->fetch_object($resql)) {
$items[$i] = array('id' => $obj->rowid, 'rang' => $i);
if ($obj->rowid == $id) {
$currentIndex = $i;
}
$i++;
}
$this->db->free($resql);
if ($currentIndex < 0) {
return 0; // Item not found
}
// Calculate new positions
$swapIndex = -1;
if ($direction == 'up' && $currentIndex > 0) {
$swapIndex = $currentIndex - 1;
} elseif ($direction == 'down' && $currentIndex < count($items) - 1) {
$swapIndex = $currentIndex + 1;
}
if ($swapIndex < 0) {
return 0; // Cannot move
}
// Swap positions
$this->db->begin();
$sql1 = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET rang = ".((int) $swapIndex);
$sql1 .= " WHERE rowid = ".((int) $items[$currentIndex]['id']);
$sql2 = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET rang = ".((int) $currentIndex);
$sql2 .= " WHERE rowid = ".((int) $items[$swapIndex]['id']);
if ($this->db->query($sql1) && $this->db->query($sql2)) {
$this->db->commit();
return 1;
} else {
$this->error = $this->db->lasterror();
$this->db->rollback();
return -1;
}
}
/**
* Generate an order from selected favorite products
*
* @param User $user User creating the order
* @param int $socid Customer ID
* @param array $selectedIds Array of favorite product IDs to include
* @param array $quantities Optional array of quantities (id => qty)
* @return int Order ID if OK, <0 if KO
*/
public function generateOrder($user, $socid, $selectedIds, $quantities = array())
{
global $conf, $langs, $mysoc;
require_once DOL_DOCUMENT_ROOT.'/commande/class/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;
}
// Load thirdparty (required for VAT calculation)
$societe = new Societe($this->db);
if ($societe->fetch($socid) <= 0) {
$this->error = 'ErrorLoadingThirdparty';
return -1;
}
// Fetch selected favorites
$favorites = $this->fetchAllBySociete($socid);
if (!is_array($favorites)) {
return -1;
}
// Filter to selected only
$toAdd = array();
foreach ($favorites as $fav) {
if (in_array($fav->id, $selectedIds)) {
$qty = isset($quantities[$fav->id]) ? (float) $quantities[$fav->id] : $fav->qty;
if ($qty > 0) {
$toAdd[] = array(
'product_id' => $fav->fk_product,
'qty' => $qty
);
}
}
}
if (empty($toAdd)) {
$this->error = 'NoValidProductsToAdd';
return -2;
}
// Create order
$order = new Commande($this->db);
$order->socid = $socid;
$order->thirdparty = $societe; // Required for VAT calculation
$order->date = dol_now();
$order->ref_client = $societe->name.' - '.$langs->trans('FavoriteProducts'); // Ihr Zeichen
$order->note_private = $langs->trans('OrderGeneratedFromFavorites');
$this->db->begin();
// First create the order header
$result = $order->create($user);
if ($result <= 0) {
$this->error = $order->error;
$this->errors = $order->errors;
$this->db->rollback();
return -3;
}
// Now add products to the created order
foreach ($toAdd as $item) {
$product = new Product($this->db);
$product->fetch($item['product_id']);
// Get VAT rate for this product and customer (mysoc = seller, societe = buyer)
$tva_tx = get_default_tva($mysoc, $societe, $product->id);
$localtax1_tx = get_default_localtax($mysoc, $societe, 1, $product->id);
$localtax2_tx = get_default_localtax($mysoc, $societe, 2, $product->id);
$lineResult = $order->addline(
$product->label, // Description
$product->price, // Unit price HT
$item['qty'], // Quantity
$tva_tx, // VAT rate
$localtax1_tx, // Local tax 1
$localtax2_tx, // Local tax 2
$product->id, // Product ID
0, // Discount
0, // Info bits
0, // fk_remise_except
'HT', // Price base type
0, // Unit price TTC
'', // Date start
'', // Date end
0, // Type (0=product)
-1, // Rang
0, // Special code
0, // fk_parent_line
0, // fk_fournprice
0, // pa_ht
$product->label, // Label
array(), // Array options
$product->fk_unit // Unit
);
if ($lineResult < 0) {
$this->error = $order->error;
$this->errors = $order->errors;
$this->db->rollback();
return -4;
}
}
$this->db->commit();
return $order->id;
}
/**
* Get all favorite products for a contact/address
*
* @param int $contactid Contact ID
* @param int $activeonly Only active favorites
* @return array|int Array of FavoriteProduct objects or -1 if error
*/
public function fetchAllByContact($contactid, $activeonly = 1)
{
global $conf;
$results = array();
$sql = "SELECT fp.rowid, fp.fk_soc, fp.fk_contact, fp.fk_product, fp.qty, fp.rang, fp.note, fp.active,";
$sql .= " p.ref as product_ref, p.label as product_label, p.price, p.price_ttc, p.tva_tx";
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as fp";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON fp.fk_product = p.rowid";
$sql .= " WHERE fp.fk_contact = ".((int) $contactid);
$sql .= " AND fp.entity = ".((int) $conf->entity);
if ($activeonly) {
$sql .= " AND fp.active = 1";
}
$sql .= " ORDER BY fp.rang ASC, p.label ASC";
$resql = $this->db->query($sql);
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$fav = new FavoriteProduct($this->db);
$fav->id = $obj->rowid;
$fav->fk_soc = $obj->fk_soc;
$fav->fk_contact = $obj->fk_contact;
$fav->fk_product = $obj->fk_product;
$fav->qty = $obj->qty;
$fav->rang = $obj->rang;
$fav->note = $obj->note;
$fav->active = $obj->active;
// Product info
$fav->product_ref = $obj->product_ref;
$fav->product_label = $obj->product_label;
$fav->product_price = $obj->price;
$fav->product_price_ttc = $obj->price_ttc;
$fav->product_tva_tx = $obj->tva_tx;
$results[] = $fav;
}
$this->db->free($resql);
return $results;
} else {
$this->error = $this->db->lasterror();
return -1;
}
}
/**
* Move a favorite product up in the list (for contact)
*
* @param int $id Favorite ID to move
* @param int $contactid Contact ID
* @return int 1 if OK, <0 if KO
*/
public function moveUpByContact($id, $contactid)
{
return $this->movePositionByContact($id, $contactid, 'up');
}
/**
* Move a favorite product down in the list (for contact)
*
* @param int $id Favorite ID to move
* @param int $contactid Contact ID
* @return int 1 if OK, <0 if KO
*/
public function moveDownByContact($id, $contactid)
{
return $this->movePositionByContact($id, $contactid, 'down');
}
/**
* Move a favorite product position (for contact)
*
* @param int $id Favorite ID to move
* @param int $contactid Contact ID
* @param string $direction 'up' or 'down'
* @return int 1 if OK, <0 if KO
*/
private function movePositionByContact($id, $contactid, $direction)
{
global $conf;
$sql = "SELECT rowid, rang FROM ".MAIN_DB_PREFIX.$this->table_element;
$sql .= " WHERE fk_contact = ".((int) $contactid);
$sql .= " AND entity = ".((int) $conf->entity);
$sql .= " ORDER BY rang ASC, rowid ASC";
$resql = $this->db->query($sql);
if (!$resql) {
$this->error = $this->db->lasterror();
return -1;
}
$items = array();
$currentIndex = -1;
$i = 0;
while ($obj = $this->db->fetch_object($resql)) {
$items[$i] = array('id' => $obj->rowid, 'rang' => $i);
if ($obj->rowid == $id) {
$currentIndex = $i;
}
$i++;
}
$this->db->free($resql);
if ($currentIndex < 0) {
return 0;
}
$swapIndex = -1;
if ($direction == 'up' && $currentIndex > 0) {
$swapIndex = $currentIndex - 1;
} elseif ($direction == 'down' && $currentIndex < count($items) - 1) {
$swapIndex = $currentIndex + 1;
}
if ($swapIndex < 0) {
return 0;
}
$this->db->begin();
$sql1 = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET rang = ".((int) $swapIndex);
$sql1 .= " WHERE rowid = ".((int) $items[$currentIndex]['id']);
$sql2 = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET rang = ".((int) $currentIndex);
$sql2 .= " WHERE rowid = ".((int) $items[$swapIndex]['id']);
if ($this->db->query($sql1) && $this->db->query($sql2)) {
$this->db->commit();
return 1;
} else {
$this->error = $this->db->lasterror();
$this->db->rollback();
return -1;
}
}
/**
* Generate an order from selected favorite products (for contact)
*
* @param User $user User creating the order
* @param int $socid Customer ID
* @param int $contactid Contact ID
* @param array $selectedIds Array of favorite product IDs to include
* @param array $quantities Optional array of quantities (id => qty)
* @return int Order ID if OK, <0 if KO
*/
public function generateOrderByContact($user, $socid, $contactid, $selectedIds, $quantities = array())
{
global $conf, $langs, $mysoc;
require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
if (empty($selectedIds)) {
$this->error = 'NoProductsSelected';
return -1;
}
// Load thirdparty
$societe = new Societe($this->db);
if ($societe->fetch($socid) <= 0) {
$this->error = 'ErrorLoadingThirdparty';
return -1;
}
// Load contact
$contact = new Contact($this->db);
if ($contact->fetch($contactid) <= 0) {
$this->error = 'ErrorLoadingContact';
return -1;
}
// Fetch selected favorites
$favorites = $this->fetchAllByContact($contactid);
if (!is_array($favorites)) {
return -1;
}
// Filter to selected only
$toAdd = array();
foreach ($favorites as $fav) {
if (in_array($fav->id, $selectedIds)) {
$qty = isset($quantities[$fav->id]) ? (float) $quantities[$fav->id] : $fav->qty;
if ($qty > 0) {
$toAdd[] = array(
'product_id' => $fav->fk_product,
'qty' => $qty
);
}
}
}
if (empty($toAdd)) {
$this->error = 'NoValidProductsToAdd';
return -2;
}
// Create order
$order = new Commande($this->db);
$order->socid = $socid;
$order->thirdparty = $societe;
$order->date = dol_now();
// Ihr Zeichen: Kunde - Kontakt/Adresse - Favoriten
$order->ref_client = $societe->name.' - '.$contact->getFullName($langs).' - '.$langs->trans('FavoriteProducts');
$order->note_private = $langs->trans('OrderGeneratedFromFavorites');
$this->db->begin();
$result = $order->create($user);
if ($result <= 0) {
$this->error = $order->error;
$this->errors = $order->errors;
$this->db->rollback();
return -3;
}
// Add products
foreach ($toAdd as $item) {
$product = new Product($this->db);
$product->fetch($item['product_id']);
$tva_tx = get_default_tva($mysoc, $societe, $product->id);
$localtax1_tx = get_default_localtax($mysoc, $societe, 1, $product->id);
$localtax2_tx = get_default_localtax($mysoc, $societe, 2, $product->id);
$lineResult = $order->addline(
$product->label,
$product->price,
$item['qty'],
$tva_tx,
$localtax1_tx,
$localtax2_tx,
$product->id,
0, 0, 0, 'HT', 0, '', '', 0, -1, 0, 0, 0, 0,
$product->label,
array(),
$product->fk_unit
);
if ($lineResult < 0) {
$this->error = $order->error;
$this->errors = $order->errors;
$this->db->rollback();
return -4;
}
}
$this->db->commit();
return $order->id;
}
}