szollosil преди 1 година
родител
ревизия
ff006a1078

+ 23 - 1
custom/bbus/class/api_bbus.class.php

@@ -635,9 +635,13 @@ class BBus extends DolibarrApi
 			}
 			if (!empty($productsArray)) {
 				foreach ($productsArray as $key => $value) {
+					ApiBbusLog::appLog("SERVER_HOST: " . $server_host);
+					ApiBbusLog::appLog("CONF_SERVER_HOST: " . $conf->global->LOCAL_SERVER_HOST);
 					if ($server_host == $conf->global->LOCAL_SERVER_HOST) {
+						ApiBbusLog::appLog($conf->global->LOCAL_SERVER_HOST);
 						$availableResult = $this->localCheckAvailablePlacesForCheckPermission($key);
 					} elseif ($server_host == $conf->global->CURL_SERVER_HOST) {
+						ApiBbusLog::appLog($conf->global->CURL_SERVER_HOST);
 						$postFields = '{"product_id":"' . $key . '"}';
 						$availableResult = $this->curlRunner('bbus/localCheckAvailablePlacesForCheckPermission', $postFields, 'POST', false);
 					}
@@ -665,6 +669,7 @@ class BBus extends DolibarrApi
 	 */
 	public function localCheckAvailablePlacesForCheckPermission($product_id)
 	{
+		$date_from = $this->getCutOffTimeDate($product_id);
 		$sql = "SELECT 
 			ac.id
 			FROM llx_actioncomm as ac 
@@ -673,11 +678,28 @@ class BBus extends DolibarrApi
 			INNER JOIN llx_eventwizard_eventproduct as ep ON ep.fk_eventdetails = ac.fk_element
 			WHERE ac.code = 'AC_EVENT'
 			AND ep.fk_product = {$product_id}
-			AND ac.datep > NOW() 
+			AND ac.datep > '{$date_from}' 
 			ORDER BY ac.datep ASC";
+		ApiBbusLog::appLog($sql);
+
 			$result = $this->db->query($sql);
 		return $this->db->num_rows($result) > 0 ? 1 : 0;
+	}
 		
+	private function getCutOffTimeDate($product_id)
+	{
+		$date = date("Y-m-d H:i:s");
+		$sql = "SELECT cut_off_time_app FROM llx_product_extrafields WHERE fk_object = {$product_id}";
+		if ($result = $this->db->query($sql)) {
+			if ($this->db->num_rows($result) > 0) {
+				while ($row = $this->db->fetch_object($result)) {
+					$cutOffTime = $row->cut_off_time_app;
+				}
+				$dateTimestamp = strtotime($date);
+				$date = date("Y-m-d H:i:s", $dateTimestamp + $cutOffTime * 60);
+			}
+		}
+		return $date;
 	}
 
 	/**

+ 2257 - 0
custom/bbus/class/api_bbus.class.php.bak

@@ -0,0 +1,2257 @@
+<?php
+/* Copyright (C) 2015   Jean-François Ferry     <jfefe@aternatik.fr>
+* Copyright (C) 2019   Cedric Ancelin          <icedo.anc@gmail.com>
+*
+* 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.
+*
+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/>.
+*/
+
+use Luracast\Restler\RestException;
+use Sabre\Xml\Element;
+
+require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
+require_once DOL_DOCUMENT_ROOT . '/product/stock/class/productlot.class.php';
+require_once DOL_DOCUMENT_ROOT . '/fourn/class/fournisseur.product.class.php';
+require_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
+require_once DOL_DOCUMENT_ROOT . '/variants/class/ProductAttribute.class.php';
+require_once DOL_DOCUMENT_ROOT . '/variants/class/ProductAttributeValue.class.php';
+require_once DOL_DOCUMENT_ROOT . '/variants/class/ProductCombination.class.php';
+require_once DOL_DOCUMENT_ROOT . '/variants/class/ProductCombination2ValuePair.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/sellproduct.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/apiuserhandler.class.php';
+require_once DOL_DOCUMENT_ROOT . '/compta/facture/class/facture.class.php';
+
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/jegy.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/bbloginlog.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/bbexchangerate.class.php';
+
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/bbticket.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/bbdevices.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/bbdevicesservicelocation.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/bbdevicesservicelocationproduct.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/bbticketinvoiceprinting.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/utils/globalconst.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/utils/emailtemplatehandler.php';
+include_once DOL_DOCUMENT_ROOT . '/core/class/CMailFile.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/bbticketnaplo.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/bbtickethandler.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/gpsposition.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/userloginnaplo.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/utils/countryhandler.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/utils/entityhandler.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/discountnaplo.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/settlements/class/groupusers.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/rollerstorage/class/packagehistory.class.php';
+require_once DOL_DOCUMENT_ROOT . '/product/inventory/class/inventory.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/apiproductlisthelper.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/apiinvoicehelper.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/ticket_checker.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/api_bbus_helper.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/api_bbus_log.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/bbapilock.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/exchangerateupdater.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/bbticketvalidationcoords.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/api_curl.class.php';
+
+/**
+ * API class for products
+ *
+ * @access protected
+ * @class  DolibarrApiAccess {@requires user,external}
+ */
+class BBus extends DolibarrApi
+{
+	use CurlApi;
+
+	private $code;
+	private $printingTime;
+	private $status = 0;
+	private $device_id;
+	private $service_location_id;
+	private $bbTicketRowId;
+	private $facture_id;
+	private $isValid = true;
+	private User $user;
+
+
+	const EMAIL_TEMPLATE = 'multiticketprinting';
+	const GLOBAL_CONF_SEND_TO_EMAIL = 'BBUS_INVOICE_PRINTING_ALERT_EMAIL';
+	const GLOBAL_CONF_EMAIL_FROM = 'BBUS_INVOICE_PRINTING_ALERT_EMAIL_FROM';
+
+	public function __construct()
+	{
+		global $db, $conf, $user;
+
+		$this->db = $db;
+		$this->user = $user;
+	}
+
+	/**
+	 * First ticket validation
+	 * 
+	 * @param  string $code
+	 * 
+	 * @return array|mixed Data without useless information
+	 * 
+	 * @url POST search
+	 */
+
+	public function search($code)
+	{
+		$this->checkUserReadPermission();
+		global $user;
+		$this->code = $code;
+		$this->facture_id = $this->getFactureRowIdByFactureId();
+
+		$sql = "SELECT bt.*, pr.ref as product_ref, pr.label as product_label, btp.printing_date, btp.printing_date_timestamp
+		FROM llx_bbus_bbticketinvoiceprinting as btp
+		INNER JOIN llx_bbus_bbticket AS bt ON bt.rowid = btp.ticket_id
+		INNER JOIN llx_product AS pr ON pr.rowid = bt.ticket_id
+		WHERE btp.fk_facture = {$this->facture_id}
+		  AND btp.printing_date = (
+			SELECT MIN(printing_date)
+			FROM llx_bbus_bbticketinvoiceprinting AS btp2
+			WHERE btp2.fk_facture = {$this->facture_id}
+		)";
+		$ticketsData = $this->db->query($sql);
+		while ($row = pg_fetch_assoc($ticketsData)) {
+			$now = date("Y-m-d H:i:s");
+			if ($row['usage'] == $row['usable_occasions'] && $row['usable_occasions'] != 0) {
+				$this->isValid = false;
+			}
+			if ($row['available_at'] < $now || ((!is_null($row['expire_at']) && !empty($row['expire_at'])) && $row['expire_at'] < $now)) {
+				$this->isValid = false;
+			}
+
+			$row['facture_ref'] = $code;
+			$row['isValid'] = $this->isValid;
+			$sub = [];
+			foreach ($row as $key => $value) {
+				$sub[$key] = $value;
+			}
+			$json[] = $sub;
+		}
+		return $json;
+	}
+
+	/**
+	 * Értékesítések
+	 * 
+	 * @param  date $date	Date
+	 * 
+	 *
+	 * @return array|mixed Data without useless information
+	 *
+	 * @url POST getsaleditems
+	 */
+	public function getsaleditems($date = null)
+	{
+		global $user;
+
+		if (!DolibarrApiAccess::$user->rights->produit->lire) {
+			throw new RestException(403);
+		}
+		//$date = isset($date) ? $date : date("Y-m-d");
+		$date = date("Y-m-d");
+
+		$apiBbusHelper = new ApiBBusHelper();
+		$facturesArray = $apiBbusHelper->getSaledItems($date);
+
+		return $apiBbusHelper->getSaledItemsArray($facturesArray);
+	}
+
+	/** LOG OK
+	 * Get properties of a product object by barcode
+	 *
+	 * Return an array with product information.
+	 *
+	 * @param  string $code	Facture ID and timestamp of printing
+	 *
+	 * @return array|mixed Data without useless information
+	 *
+	 * @url POST ticketinfos
+	 */
+	public function getinfos($code)
+	{
+		global $user;
+		ApiBbusLog::getinfosLog('=== NEW TICKETINFOS ===');
+		ApiBbusLog::getinfosLog("User: {$user->firstname} {$user->lastname} (ID: {$user->id})");
+		ApiBbusLog::getinfosLog("DOLAPIKEY: {$user->api_key}");
+		$ticketsFromBbticketinvoiceprinting = [];
+		$code_and_timstamp = explode('_', $code);
+		$this->code = $code_and_timstamp[0];
+		if (empty($this->code)) {
+			ApiBbusLog::getinfosLog('Code or mac is empty!');
+			ApiBbusLog::getinfosLog('=================');
+			throw new RestException(404, 'Code or mac is empty!');
+		}
+		$this->printingTime = $code_and_timstamp[1];
+		if (empty($this->printingTime)) {
+			ApiBbusLog::getinfosLog('Timestamp is empty!');
+			ApiBbusLog::getinfosLog('=================');
+			throw new RestException(404, 'Timestamp is empty!');
+		}
+		if (!DolibarrApiAccess::$user->rights->bbus->ticket->validation) {
+			ApiBbusLog::getinfosLog('No access: Ticket Validation!');
+			ApiBbusLog::getinfosLog('=================');
+			throw new RestException(403);
+		}
+		ApiBbusLog::getinfosLog("Code: {$this->code}");
+		ApiBbusLog::getinfosLog("Code: {$this->printingTime}");
+
+		//Kikeresem a facture_id-t
+		$facture = new Facture($this->db);
+		$sql = "SELECT * FROM " . $this->db->prefix() . $facture->table_element . " WHERE ref ILIKE '{$this->code}'";
+		$factureResult = $this->db->query($sql);
+		if (pg_num_rows($factureResult) < 1) {
+			ApiBbusLog::getinfosLog('Invoice not found!');
+			ApiBbusLog::getinfosLog('=================');
+			throw new RestException(404, 'Invoice not found!');
+		}
+		while ($row = pg_fetch_assoc($factureResult)) {
+			$selectedFacture_id = $row['rowid'];
+		}
+
+		ApiBbusLog::getinfosLog('Facture ID: ' . (string) $selectedFacture_id);
+
+
+		$sql = "SELECT rowid FROM " . $this->db->prefix() . $facture->table_element . " WHERE fk_facture_source = {$selectedFacture_id} AND type = 2";
+		$res = $this->db->query($sql);
+		if (pg_num_rows($res) > 0) {
+			ApiBbusLog::getinfosLog('Invoice has a Credit account!');
+			ApiBbusLog::getinfosLog('=================');
+			throw new RestException(404, 'Invoice has a Credit account.');
+		}
+
+		// Kikeresem az llx_bbus_bbticketinvoiceprinting táblából a timestamp-hez tartozo jegy Id(ka)t
+		$bbticketinvoiceprinting = new BbTicketInvoicePrinting($this->db);
+		$result = $bbticketinvoiceprinting->fetchAll('', '', 0, 0, ['customsql' => "printing_date_timestamp = '{$this->printingTime}' AND fk_facture = {$selectedFacture_id}"]);
+		$this->checkResult($result, 'bbticketinvoiceprinting');
+		foreach ($result as $device) {
+			$ticketsFromBbticketinvoiceprinting[] = $device->ticket_id;
+		}
+		$inString = implode(', ', $ticketsFromBbticketinvoiceprinting);
+		//echo $inString;exit;
+		// A ticket_id alapjan, hogy a jegyek kozott szerepel-e es ervenyes-e
+		$bbticket = new BbTicket($this->db);
+		$sql = "SELECT tic.rowid as ticketrowid, tic.*, bbd.*, fac.* FROM " . $this->db->prefix() . $bbticket->table_element . " AS tic
+		INNER JOIN public.llx_product as bbd ON tic.ticket_id = bbd.rowid
+		INNER JOIN public.llx_facture as fac ON tic.fk_facture = fac.rowid
+		WHERE tic.rowid IN ({$inString})";
+		$statement = $this->db->query($sql);
+		while ($row = pg_fetch_assoc($statement)) {
+			$sub = [];
+			foreach ($row as $key => $value) {
+				$sub[$key] = $value;
+			}
+			$json[] = $sub;
+		}
+		ApiBbusLog::getinfosLog('Status: OK!');
+		ApiBbusLog::getinfosLog('=====================');
+		return $json;
+	}
+
+	/**
+	 * Get properties of a product object by barcode
+	 *
+	 * Return an array with product information.
+	 *
+	 *
+	 * @return array|mixed Data without useless information
+	 *
+	 * @url GET product
+	 */
+	public function getProducts()
+	{
+		global $user;
+		$productsArray = [];
+		$products = new Product($this->db);
+		$sql = "SELECT price, default_vat_code, rowid FROM " . $this->db->prefix() . $products->table_element . " WHERE fk_product_type = 1 ORDER BY rowid ASC";
+		$res = $this->db->query($sql);
+		while ($row = pg_fetch_assoc($res)) {
+			$productsArray[$row['rowid']] = $row;
+		}
+		return 'OK';
+	}
+
+	/** LOG OK
+	 * Validate a product bbticket id.
+	 *
+	 * Return an array with product validation data.
+	 *
+	 * @param  string $code	Facture ID and timestamp of printing
+	 * @param  string $ticketid	Ticketid (bbticket rowid)
+	 * @param  string $imei	IMEI (Mobile IMEI number)
+	 *
+	 * @return array|mixed Data without useless information
+	 *
+	 * @url POST ticketvalidationbyphone
+	 */
+	public function validateTicket($code, $ticketid, $imei)
+	{
+		global $user;
+		$logId = ApiBbusLog::getLogId();
+		ApiBbusLog::ticketvalidationByPhone("{$logId} === NEW TICKETVALIDATION BY PHONE ===");
+		ApiBbusLog::ticketvalidationByPhone("{$logId} User: {$user->firstname} {$user->lastname} (ID: {$user->id})");
+		ApiBbusLog::ticketvalidationByPhone("{$logId} DOLAPIKEY: {$user->api_key}");
+		ApiBbusLog::ticketvalidationByPhone("{$logId} code: {$code}");
+		$ticketChecker = new TicketChecker();
+
+
+		$ticketChecker->setCode($code);
+		$ticketChecker->setTimestamp($code, 'ticketvalidationByPhone', $logId);
+		$ticketChecker->SetTicketidFromPhone($ticketid, $logId);
+		$ticketChecker->setImei($imei, $logId);
+
+		$this->checkUserValidatePermission();
+
+		$ticketChecker->setFactureId('ticketvalidationByPhone', $logId);
+		$ticketChecker->checkBbTicketInvoicePrinting();
+		$selectedTicket = $ticketChecker->getDataOfTheSelectedTicket();
+
+		$ticketChecker->setBbTicketRowId($selectedTicket->id);
+
+		$ticketChecker->checkExceptionHandlers($selectedTicket, 'ticketvalidationByPhone', $logId);
+
+		$ticketChecker->setMergedTickets($selectedTicket, 'ticketvalidationByPhone', $logId);
+
+		$ticketChecker->saveData($selectedTicket);
+		ApiBbusLog::ticketvalidationByPhone("{$logId} Status: OK!");
+		ApiBbusLog::ticketvalidationByPhone("{$logId} =================");
+		return 'OK';
+	}
+
+	/** LOG OK
+	 * Get properties of a product object by barcode
+	 *
+	 * Return an array with product information.
+	 *
+	 * @param  string $code	Facture ID and timestamp of printing
+	 * @param  string $mac	MAC adress of the device
+	 *
+	 * @return array|mixed Data without useless information
+	 *
+	 * @url POST barcode
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 */
+
+	public function getByBarcode(string $code, string $mac, string $lat = null, string $lon = null)
+	{
+		global $user;
+		$logId = ApiBbusLog::getLogId();
+		ApiBbusLog::getByBarcode("{$logId} === NEW OBU validation ===");
+		ApiBbusLog::getByBarcode("{$logId} User: {$user->firstname} {$user->lastname} (ID: {$user->id})");
+		ApiBbusLog::getByBarcode("{$logId} DOLAPIKEY: {$user->api_key}");
+		$bbticket = new BbTicket($this->db);
+		$ticketChecker = new TicketChecker();
+
+		if (empty($code) || empty($mac)) {
+			ApiBbusLog::getByBarcode("{$logId} Code or mac is empty!");
+			throw new RestException(404, 'Code or mac is empty!');
+		}
+
+		ApiBbusLog::getByBarcode("{$logId} Code: " . $code);
+		ApiBbusLog::getByBarcode("{$logId} MAC: " . $mac);
+		ApiBbusLog::getByBarcode("{$logId} Latitude: " . $lat);
+		ApiBbusLog::getByBarcode("{$logId} Longitude: " . $lon);
+		$ticketChecker->setMac($mac);
+		$ticketChecker->setCode($code);
+		$ticketChecker->setLat($lat);
+		$ticketChecker->setLon($lon);
+		$ticketChecker->setTimestamp($code, 'getByBarcode', $logId);
+
+		$this->checkUserReadPermission();
+
+		$ticketChecker->setFactureId('getByBarcode', $logId);
+		$ticketChecker->setFilsArray();
+
+		$ticketChecker->setTicketId($logId);
+		$selectedTicket = $ticketChecker->getDataOfTheSelectedTicket();
+
+		$ticketChecker->setBbTicketRowId($selectedTicket->id);
+		$ticketChecker->check5Minutes($logId);
+		$ticketChecker->checkExceptionHandlers($selectedTicket, 'getByBarcode', $logId);
+
+		$ticketChecker->setMergedTickets($selectedTicket, 'getByBarcode', $logId);
+		$ticketChecker->saveCoordinates($logId);
+		$ticketChecker->saveData($selectedTicket);
+
+		ApiBbusLog::getByBarcode("{$logId} Status: OK!");
+		ApiBbusLog::getByBarcode("{$logId} =================");
+		return 'OK';
+	}
+
+	/**
+	 * Post invoice ref and creation date for data recording
+	 *
+	 * Return an array with success information.
+	 *
+	 * @param  string $type_id	Type id
+	 * @param  string $ref	Facture ref
+	 * @param  string $datetime	invoice creation date
+	 *
+	 * @return array|mixed Data without useless information
+	 *
+	 * @url POST printdate
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 */
+	public function dateHandler(string $type_id, string $ref, string $datetime)
+	{
+		global $user, $conf;
+		ApiBbusLog::appLog("dateHandler");
+
+		if (!DolibarrApiAccess::$user->rights->produit->lire) {
+			throw new RestException(403);
+		}
+		if (empty($ref)) {
+			throw new RestException(401, 'Empty ref!');
+		}
+		if (empty($datetime)) {
+			throw new RestException(401, 'Empty datetime!');
+		}
+		ApiBbusLog::appLog("dateHandler: Access granted");
+
+		$printDate = DateTime::createFromFormat('Y-m-d H:i:s', $datetime);
+		$now = new DateTime('now');
+		$now = (clone $now)->modify('+2 minutes');
+		$validDate = (clone $now)->modify('-5 minutes');
+		$datetime = $printDate->format('Y-m-d H:i:s');
+		$datetime_timestamp = strtotime($datetime);
+
+		dol_syslog('printDate: ' . print_r($printDate, true), LOG_DEBUG | LOG_INFO | LOG_WARNING | LOG_ERR);
+		dol_syslog('now: ' . print_r($now, true), LOG_DEBUG | LOG_INFO | LOG_WARNING | LOG_ERR);
+		dol_syslog('validDate: ' . print_r($validDate, true), LOG_DEBUG | LOG_INFO | LOG_WARNING | LOG_ERR);
+
+		//print_r($validDate);
+		//print_r($printDate);
+		//print_r($now);
+
+		if ($validDate <= $printDate && $printDate <= $now) {
+			$server_host = $this->getServerHost($type_id);
+			# Keresztvásárlás
+			if ($server_host == $conf->global->LOCAL_SERVER_HOST) {
+				ApiBbusLog::appLog("dateHandler: local");
+				ApiBbusLog::appLog("dateHandler: {$datetime_timestamp}");
+				$this->localDateHandler($ref, $datetime, $datetime_timestamp, $now, $type_id);
+			} else {
+				ApiBbusLog::appLog("dateHandler: curl");
+				$this->curlDateHandler($ref, $datetime, $type_id);
+			}
+		} else {
+			ApiBbusLog::appLog("dateHandler: Invalid date");
+			throw new RestException(401, 'Invalid date');
+		}
+		return 'OK';
+	}
+
+
+	private function localDateHandler($ref, $datetime, $datetime_timestamp, $now)
+	{
+		global $user;
+		$helper = new ApiBBusHelper();
+		$facture_id = $helper->getFactureIdForInvoicePrinting($ref);
+		if (empty($facture_id)) {
+			ApiBbusLog::appLog("localDateHandler: no facture_id because cross-shopping");
+			$printedCopies = $this->checkPrintedCopiesWithRef($ref);
+		} else {
+			$printedCopies = $this->checkPrintedCopies($facture_id);
+		}
+		// Leellenőrzöm, hogy a számla volt-e már nyomtatva
+
+		ApiBbusLog::appLog("printedCopies: {$printedCopies}");
+
+
+		if ($printedCopies == 0) {
+			ApiBbusLog::appLog("printedCopies: no copies");
+			if (empty($facture_id)) {
+				$bbTicketsByFacture = $this->getTicketsByInvoiceNumber($ref);
+			} else {
+				$bbTicketsByFacture = $this->getTicketsByFactureId($facture_id);
+			}
+			foreach ($bbTicketsByFacture as $ticket) {
+				// Rogzitem a bbticketinvoiceprinting tablaba a rekordot
+				$helper->setPrintingInvoiceObject($user, $facture_id, $datetime, $datetime_timestamp, $ticket, $ref);
+			}
+		} else {
+			ApiBbusLog::appLog("printedCopies: MULTIPRINT");
+			if (empty($facture_id)) {
+				$ticketIds = $helper->getTicketIdsForCrossShopping($ref);
+			} else {
+				$ticketIds = $helper->getTicketIdsByFactureId($facture_id, $ref);
+			}
+			$ticketObj = new stdClass();
+			foreach ($ticketIds as $key => $value) {
+				// Rogzitem a bbticketinvoiceprinting tablaba a rekordot
+				$ticketObj->id = $key;
+				$ticketObj->ticket_id = $value;
+				$helper->setPrintingInvoiceObject($user, $facture_id, $datetime, $datetime_timestamp, $ticketObj, $ref);
+			}
+			// e-mail küldése
+			$helper->sendMail($facture_id, $datetime, $now->format('Y-m-d H:i:s'));
+			return 'Multiprinting';
+		}
+	}
+	/* 	private function localDateHandler($ref, $datetime, $datetime_timestamp, $now)
+	{
+		global $user;
+		$helper = new ApiBBusHelper();
+		$facture_id = $helper->getFactureIdForInvoicePrinting($ref);
+
+		// Leellenőrzöm, hogy a számla volt-e már nyomtatva
+		$printedCopies = $this->checkPrintedCopies($facture_id);
+		if ($printedCopies == 0) {
+			$bbTicketsByFacture = $this->getTicketsByFactureId($facture_id);
+			foreach ($bbTicketsByFacture as $ticket) {
+				// Rogzitem a bbticketinvoiceprinting tablaba a rekordot
+				$helper->setPrintingInvoiceObject($user, $facture_id, $datetime, $datetime_timestamp, $ticket, $ref);
+			}
+		} else {
+			$ticketIds = $helper->getTicketIdsByFactureId($facture_id);
+			$ticketObj = new stdClass();
+			foreach ($ticketIds as $key => $value) {
+				// Rogzitem a bbticketinvoiceprinting tablaba a rekordot
+				$ticketObj->id = $key;
+				$ticketObj->ticket_id = $value;
+				$helper->setPrintingInvoiceObject($user, $facture_id, $datetime, $datetime_timestamp, $ticketObj, $ref);
+			}
+			// e-mail küldése
+			$helper->sendMail($facture_id, $datetime, $now->format('Y-m-d H:i:s'));
+			//return 'Multiprinting';
+		}
+	} */
+
+	public function curlDateHandler($ref, $datetime, $type_id)
+	{
+		$params = compact('ref', 'datetime', 'type_id');
+		$datehandlerPostFields = json_encode($params);
+		ApiBbusLog::appLog("dateHandler: {$datehandlerPostFields}");
+		$this->curlRunner('bbus/printdate', $datehandlerPostFields, 'POST', true);
+	}
+
+	/**
+	 * Save customer data (zip or countrycode) in facture table
+	 *
+	 * Return a result.
+	 *
+	 * @param  string $facture_id	Facture rowid
+	 * @param  string $customerdatazip	Customer data ZIP
+	 * @param  string $customerdatacountrycode	Customer data Countrycode
+	 *
+	 * @return array|mixed Data without useless information
+	 *
+	 * @url POST customerDataHandler
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 */
+	public function customerDataHandler(string $facture_id, string $customerdatazip = null, string $customerdatacountrycode = null)
+	{
+		if (is_null($customerdatazip) && is_null($customerdatacountrycode)) {
+			dol_syslog("Nem sikerult a facture updateje. Ref: " . $this->facture_id, LOG_DEBUG | LOG_INFO | LOG_WARNING | LOG_ERR);
+			throw new RestException(404, 'Empty params');
+		}
+		$sql = "UPDATE " . $this->db->prefix() . "facture_extrafields SET ";
+		$sql .= !is_null($customerdatazip) ? "customer_data_zip = '{$customerdatazip}' " : "customer_data_countrycode = '{$customerdatacountrycode}' ";
+		$sql .= "WHERE fk_object = '{$facture_id}'";
+		$this->factureUpdate($sql, $facture_id);
+		return "OK";
+	}
+
+	/**
+	 * Check user and eventdetail type permissions
+	 *
+	 * Return a result.
+	 *
+	 * @return array|mixed Data without useless information
+	 *
+	 * @url GET checkPermission
+	 */
+	public function checkPermission()
+	{
+		global $user, $db;
+		$permissionArray = [];
+		$groupUsersObj = new GroupUsers($db);
+		$result = $groupUsersObj->fetchAll('desc', 'rowid', 1, 0, ["customsql" => "fk_user = {$user->id}"]);
+		if ($result == -1 || empty($result)) {
+			return $permissionArray;
+		}
+		dol_include_once('/eventwizard/class/eventdetails.class.php');
+		$sqlBasicService = "SELECT pe.basic_service FROM llx_product as p
+			INNER JOIN llx_product_extrafields as pe ON pe.fk_object = p.rowid
+			GROUP BY pe.basic_service";
+		$resultBasicServices = $db->query($sqlBasicService);
+		if ($db->num_rows($resultBasicServices) > 0) {
+			while ($row = $db->fetch_object($resultBasicServices)) {
+				$basicServices[] = $row->basic_service;
+			}
+		}
+		$sqlBasicServicesTable = "SELECT basic_service_id, ref, is_event FROM llx_bbus_basicServices ORDER BY basic_service_id ASC";
+		$resultBasicServicesObj = $db->query($sqlBasicServicesTable);
+		if ($db->num_rows($resultBasicServicesObj) > 0) {
+			while ($bsrow = $db->fetch_object($resultBasicServicesObj)) {
+				foreach ($basicServices as $item) {
+					if ($bsrow->basic_service_id == $item && $item != 2) {
+						if ($bsrow->is_event == '1' && $this->isAvailablePlaces($item)) {
+							$permissionArray[$item] = $bsrow->ref;
+						} elseif ($bsrow->is_event != '1') {
+							$permissionArray[$item] = $bsrow->ref;
+						}
+					}
+				}
+			}
+		}
+		//$curlPermissionArray = $this->curlRunner('rollerstorageapi/checkPermission', '');
+		//$permissionArray = $permissionArray + (array)json_decode($curlPermissionArray);
+		return $permissionArray;
+	}
+
+	private function isAvailablePlaces($item)
+	{
+		global $conf;
+		$available = false;
+		$server_host = $this->getServerHost($item);
+		$sqlProducts = "SELECT fk_object, basic_service FROM llx_product_extrafields WHERE basic_service = '{$item}'";
+		$resultProducts = $this->db->query($sqlProducts);
+		if ($this->db->num_rows($resultProducts) > 0) {
+			while ($prodductsRow = $this->db->fetch_object($resultProducts)) {
+				$productsArray[$prodductsRow->fk_object] = $prodductsRow->basic_service;
+			}
+			if (!empty($productsArray)) {
+				foreach ($productsArray as $key => $value) {
+					if ($server_host == $conf->global->LOCAL_SERVER_HOST) {
+						$availableResult = $this->localCheckAvailablePlacesForCheckPermission($key);
+					} elseif ($server_host == $conf->global->CURL_SERVER_HOST) {
+						$postFields = '{"product_id":"' . $key . '"}';
+						$availableResult = $this->curlRunner('bbus/localCheckAvailablePlacesForCheckPermission', $postFields, 'POST', false);
+					}
+					if ($availableResult == 1) {
+						$available = true;
+					}
+				}
+			}
+		};
+		return $available;
+	}
+
+	/**
+	 * localCheckAvailablePlacesForCheckPermission
+	 *
+	 * @param  int $product_id			Product rowid
+	 *
+	 * @return array|mixed Data without useless information
+	 *
+	 * @url POST localCheckAvailablePlacesForCheckPermission
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 */
+	public function localCheckAvailablePlacesForCheckPermission($product_id)
+	{
+		$sql = "SELECT 
+			ac.id
+			FROM llx_actioncomm as ac 
+			LEFT JOIN llx_actioncomm_extrafields as ace ON ace.fk_object = ac.id
+			INNER JOIN llx_eventwizard_eventdetails as ed ON ac.fk_element = ed.rowid
+			INNER JOIN llx_eventwizard_eventproduct as ep ON ep.fk_eventdetails = ac.fk_element
+			WHERE ac.code = 'AC_EVENT'
+			AND ep.fk_product = {$product_id}
+			AND ac.datep > NOW() 
+			ORDER BY ac.datep ASC";
+			$result = $this->db->query($sql);
+		return $this->db->num_rows($result) > 0 ? 1 : 0;
+		
+	}
+
+	/**
+	 * Create naplo for userlogout
+	 *
+	 * Return a result.
+	 *
+	 * @return array|mixed Data without useless information
+	 *
+	 * @url POST logoutNaploCreator
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 */
+
+	public function logoutNaploCreator()
+	{
+		global $user, $db;
+		$error = 0;
+		$db->begin();
+
+		$group_user_id = $this->getGroupUserIdByUserId($user->id);
+		if ($group_user_id == -1) {
+			$error++;
+		}
+		$result = $this->isLastStatusLogout($user);
+		if ($result !== '') {
+			return date('Y-m-d H:i:s', $result);
+		}
+
+		$userLoginNaplo = new UserLoginNaplo($db);
+		$userLoginNaplo->login_logout_status = 1;
+		$userLoginNaplo->user_id = $user->id;
+		$result = $userLoginNaplo->create($user);
+		if ($result < 0) {
+			$error++;
+		}
+
+		$userNaploObj = new UserNaplo($db);
+		$userNaploObj->user_id = $user->id;
+		$userNaploObj->group_user_id = $group_user_id;
+		$userNaploObj->status = 0;
+		$result = $userNaploObj->create($user);
+		if ($result < 0) {
+			$error++;
+		}
+
+		$groupUsersObj = new GroupUsers($db);
+		$sql = "SELECT rowid FROM " . MAIN_DB_PREFIX . "settlements_groupusers WHERE fk_user = {$user->id}";
+		$data = $db->query($sql);
+		$dataArray = pg_fetch_assoc($data);
+		$selectedGroupId = $dataArray['rowid'];
+		if (isset($selectedGroupId)) {
+			$resultKickOff = $groupUsersObj->deleteLine($user, $selectedGroupId);
+			if (!$resultKickOff) {
+				$error++;
+			}
+		}
+
+		if ($error) {
+			$db->rollback();
+		}
+		$db->commit();
+		return 'OK';
+	}
+
+	/**
+	 * Get the discount unit From Product
+	 *
+	 * Return a result.
+	 *
+	 * @param  int $product_id			Product rowid
+	 * 
+	 * @return string discount unit
+	 *
+	 * @url POST getDiscountUnit
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 */
+	public function getDiscountUnit(int $product_id)
+	{
+		global $db, $user;
+		$ProductObj = new Product($db);
+		$ProductObj->fetch($product_id);
+		return $ProductObj->array_options['discount_period'];
+	}
+
+	/**
+	 * Create naplo for Discount
+	 *
+	 * Return a result.
+	 *
+	 * @param  int $facture_id			Facture rowid
+	 * @param  int $product_id			Product rowid
+	 * @param  int $discount			Discount
+	 * @param  string $discount_unit	Discount unit
+	 * 
+	 * @return array|mixed Data without useless information
+	 *
+	 * @url POST createDiscountNaplo
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 */
+	public function createDiscountNaplo(int $facture_id, int $product_id, int $discount, string $discount_unit)
+	{
+		global $db, $user;
+		$discountNaploObj = new DiscountNaplo($db);
+		$discountNaploObj->fk_facture = $facture_id;
+		$discountNaploObj->fk_product = $product_id;
+		$discountNaploObj->discount = $discount;
+		$discountNaploObj->discount_unit = $discount_unit;
+		$result = $discountNaploObj->create($user);
+		if ($result < 0) {
+			return false;
+		}
+		return $result;
+	}
+
+	/**
+	 * Get the products from Product
+	 *
+	 * Return a result.
+	 *
+	 * @param  int $entity	entity
+	 * 
+	 * @return array|mixed Data without useless information
+	 *
+	 * @url POST getHotelSalesProducts
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 */
+	public function getHotelSalesProducts(int $entity)
+	{
+		global $db, $user;
+		$sql = "SELECT p.* FROM public.llx_product AS p 
+		INNER JOIN public.llx_product_extrafields as pe ON pe.fk_object = p.rowid
+		WHERE p.entity = {$entity} AND pe.hotelsales = 1";
+		$products = $this->db->query($sql);
+		$row = pg_fetch_all($products);
+		return $row;
+	}
+
+	private function getGroupUserIdByUserId($user_id)
+	{
+		global $db;
+		$groupUsersObj = new GroupUsers($db);
+		$result = $groupUsersObj->fetchAll('DESC', 'rowid', 1, 0, ["customsql" => "fk_user = {$user_id}"]);
+		if (!empty($result)) {
+			foreach ($result as $record) {
+				return $record->fk_settlements_group;
+			}
+		}
+		return -1;
+	}
+
+	private function isLastStatusLogout($user)
+	{
+		global $db;
+		$userLoginNaplo = new UserLoginNaplo($db);
+		$result = $userLoginNaplo->fetchAll('DESC', 'date_creation', 1, 0, array('user_id' => $user->id));
+		foreach ($result as $lastrecord) {
+			return $lastrecord->login_logout_status == 1 ? $lastrecord->date_creation : '';
+		}
+	}
+
+	private function factureUpdate($sql, $facture_id)
+	{
+		$updated = $this->db->query($sql);
+		if (!$updated) {
+			dol_syslog("Nem sikerult a facture updateje. rowid: " . $facture_id, LOG_DEBUG | LOG_INFO | LOG_WARNING | LOG_ERR);
+			throw new RestException(404, 'Update failed');
+		}
+	}
+
+	private function checkUserReadPermission()
+	{
+		if (!DolibarrApiAccess::$user->rights->produit->lire) {
+			throw new RestException(403);
+		}
+	}
+
+	private function checkUserValidatePermission()
+	{
+		if (!DolibarrApiAccess::$user->rights->bbus->ticket->validation) {
+			throw new RestException(403);
+		}
+	}
+
+	private function getFactureRowIdByFactureId()
+	{
+		$facture = new Facture($this->db);
+		$sqlFacture = "SELECT rowid FROM public.llx_facture WHERE ref ILIKE '%{$this->code}%'";
+		$result = $this->db->query($sqlFacture);
+		if (pg_num_rows($result) > 0) {
+			while ($adatok = pg_fetch_assoc($result)) {
+				$factureRowid = $adatok['rowid'];
+			}
+		} else {
+			throw new RestException(404, 'Invoice not found.');
+		}
+		$sql = "SELECT rowid FROM llx_facture WHERE fk_facture_source = {$factureRowid} AND type = 2";
+		$res = $this->db->query($sql);
+		if (pg_num_rows($res) > 0) {
+			throw new RestException(404, 'Invoice has a Credit account.');
+		}
+
+		return $factureRowid;
+	}
+
+	private function checkResult($result, $tableName)
+	{
+		if (!is_array($result) || empty($result)) {
+			dol_syslog("A megadott szuresi adatokhoz nem tartozik rekord ({$tableName}).", LOG_DEBUG | LOG_INFO | LOG_WARNING | LOG_ERR);
+			throw new RestException(404, "A megadott szuresi adatokhoz nem tartozik rekord ({$tableName}).");
+		}
+	}
+
+	private function saveBbTicketNaplo($user)
+	{
+		$bbTicketNaplo = new BbTicketNaplo($this->db);
+		$bbTicketNaplo->ticket_row_id = $this->bbTicketRowId;
+		$bbTicketNaplo->bbservicelocation_id = isset($this->service_location_id) ? $this->service_location_id : null;
+		$bbTicketNaplo->device_id = isset($this->device_id) ? $this->device_id : null;
+		$bbTicketNaplo->status = $this->status;
+
+
+		if ($bbTicketNaplo->create($user) < 0) {
+			dol_syslog('Nem sikerult menteni a bbticketNaplo tablaba a rekordot.', LOG_DEBUG | LOG_INFO | LOG_WARNING | LOG_ERR);
+			throw new RestException(500, 'Nem sikerult menteni a bbticketNaplo tablaba a rekordot.');
+		}
+	}
+
+	private function getTicketsByFactureId($facture_id)
+	{
+		$bbticket = new BbTicket($this->db);
+		$bbTicketsByFacture = $bbticket->fetchAll('', '', 0, 0, ['customsql' => 'fk_facture = ' . intval($facture_id)]);
+		if ($bbTicketsByFacture < 1) {
+			throw new RestException(404, 'BBTicket not found');
+		}
+		return $bbTicketsByFacture;
+	}
+
+	private function getTicketsByInvoiceNumber($ref)
+	{
+		$bbticket = new BbTicket($this->db);
+		$bbTicketsByFacture = $bbticket->fetchAll('', '', 0, 0, ["customsql" => "invoice_number =  '" . $ref . "'"]);
+		if ($bbTicketsByFacture < 1) {
+			ApiBbusLog::appLog("getTicketsByInvoiceNumber: BBTicket not found");
+			throw new RestException(404, 'BBTicket not found');
+		}
+		ApiBbusLog::appLog("getTicketsByInvoiceNumber: I got it");
+		return $bbTicketsByFacture;
+	}
+
+
+	private function checkPrintedCopies($facture_id)
+	{
+		$bbticketinvoiceprinting = new BbTicketInvoicePrinting($this->db);
+		$copies = $bbticketinvoiceprinting->fetchAll('ASC', 'rowid', 0, 0, ['customsql' => 'fk_facture = ' . intval($facture_id)]);
+		return (is_array($copies)) ? count($copies) : 0;
+	}
+
+	private function checkPrintedCopiesWithRef($ref)
+	{
+		$bbticketinvoiceprinting = new BbTicketInvoicePrinting($this->db);
+		$copies = $bbticketinvoiceprinting->fetchAll('ASC', 'rowid', 0, 0, ["customsql" => "invoice_number = '" . $ref . "'"]);
+		return (is_array($copies)) ? count($copies) : 0;
+	}
+
+	/**
+	 * @param  string $facture_id	Facture ref
+	 *
+	 * @return array|mixed Data without useless information
+	 *
+	 * @url POST getRefFromFacture
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 */
+	public function getRefFromFacture(string $facture_id)
+	{
+		global $user;
+		$facture = new Facture($this->db);
+		$sql = "SELECT ref FROM " . $this->db->prefix() . $facture->table_element . " WHERE rowid = " . $facture_id;
+		$res = $this->db->query($sql);
+		while ($adatok = pg_fetch_assoc($res)) {
+			print_r($adatok['ref']);
+			exit;
+		}
+
+		return 'OK';
+	}
+
+	/**
+	 * Get properties of a product object by barcode
+	 *
+	 * Return an array with product information.
+	 *
+	 * @param  string $barcode            Barcode of element
+	 * @param  int    $includestockdata   Load also information about stock (slower)
+	 * @param  bool   $includesubproducts Load information about subproducts
+	 * @param  bool   $includeparentid    Load also ID of parent product (if product is a variant of a parent product)
+	 * @param  bool   $includetrans		  Load also the translations of product label and description
+	 *
+	 * @return array|mixed Data without useless information
+	 *
+	 * @url GET profileimage/{id}
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 */
+	public function getUserImage(int $id)
+	{
+		$image = null;
+		$dolApiKey = $_SERVER['HTTP_DOLAPIKEY'] ?? null;
+
+		if (empty($dolApiKey)) {
+			throw new RestException(401, 'Access not allowed');
+		}
+
+		$user = (new ApiUserHandler)->getUser($id, $dolApiKey);
+		if (empty($user->photo)) {
+			throw new RestException(404, 'Photo not found');
+		} else {
+			$originalFile = get_exdir(0, 0, 0, 0, $user, 'user') . 'photos/' . $user->photo;
+			$originalFile = DOL_DOCUMENT_ROOT . '/documents/users/' . $originalFile;
+
+			$filename = basename($originalFile);
+			$originalFileOsEncoded = dol_osencode($originalFile); // New file name encoded in OS encoding charset
+
+			$fileContent = file_get_contents($originalFileOsEncoded);
+			$image = [
+				'filename' => $filename,
+				'content-type' => dol_mimetype($filename),
+				'filesize' => filesize($originalFile),
+				'content' => base64_encode($fileContent),
+				'encoding' => 'base64'
+			];
+		}
+
+		return $image;
+	}
+
+	/**
+	 * Create invoices from the order
+	 *
+	 * @param int $id_order   		(Row)Id of the order
+	 * @param int $id_soc  			Id of 3th party
+	 * @return array                Response
+						   
+	 * @url POST invoicesFromOrder
+						  
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 */
+	public function invoicesFromOrder(int $id_order, int $id_soc = 0): array
+	{
+		// echo ini_get('memory_limit');
+		// exit;
+		if (!DolibarrApiAccess::$user->rights->facture->creer) {
+			throw new RestException(401, "Insuffisant rights");
+		}
+
+		require_once DOL_DOCUMENT_ROOT . '/compta/facture/class/facture.class.php';
+		// Standard or deposit invoice, not from a Predefined template invoice
+		// Si facture standard
+		$this->now = time();
+		$this->invoices = [];
+
+		$element == 'order';
+		$element = $subelement = 'commande';
+
+		//etalon obj:
+
+		$object = new Facture($this->db);
+		// $extrafields = new ExtraFields($db);
+		$defaultDevSocId = 1;
+		$this->defaultSocId = (isset($_ENV['DEFAULT_SOCID']) && (int) $_ENV['DEFAULT_SOCID']) ? (int) $_ENV['DEFAULT_SOCID'] : $defaultDevSocId;
+
+		$object->socid = (empty((int) $id_soc)) ? $this->defaultSocId : (int) $id_soc;
+		$object->type = null;
+		$object->ref = null;
+		$object->date = $this->now;
+		// $object->date_pointoftax 	= $date_pointoftax;
+		// $object->note_public		= trim(GETPOST('note_public', 'restricthtml'));
+		// $object->note_private = trim(GETPOST('note_private', 'restricthtml'));
+		// $object->ref_client			= GETPOST('ref_client');
+		$object->model_pdf = null;
+		// $object->fk_project			= GETPOST('projectid', 'int');
+		// $object->cond_reglement_id	= (GETPOST('type') == 3 ? 1 : GETPOST('cond_reglement_id'));
+		// $object->mode_reglement_id	= GETPOST('mode_reglement_id');
+		// $object->fk_account = GETPOST('fk_account', 'int');
+		// $object->amount = price2num(GETPOST('amount'));
+		// $object->remise_absolue		= price2num(GETPOST('remise_absolue'), 'MU');
+		// $object->remise_percent		= price2num(GETPOST('remise_percent'), '', 2);
+		// $object->fk_incoterms = GETPOST('incoterm_id', 'int');
+		// $object->location_incoterms = GETPOST('location_incoterms', 'alpha');
+		// $object->multicurrency_code = GETPOST('multicurrency_code', 'alpha');
+		// $object->multicurrency_tx   = GETPOST('originmulticurrency_tx', 'int');
+
+		$object->fetch_thirdparty();
+
+		// If creation from another object of another module (Example: origin=propal, originid=1)
+
+		$object->origin = $element;
+		$object->origin_id = $id_order;
+
+		// Possibility to add external linked objects with hooks
+		$object->linked_objects[$object->origin] = $object->origin_id;
+		// link with order if it is a shipping invoice
+
+		if (is_array($_POST['other_linked_objects']) && !empty($_POST['other_linked_objects'])) {
+			$object->linked_objects = array_merge($object->linked_objects, $_POST['other_linked_objects']);
+		}
+
+		//
+		dol_include_once('/' . $element . '/class/' . $subelement . '.class.php');
+		$classname = ucfirst($subelement);
+		$srcobject = new $classname($this->db);
+
+		$result = $srcobject->fetch($id_order);
+		if ($result > 0) {
+			$lines = $srcobject->lines;
+			if (empty($lines) && method_exists($srcobject, 'fetch_lines')) {
+				$srcobject->fetch_lines();
+				$lines = $srcobject->lines;
+			}
+			/*
+																																																						   // If we create a standard invoice with a percent, we change amount by changing the qty
+																																																						   if (is_array($lines)) {
+																																																						   foreach ($lines as $line) {
+																																																						   // We keep ->subprice and ->pa_ht, but we change the qty
+																																																						   $line->qty = price2num($line->qty * $valuestandardinvoice / 100, 'MS');
+																																																						   }
+																																																						   }
+																																																						   */
+			$fk_parent_line = 0;
+			$num = count($lines);
+			$invNum = 0;
+
+			for ($i = 0; $i < $num; $i++) {
+				// if (!in_array($lines[$i]->id, $selectedLines)) {
+				// continue; // Skip unselected lines
+				// }
+
+				//!!! create invoice by line:
+				$quantity = $lines[$i]->qty;
+				for ($j = 0; $j < $quantity; $j++) {
+					$this->invoices[$invNum] = clone $object;
+					if ($id = $this->invoices[$invNum]->create(DolibarrApiAccess::$user)) {
+						$this->invoices[$invNum]->update_price(1, 'auto', 0, $mysoc);
+
+						$label = (!empty($lines[$i]->label) ? $lines[$i]->label : '');
+						$desc = (!empty($lines[$i]->desc) ? $lines[$i]->desc : $lines[$i]->libelle);
+						if ($this->invoices[$invNum]->situation_counter == 1) {
+							$lines[$i]->situation_percent = 0;
+						}
+
+						if ($lines[$i]->subprice < 0 && empty($conf->global->INVOICE_KEEP_DISCOUNT_LINES_AS_IN_ORIGIN)) {
+							// Negative line, we create a discount line
+							require_once DOL_DOCUMENT_ROOT . '/core/class/discount.class.php';
+							$discount = new DiscountAbsolute($db);
+							$discount->fk_soc = $this->invoices[$invNum]->socid;
+							$discount->amount_ht = abs($lines[$i]->total_ht);
+							$discount->amount_tva = abs($lines[$i]->total_tva);
+							$discount->amount_ttc = abs($lines[$i]->total_ttc);
+							$discount->tva_tx = $lines[$i]->tva_tx;
+							$discount->fk_user = DolibarrApiAccess::$user->id;
+							$discount->description = $desc;
+							$discount->multicurrency_subprice = abs($lines[$i]->multicurrency_subprice);
+							$discount->multicurrency_amount_ht = abs($lines[$i]->multicurrency_total_ht);
+							$discount->multicurrency_amount_tva = abs($lines[$i]->multicurrency_total_tva);
+							$discount->multicurrency_amount_ttc = abs($lines[$i]->multicurrency_total_ttc);
+
+							$discountid = $discount->create(DolibarrApiAccess::$user);
+							if ($discountid > 0) {
+								$result = $this->invoices[$invNum]->insert_discount($discountid); // This include link_to_invoice
+							} else {
+								setEventMessages($discount->error, $discount->errors, 'errors');
+								$error++;
+								break;
+							}
+						} else {
+							// Positive line
+							$product_type = ($lines[$i]->product_type ? $lines[$i]->product_type : 0);
+
+							// Date start
+							$date_start = false;
+							if ($lines[$i]->date_debut_prevue) {
+								$date_start = $lines[$i]->date_debut_prevue;
+							}
+							if ($lines[$i]->date_debut_reel) {
+								$date_start = $lines[$i]->date_debut_reel;
+							}
+							if ($lines[$i]->date_start) {
+								$date_start = $lines[$i]->date_start;
+							}
+
+							// Date end
+							$date_end = false;
+							if ($lines[$i]->date_fin_prevue) {
+								$date_end = $lines[$i]->date_fin_prevue;
+							}
+							if ($lines[$i]->date_fin_reel) {
+								$date_end = $lines[$i]->date_fin_reel;
+							}
+							if ($lines[$i]->date_end) {
+								$date_end = $lines[$i]->date_end;
+							}
+
+							// Reset fk_parent_line for no child products and special product
+							if (($lines[$i]->product_type != 9 && empty($lines[$i]->fk_parent_line)) || $lines[$i]->product_type == 9) {
+								$fk_parent_line = 0;
+							}
+
+							// Extrafields
+							if (method_exists($lines[$i], 'fetch_optionals')) {
+								$lines[$i]->fetch_optionals();
+								$array_options = $lines[$i]->array_options;
+							}
+
+							$tva_tx = $lines[$i]->tva_tx;
+							if (!empty($lines[$i]->vat_src_code) && !preg_match('/\(/', $tva_tx)) {
+								$tva_tx .= ' (' . $lines[$i]->vat_src_code . ')';
+							}
+
+							// View third's localtaxes for NOW and do not use value from origin.
+							// TODO Is this really what we want ? Yes if source is template invoice but what if proposal or order ?
+							$localtax1_tx = get_localtax($tva_tx, 1, $this->invoices[$invNum]->thirdparty);
+							$localtax2_tx = get_localtax($tva_tx, 2, $this->invoices[$invNum]->thirdparty);
+
+							$result = $this->invoices[$invNum]->addline(
+								$desc,
+								$lines[$i]->subprice,
+								// $lines[$i]->qty,
+								1,
+								$tva_tx,
+								$localtax1_tx,
+								$localtax2_tx,
+								$lines[$i]->fk_product,
+								$lines[$i]->remise_percent,
+								$date_start,
+								$date_end,
+								0,
+								$lines[$i]->info_bits,
+								$lines[$i]->fk_remise_except,
+								'HT',
+								0,
+								$product_type,
+								$lines[$i]->rang,
+								$lines[$i]->special_code,
+								$this->invoices[$invNum]->origin,
+								$lines[$i]->rowid,
+								$fk_parent_line,
+								$lines[$i]->fk_fournprice,
+								$lines[$i]->pa_ht,
+								$label,
+								$array_options,
+								$lines[$i]->situation_percent,
+								$lines[$i]->fk_prev_id,
+								$lines[$i]->fk_unit,
+								0,
+								'',
+								1
+							);
+
+							if ($result > 0) {
+								$lineid = $result;
+							} else {
+								$lineid = 0;
+								$error++;
+								break;
+							}
+
+							// Defined the new fk_parent_line
+							if ($result > 0 && $lines[$i]->product_type == 9) {
+								$fk_parent_line = $result;
+							}
+						}
+					}
+
+					$invNum++;
+				}
+			}
+		} else {
+			setEventMessages($srcobject->error, $srcobject->errors, 'errors');
+			$error++;
+		}
+		$num = count($this->invoices);
+		$return = [
+			'validated' => []
+		];
+		// $used_mem = round(memory_get_usage(false) / 1024 / 1024); //mb
+		// $used_mem = round(memory_get_usage(true) / 1024 / 1024); //mb
+		// echo $used_mem. " / ".$allowed_mem; 
+		// echo ini_get('memory_limit');
+		// exit;
+
+		for ($i = 0; $i < $num; $i++) {
+
+			//!!! validation
+			// $idwarehouse = 0;
+			// $notrigger = 0;
+			// $result = $this->invoices[$i]->validate(DolibarrApiAccess::$user, '', $idwarehouse, $notrigger);
+			$id = $this->invoices[$i]->id;
+			$result = $this->invoices[$i]->validate(DolibarrApiAccess::$user, '', null);
+			if ($result >= 0) {
+				// Define output language
+				if (empty($conf->global->MAIN_DISABLE_PDF_AUTOUPDATE)) {
+					global $langs;
+					$outputlangs = $langs;
+					$newlang = '';
+					if (!empty($conf->global->MAIN_MULTILANGS) && empty($newlang) && GETPOST('lang_id', 'aZ09')) {
+						$newlang = GETPOST('lang_id', 'aZ09');
+					}
+					if (!empty($conf->global->MAIN_MULTILANGS) && empty($newlang)) {
+						$newlang = $this->invoices[$i]->thirdparty->default_lang;
+					}
+					if (!empty($newlang)) {
+						$outputlangs = new Translate("", $conf);
+						$outputlangs->setDefaultLang($newlang);
+						$outputlangs->load('products');
+					}
+					$model = $this->invoices[$i]->model_pdf;
+
+					$ret = $this->invoices[$i]->fetch($id); // Reload to get new records
+					// PDF
+					$hidedetails = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DETAILS)) ? 1 : 0;
+					$hidedesc = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_DESC)) ? 1 : 0;
+					$hideref = (!empty($conf->global->MAIN_GENERATE_DOCUMENTS_HIDE_REF)) ? 1 : 0;
+					$result = $this->invoices[$i]->generateDocument($model, $outputlangs, $hidedetails, $hidedesc, $hideref);
+					if ($result < 0) {
+						//...
+						// setEventMessages($this->invoices[$i]->error, $this->invoices[$i]->errors, 'errors');
+					}
+				}
+			} else {
+				// if (count($this->invoices[$i]->errors)) {
+				// setEventMessages(null, $this->invoices[$i]->errors, 'errors');
+				// } else {
+				// setEventMessages($this->invoices[$i]->error, $this->invoices[$i]->errors, 'errors');
+				// }
+			}
+
+			// if ($result == 0) {
+			// throw new RestException(304, 'Error nothing done. May be object is already validated');
+			// }
+			// if ($result < 0) {
+			// throw new RestException(500, 'Error when validating Invoice: '.$this->invoice->error);
+			// }
+
+			// $this->invoices[$i] = $this->invoicees[$i]->fetch($id);
+			if (!$this->invoices[$i]) {
+				throw new RestException(404, 'Invoice not found');
+			}
+
+			$this->invoices[$i] = parent::_cleanObjectDatas($this->invoices[$i]);
+			unset($this->invoices[$i]->note);
+			unset($this->invoices[$i]->address);
+			unset($this->invoices[$i]->barcode_type);
+			unset($this->invoices[$i]->barcode_type_code);
+			unset($this->invoices[$i]->barcode_type_label);
+			unset($this->invoices[$i]->barcode_type_coder);
+			unset($this->invoices[$i]->canvas);
+
+
+			if (!DolibarrApi::_checkAccessToResource('facture', $id)) {
+				throw new RestException(401, 'Access not allowed for login ' . DolibarrApiAccess::$user->login);
+			}
+
+			$return['validated'][] = $this->invoices[$i]->ref;
+		}
+		return $return;
+	}
+
+	/**
+	 * Jegy generálás -> (order?->) invoice -> payment -> jegy -> qr on the bill
+	 *
+	 * Return an array with prcess information.
+	 *
+	 * @param  array $orderDetails			Order details
+	 *
+	 * @return array|mixed Data without useless information
+	 *
+	 * @url POST order
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 */
+	public function jegy(array $orderDetails)
+	{
+		$return = [
+			"request" => $orderDetails
+		];
+		$Jegy = new Jegy();
+		$return['hash'] = [
+			$Jegy->generate_hash(),
+			$Jegy->generate_hash(1),
+			$Jegy->generate_hash(20),
+			$Jegy->generate_hash(50),
+			$Jegy->generate_hash(100),
+		];
+
+		return $return;
+	}
+
+	/**
+	 * Get app config
+	 *
+	 * Return an array with config params.
+	 *
+	 * @return array|mixed Data without useless information
+	 *
+	 * @url GET config
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 */
+	public function config(): array
+	{
+		global $user, $conf;
+		$helper = new ApiBBusHelper();
+		// company
+		$companyData = $helper->getCompany();
+
+		return [
+			'exchange_rate' => ExchangeRateUpdater::getExchangeRate(ExchangeRateUpdater::EUR, $conf->entity),
+			'invoice' => [
+				'header' => [
+					'name' => $companyData['company_data']['NOM'],
+					'tax_number' => $companyData['company_data']['TVAINTRA'],
+					'address' => [
+						'zip' => $companyData['company_data']['ZIP'],
+						'city' => $companyData['company_data']['TOWN'],
+						'address' => $companyData['company_data']['ADDRESS']
+					]
+				],
+				'customer' => $helper->getAppCustomers(),
+				'vat_percent' => 27,
+				'entity' => $user->entity,
+				'account_id' => $helper->getAccountRowid($user->entity),
+				'currencies' => $helper->getCurrenciesRowid($user->entity),
+				'payments_mode' => $helper->getPaymentsMode($user->entity)
+			]
+		];
+	}
+
+	/**
+	 * Get groupId for REACT
+	 *
+	 * Return an ID.
+	 *
+	 * @return integer Data without useless information
+	 *
+	 * @url GET getGroupId
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 */
+	public function getGroupId(): array
+	{
+		global $user, $db;
+		$sql = "SELECT fk_settlements_group FROM public.llx_settlements_groupusers WHERE fk_user = {$user->id} ORDER BY rowid DESC LIMIT 1";
+		$result = $db->query($sql);
+		while ($row = pg_fetch_assoc($result)) {
+			return $row;
+		}
+		return [];
+	}
+
+	/**
+	 * Insert a row into login log table
+	 *
+	 * @param  string $login		Login name
+	 *
+	 * @return array|mixed Data without useless information
+	 *
+	 * @url POST savelogin
+	 *
+	 * @throws RestException 404
+	 */
+	public function saveLogin(string $login)
+	{
+		$error = 0;
+		$this->db->begin();
+		$user = new User($this->db);
+		//$user->fetch(0, $login, 0, 0, 1);
+		$user->fetch(0, $login, 0, 0);
+		if (empty($user->id)) {
+			throw new RestException(404, 'User not found');
+		}
+
+		$log = new BbLoginLog($this->db);
+		$log->entity = 1;
+		$log->description = 'Login from App';
+		$id = $log->create($user);
+		if ($id < 1) {
+			$error++;
+			throw new RestException(500, 'Log failure');
+		}
+
+		$now = dol_now();
+
+		$userremoteip = getUserRemoteIP();
+
+		$sql = "UPDATE " . $this->db->prefix() . "user SET";
+		$sql .= " datepreviouslogin = datelastlogin,";
+		$sql .= " ippreviouslogin = iplastlogin,";
+		$sql .= " datelastlogin = '" . $this->db->idate($now) . "',";
+		$sql .= " iplastlogin = '" . $this->db->escape($userremoteip) . "',";
+		$sql .= " tms = tms"; // La date de derniere modif doit changer sauf pour la mise a jour de date de derniere connexion
+		$sql .= " WHERE rowid = " . ((int) $user->id);
+		$resql = $this->db->query($sql);
+		if (!$resql) {
+			$error++;
+			$this->error = $this->db->lasterror() . ' sql=' . $sql;
+		}
+		if (!$error) {
+			$this->db->commit();
+			return 'OK';
+		} else {
+			$this->db->rollback();
+		}
+	}
+
+	/**
+	 * Receive GPS coordinates of the device
+	 *
+	 * @param string  $licplate	License plate of the vehicle
+	 * @param string  $lat	GPS latitude coordinate
+	 * @param string  $lon	GPS longitude coordinate
+	 *
+	 * @return string|mixed Data without useless information
+	 *
+	 * @url GET /gps_coords
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 * @throws RestException 500
+	 */
+	public function gpsCoords(string $licplate, string $lat, string $lon): string
+	{
+		if (!(new GpsPosition)->save($licplate, $lat, $lon)) {
+			throw new RestException(500, 'Save failure');
+		}
+
+		return 'OK';
+	}
+
+	/**
+	 * Receive GPS coordinates of the device
+	 *
+	 * @param string  $licplate	License plate of the vehicle
+	 * @param string  $lat	GPS latitude coordinate
+	 * @param string  $lon	GPS longitude coordinate
+	 *
+	 * @return string|mixed Data without useless information
+	 *
+	 * @url POST /gps_position
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 * @throws RestException 500
+	 */
+	public function gpsPosition(string $licplate, string $lat, string $lon): string
+	{
+		if (!(new GpsPosition)->save($licplate, $lat, $lon)) {
+			throw new RestException(500, 'Save failure');
+		}
+
+		return 'OK';
+	}
+
+	/**
+	 * List of countries
+	 *
+	 * Return an array with country name and code.
+	 *
+	 * @param string $lang
+	 * @param string $search
+	 * 
+	 * @return array|mixed Data without useless information
+	 *
+	 * @url GET /countries
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 */
+	public function countries(string $lang = 'hu', string $search = ''): array
+	{
+		return (new CountryHandler)->getCountries($lang, $search);
+	}
+
+	/**
+	 * List of entities
+	 *
+	 * Return an array with companies data.
+	 *
+	 * @return array|mixed Data without useless information
+	 *
+	 * @url GET /entities
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 */
+	public function entities(): array
+	{
+		return (new EntityHandler)->getEntities();
+	}
+
+	/**
+	 * List of assets received
+	 *
+	 * Return an array with assets received.
+	 *
+	 * @return array|mixed Data without useless information
+	 *
+	 * @url GET /assets_received
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 */
+	public function assetsReceived(): array
+	{
+		global $user, $db;
+
+		$packageHistory = new PackageHistory($db);
+		$inventoryObj = new Inventory($db);
+		$sql = "SELECT pt.device_id, i.ref, i.title, ie.unique_identifier, 
+		(SELECT ph.date_creation FROM " . $this->db->prefix() . $packageHistory->table_element . " as ph WHERE user_id = {$user->id} AND DATE(ph.date_creation) = CURRENT_DATE
+		ORDER BY ph.rowid DESC LIMIT 1) AS ph_date_creation
+		FROM llx_settlements_packagetool pt
+		INNER JOIN llx_inventory as i ON i.rowid = pt.device_id
+		INNER JOIN llx_inventory_extrafields as ie ON ie.fk_object = i.rowid
+		WHERE pt.package_id = (SELECT ph.package_id FROM public.llx_rollerstorage_packagehistory as ph WHERE user_id = {$user->id}
+		AND DATE(ph.date_creation) = CURRENT_DATE
+		ORDER BY ph.rowid DESC LIMIT 1)
+		ORDER BY pt.rowid DESC";
+		//print $sql;exit;
+		$result = $db->query($sql);
+		$changeSql = "SELECT ref, title FROM " . $this->db->prefix() . $inventoryObj->table_element . " WHERE ref ILIKE '%Change%'";
+		$resultCh = $db->query($changeSql);
+		if ($result > 0 && $resultCh > 0) {
+			while ($row = $db->fetch_object($result)) {
+				$row = (array) $row;
+				$date = $row['ph_date_creation'];
+				$device[] = [
+					"name" => isset($row['title']) ? $row['title'] : '',
+					"sku" => isset($row['unique_identifier']) ? $row['unique_identifier'] : ''
+				];
+			}
+			$change = [];
+			while ($rowCh = $db->fetch_object($resultCh)) {
+				$rowCh = (array) $rowCh;
+				$ChangeArray = explode('_', $rowCh['ref']);
+				$key = isset($ChangeArray[1]) ? strtolower($ChangeArray[1]) : '';
+				if (!empty($key)) {
+					$change[$key] = isset($rowCh['title']) ? $rowCh['title'] : '';
+				}
+			}
+		} else {
+			return [];
+		}
+
+		return [
+			'date' => $date,
+			'devices' => $device,
+			'change' => $change
+		];
+	}
+
+	/**
+	 * Save card payment log
+	 *
+	 * @param string $invoice_ref
+	 * @param string $data
+	 * 
+	 * @return array|mixed Data without useless information
+	 *
+	 * @url POST /cardpaymentlog
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 */
+	public function cardPaymentLog(string $invoice_ref, string $data)
+	{
+		$result = 'OK';
+
+		$facture = new Facture($this->db);
+		if ($facture->fetch(0, $invoice_ref) > 0) {
+			$facture->array_options['options_payment_log'] = $data;
+			$facture->updateExtraField('payment_log');
+
+			$json = json_decode($data, true);
+			if (!empty($json['authorization_number'])) {
+				$sql = "
+					UPDATE " . MAIN_DB_PREFIX . "paiement 
+					SET num_paiement='" . $json['authorization_number'] . "' 
+					WHERE rowid=(
+						SELECT fk_paiement 
+						FROM " . MAIN_DB_PREFIX . "paiement_facture 
+						WHERE fk_facture={$facture->id}
+					)
+				";
+				$this->db->query($sql);
+			}
+		} else {
+			$result = 'Missing invoice';
+		}
+
+		return $result;
+	}
+
+	/**
+	 * Products list with children
+	 *
+	 * @param  string $sortfield  			Sort field
+	 * @param  string $sortorder  			Sort order
+	 * @param  int    $limit      			Limit for list
+	 * @param  int    $page       			Page number
+	 * @param  int    $mode       			Use this param to filter list (0 for all, 1 for only product, 2 for only service)
+	 * @param  int    $category   			Use this param to filter list by category
+	 * @param  string $sqlfilters 			Other criteria to filter answers separated by a comma. Syntax example "(t.tobuy:=:0) and (t.tosell:=:1)"
+	 * @param  bool   $ids_only   			Return only IDs of product instead of all properties (faster, above all if list is long)
+	 * @param  int    $variant_filter   		Use this param to filter list (0 = all, 1=products without variants, 2=parent of variants, 3=variants only)
+	 * @param  bool   $pagination_data   	If this parameter is set to true the response will include pagination data. Default value is false. Page starts from 0
+	 * @param  int    $includestockdata		Load also information about stock (slower)
+	 * @return array                			Array of product objects
+	 *
+	 * @url GET /products
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 */
+	public function products($sortfield = 't.ref', $sortorder = 'ASC', $limit = 100, $page = 0, $mode = 0, $category = 0, $sqlfilters = '', $ids_only = false, $variant_filter = 0, $pagination_data = false, $includestockdata = 0): array
+	{
+		if (!DolibarrApiAccess::$user->rights->produit->lire) {
+			throw new RestException(403);
+		}
+		$productsArray = [];
+		$productsArray = (new ApiProductListHelper)->list($sortfield, $sortorder, $limit, $page, $mode, $category, $sqlfilters, $ids_only, $variant_filter, $pagination_data, $includestockdata);
+		return $productsArray;
+	}
+
+	/**
+	 * Products list with children
+	 *
+	 * @param  string $sortfield  			Sort field
+	 * @param  string $sortorder  			Sort order
+	 * @param  int    $limit      			Limit for list
+	 * @param  int    $page       			Page number
+	 * @param  int    $mode       			Use this param to filter list (0 for all, 1 for only product, 2 for only service)
+	 * @param  int    $category   			Use this param to filter list by category
+	 * @param  string $sqlfilters 			Other criteria to filter answers separated by a comma. Syntax example "(t.tobuy:=:0) and (t.tosell:=:1)"
+	 * @param  bool   $ids_only   			Return only IDs of product instead of all properties (faster, above all if list is long)
+	 * @param  int    $variant_filter   		Use this param to filter list (0 = all, 1=products without variants, 2=parent of variants, 3=variants only)
+	 * @param  bool   $pagination_data   	If this parameter is set to true the response will include pagination data. Default value is false. Page starts from 0
+	 * @param  int    $includestockdata		Load also information about stock (slower)
+	 * @return array                			Array of product objects
+	 *
+	 * @url GET /productshotel
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 */
+	public function productshotel($sortfield = 't.ref', $sortorder = 'ASC', $limit = 100, $page = 0, $mode = 0, $category = 0, $sqlfilters = '', $ids_only = false, $variant_filter = 0, $pagination_data = false, $includestockdata = 0): array
+	{
+		if (!DolibarrApiAccess::$user->rights->produit->lire) {
+			throw new RestException(403);
+		}
+
+		return (new ApiProductListHelper)->listhotel($sortfield, $sortorder, $limit, $page, $mode, $category, $sqlfilters, $ids_only, $variant_filter, $pagination_data, $includestockdata);
+	}
+
+	/**
+	 * Create invoice
+	 * 
+	 * @param  array 	$invoice			Invoice data
+	 * @param  array 	$lines				Invoice lines
+	 * @param  array 	$payment			Invoice payment
+	 * @param  string 	$cardPaymentLog		Card payment log data
+	 * 
+	 * @return array|mixed Data without useless information
+	 * 
+	 * @url POST /invoice
+	 */
+	public function invoice(array $invoice, array $lines, array $payment, string $cardPaymentLog = '', string $sendId = '', $server_host_curl = false)
+	{
+		global $user;
+		$lockLabel = 'CREATE_INVOICE';
+		if (empty($sendId)) {
+			$sendId = substr(md5(rand()), 0, 8);
+		}
+		/**
+		 * LOG SECTION
+		 */
+		ApiBbusLog::appLog("{$sendId} === NEW INVOICE ===");
+		dol_syslog("{$sendId} === NEW INVOICE ===", LOG_INFO, 0);
+		ApiBbusLog::appLog("{$sendId} REQUEST: {$sendId}");
+		dol_syslog("{$sendId} REQUEST: {$sendId}", LOG_INFO, 0);
+		ApiBbusLog::appLog("{$sendId} User: {$user->firstname} {$user->lastname} (ID: {$user->id})");
+		dol_syslog("{$sendId} User: {$user->firstname} {$user->lastname} (ID: {$user->id})", LOG_INFO, 0);
+		ApiBbusLog::appLog("{$sendId} " . json_encode([
+			'invoice' => $invoice,
+			'lines' => $lines,
+			'payment' => $payment,
+			'cardPaymentLog' => $cardPaymentLog
+		]));
+
+		/**
+		 * CHECK RIGHTS
+		 */
+		if (!DolibarrApiAccess::$user->rights->facture->creer) {
+			ApiBbusLog::appLog("{$sendId} Insufficient rights");
+			throw new RestException(401, 'Insufficient rights');
+		}
+
+
+		$apiInvoiceHelper = new ApiInvoiceHelper;
+		/**
+		 * handle invoice basic data
+		 */
+		$invoiceObj = $apiInvoiceHelper->createInvoice($invoice, $sendId);
+		/**
+		 * handle invoice line(s)
+		 */
+		$products = [];
+
+		foreach ($lines as $line) {
+			$invoiceObj = $apiInvoiceHelper->addLineToInvoice($invoiceObj, $line, $sendId);
+
+			$product = $apiInvoiceHelper->loadProductToResult($line);
+			if (!empty($product)) {
+				$products[] = $product;
+			}
+		}
+
+		$invoiceObj->fetch_lines();
+
+		foreach ($products as &$row) {
+			foreach ($invoiceObj->lines as $line) {
+				if ($line->product_ref == $row['ref']) {
+					//$row['price'] = $line->total_ttc;
+					$row['price'] = $line->multicurrency_total_ttc;
+					$row['total_tva'] = $line->total_tva;
+					//$row['total_tva'] = $line->multicurrency_total_tva;
+				}
+			}
+		}
+
+
+
+		/**
+		 * validate
+		 */
+		$invoiceObj = $apiInvoiceHelper->validateInvoice($invoiceObj, $sendId);
+		/**
+		 * set payment
+		 */
+		$apiInvoiceHelper->setPayment($invoiceObj, $payment, $sendId);
+		/**
+		 * save card payment data
+		 */
+		if (!empty($cardPaymentLog)) {
+			$invoiceObj = $apiInvoiceHelper->saveCardPaymentLog($invoiceObj, $cardPaymentLog, $sendId);
+		}
+
+		ApiBbusLog::appLog("{$sendId} Invoice created. ID: {$invoiceObj->id} REF: {$invoiceObj->ref} REQ: {$sendId}");
+		dol_syslog("{$sendId} Invoice created. ID: {$invoiceObj->id} REF: {$invoiceObj->ref} REQ: {$sendId}", LOG_INFO, 0);
+		//$bbApiLock->delete($user);
+
+		ApiBbusLog::appLog("{$sendId}####################################################################");
+		dol_syslog("{$sendId}####################################################################", LOG_INFO, 0);
+
+		#-------------- UPDATE BTICKET --------------
+		ApiBbusLog::appLog("{$sendId}: Update bbticket with curl");
+		$invoiceForCurl['id'] = $invoiceObj->id;
+		$invoiceForCurl['ref'] = $invoiceObj->ref;
+		//$array['fk_booking_history'] = null;
+		$array['invoice'] = $invoiceForCurl;
+		$updateBBticketPostFields = json_encode($array);
+		ApiBbusLog::appLog("{$updateBBticketPostFields}");
+		$this->curlRunner('bookingapi/curlUpdateBbticket', $updateBBticketPostFields, 'POST', true);
+
+		return [
+			'sendId' => $sendId,
+			'invoice' => [
+				'id' => $invoiceObj->id,
+				'ref' => $invoiceObj->ref,
+				'total' => $invoiceObj->multicurrency_total_ttc
+			],
+			'products' => $products,
+		];
+	}
+
+	/**
+	 * Get the saled factures data by user
+	 * 
+	 * @param  string 	$date			Date
+	 * 
+	 * @return array|mixed Data without useless information
+	 * 
+	 * @url POST /saledfacturesbyuser
+	 */
+	function saledfacturesbyuser(string $date)
+	{
+		global $user, $db;
+		$date = date("Y-m-d", dol_now());
+		$fulldate = date("Y-m-d H:i:s", dol_now());
+		$apiBbusHelper = new ApiBBusHelper();
+		$from = $apiBbusHelper->getGroupLoginDate($date);
+		$to = $apiBbusHelper->getGroupLogout($date, $from);
+		$array = [];
+		if (strtotime($fulldate) > strtotime($to)) {
+			return $array;
+		}
+		$sql = "SELECT 
+		f.rowid as facture_rowid, 
+		f.ref AS facture_ref, 
+		f.datec, 
+		f.multicurrency_code, 
+		pa.libelle, 
+		pai.note AS transaction_id, 
+		fdet.fk_product AS product_id,  
+		pr.label AS product_label, 
+		pr.ref AS product_ref, 
+		pr.description, 
+		pass.fk_product_pere AS bundle_id, 
+		pr2.label AS bundle_label, 
+		pr2.ref AS bundle_ref, 
+		fdet.multicurrency_total_tva AS base_tva, 
+		fdet.multicurrency_total_ttc AS base_ttc, 
+		fdete.discount_percent, 
+		fdete.discount_hours, 
+		pr.default_vat_code, 
+		f.multicurrency_total_ttc AS bundle_ttc, 
+		(SELECT rate FROM llx_multicurrency_rate AS mc WHERE mc.fk_multicurrency = f.fk_multicurrency AND mc.entity = {$user->entity} ORDER BY mc.rowid DESC LIMIT 1) AS rate,
+		(SELECT date_start FROM llx_eventwizard_eventhistory AS evev WHERE evev.fk_facture = f.rowid) AS date_start
+				FROM llx_facture AS f
+				INNER JOIN llx_facture_extrafields AS fe ON fe.fk_object = f.rowid
+				INNER JOIN llx_c_paiement AS pa ON pa.id = f.fk_mode_reglement
+				INNER JOIN llx_paiement_facture AS pafa ON pafa.fk_facture = f.rowid
+				INNER JOIN llx_paiement AS pai ON pai.rowid = pafa.fk_paiement
+				INNER JOIN llx_facturedet AS fdet ON fdet.fk_facture = f.rowid
+				INNER JOIN llx_product AS pr ON pr.rowid = fdet.fk_product
+				left JOIN llx_product_association AS pass ON pass.fk_product_fils = pr.rowid
+				LEFT JOIN llx_product AS pr2 ON pr2.rowid = pass.fk_product_pere
+				LEFT JOIN llx_facturedet_extrafields AS fdete ON fdet.rowid = fdete.fk_object
+				WHERE f.datec BETWEEN '{$from}' AND '{$to}' 
+				AND f.fk_user_closing = $user->id
+				ORDER BY f.rowid DESC";
+		//print $sql;exit;
+		$data = $db->query($sql);
+		if ($db->num_rows($data > 0)) {
+
+			while ($row = $db->fetch_object($data)) {
+				$tmpArray = (array) $row;
+				if ($tmpArray['transaction_id'] != '') {
+					$jsonArray = json_decode($tmpArray['transaction_id']);
+					$row->transaction_id = $jsonArray->card_transaction_id;
+				}
+				$row->bundle_id = $row->bundle_id == '' ? $row->product_id : $row->bundle_id;
+				$row->bundle_label = $row->bundle_label == '' ? $row->product_label : $row->bundle_label;
+				$row->bundle_ref = $row->bundle_ref == '' ? $row->product_ref : $row->bundle_ref;
+				$array[$row->facture_rowid]['ref'] = $row->facture_ref;
+				$array[$row->facture_rowid]['bundle_name'] = $row->bundle_label;
+				$array[$row->facture_rowid]['currency'] = $row->multicurrency_code;
+				$array[$row->facture_rowid]['total_price'] = $row->bundle_ttc;
+				$array[$row->facture_rowid]['type'] = $row->libelle;
+				$array[$row->facture_rowid]['transaction_id'] = $row->transaction_id;
+				$array[$row->facture_rowid]['rate'] = $row->rate;
+				$array[$row->facture_rowid]['date'] = $row->datec;
+				$array[$row->facture_rowid]['lines'][$row->product_id] = (array) $row;
+			}
+			return $array;
+		} else {
+			return $array;
+		}
+	}
+
+	/**
+	 * List of assets received
+	 *
+	 * Return an array with assets received.
+	 *
+	 * @return array|mixed Data without useless information
+	 *
+	 * @url GET /getAllUsersOfTheGroup
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 */
+	public function getAllUsersOfTheGroup(): array
+	{
+		global $user, $db;
+		$array = [];
+		$sql = "SELECT u.rowid, u.firstname, u.lastname, u.login, ue.nickname, u.email, u.office_phone, u.user_mobile, u.personal_mobile   FROM llx_settlements_groupusers AS gu
+		INNER JOIN llx_user_extrafields as ue ON ue.fk_object = gu.fk_user
+		INNER JOIN llx_user as u ON u.rowid = gu.fk_user
+		WHERE gu.fk_settlements_group = (SELECT gu2.fk_settlements_group FROM llx_settlements_groupusers as gu2 WHERE gu2.fk_user = {$user->id})";
+		$data = $db->query($sql);
+		while ($row = $db->fetch_object($data)) {
+			$array[] = [
+				'id' => $row->rowid,
+				'name' => $row->firstname . ' ' . $row->lastname,
+				'login' => $row->login,
+				'email' => $row->email,
+				'nickname' => $row->nickname ? $row->nickname : '',
+				'office_phone' => $row->office_phone,
+				'user_mobile' => $row->user_mobile,
+				'personal_mobile' => $row->personal_mobile
+			];
+		}
+		return $array;
+	}
+
+	/**
+	 * Generate events	 
+	 *
+	 * @return array|mixed Data without useless information
+	 *
+	 * @url GET /genEvents
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 */
+	public function genEvents()
+	{
+		$period = new DatePeriod(
+			new DateTime('2024-05-09'),
+			new DateInterval('P1D'),
+			new DateTime('2024-09-01')
+		);
+
+		$size = 30;
+		$buffer = 5;
+
+		$dates = [];
+
+		foreach ($period as $date) {
+			if (rand(0, 1)) {
+				$eventDate = $date->format('Y-m-d');
+				$eventTimes = array_rand(
+					array_flip([
+						'16:00',
+						'18:00',
+						'20:00',
+						'22:00'
+					]),
+					rand(1, 4)
+				);
+				/* 				print $eventDate;
+																				print "\r\n";
+																				print_r($eventTimes); */
+				if (is_array($eventTimes)) {
+					foreach ($eventTimes as $eventTime) {
+						$dates[$eventDate][] = [
+							'start' => $eventTime,
+							'duration' => '02:00:00',
+						];
+					}
+				}
+			}
+		}
+
+		$dates = ['dates' => $dates];
+
+
+
+		dol_include_once('/eventwizard/class/eventdetails.class.php');
+
+		$object = new EventDetails($this->db);
+		$object->fetch(1);
+		$object->genEvents($dates);
+
+		return [];
+	}
+
+	/**
+	 * Generate events	 
+	 *
+	 * @return array|mixed Data without useless information
+	 *
+	 * @url GET /updateeventextrafields
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 */
+	public function updateEventsExtrafields()
+	{
+		$sql = "SELECT ac.id FROM llx_actioncomm as ac 
+		LEFT JOIN llx_actioncomm_extrafields as ace ON ace.fk_object = ac.id
+		WHERE ac.code = 'AC_EVENT'
+		AND ace.fk_object IS NULL";
+		$result1 = $this->db->query($sql);
+		while ($row1 = $this->db->fetch_object($result1)) {
+			$sql2 = "INSERT INTO llx_actioncomm_extrafields (fk_object, max_num, buffer)
+			VALUES ({$row1->id}, 30, 5)";
+			$result = $this->db->query($sql2);
+		}
+	}
+
+	/**
+	 * Generate factures	 
+	 * 
+	 * @return array|mixed Data without useless information
+	 *
+	 * @url POST /generateFactures
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 */
+	public function generateFactures()
+	{
+		$numberOfFactures = 1;
+
+		for ($i = 0; $i < $numberOfFactures; $i++) {
+			$currencyRand = rand(0, 1);
+			$currencyText = [0 => 'HUF', 1 => 'EUR'];
+			$currencyNumber = [0 => 3, 1 => 5];
+
+			$reglementRand = rand(14, 15);
+			//$reglementRand = rand(106, 107); ## STAGING
+			$mode_reglement_id = $reglementRand;
+			$paymentid = $reglementRand;
+			$accountid = [0 => 4, 1 => 5];
+
+			$deduction = rand(1, 5) == 3 ? 1 : null;
+			$storno = rand(1, 5) == 1 ? 1 : null;
+
+			$invoice = ["mode_reglement_id" => $mode_reglement_id, "array_options_app_facture" => 1, "array_options_commission_deduction" => $deduction, "array_options_marked_for_storno" => $storno, "multicurrency_code" => $currencyText[$currencyRand], "fk_multicurrency" => $currencyNumber[$currencyRand], "array_options_customer_data_zip" => null];
+			$linesHUFBundle = [
+				["desc" => "", "subprice" => "787.4015", "qty" => 1, "tva_tx" => "27.0", "localtax1_tx" => "0.000", "localtax2_tx" => "0.000", "fk_product" => "102", "remise_percent" => "0", "date_start" => "", "date_end" => "", "fk_code_ventilation" => 0, "info_bits" => "0", "fk_remise_except" => null, "product_type" => "1", "rang" => "-1", "special_code" => "0", "fk_parent_line" => null, "fk_fournprice" => null, "pa_ht" => "0.00000000", "label" => "", "array_options" => [], "situation_percent" => "100", "fk_prev_id" => null, "fk_unit" => null, "price_base_type" => "HT"],
+				["desc" => "", "subprice" => "13333.3333", "qty" => 1, "tva_tx" => "5.0", "localtax1_tx" => "0.000", "localtax2_tx" => "0.000", "fk_product" => "108", "remise_percent" => "0", "date_start" => "", "date_end" => "", "fk_code_ventilation" => 0, "info_bits" => "0", "fk_remise_except" => null, "product_type" => "1", "rang" => "-1", "special_code" => "0", "fk_parent_line" => null, "fk_fournprice" => null, "pa_ht" => "0.00000000", "label" => "", "array_options" => [], "situation_percent" => "100", "fk_prev_id" => null, "fk_unit" => null, "price_base_type" => "HT"]
+			];
+
+			$linesEURBundle = [
+				["desc" => "", "subprice" => "2.3622", "qty" => 1, "tva_tx" => "27.0", "localtax1_tx" => "0.000", "localtax2_tx" => "0.000", "fk_product" => "101", "remise_percent" => "0", "date_start" => "", "date_end" => "", "fk_code_ventilation" => 0, "info_bits" => "0", "fk_remise_except" => null, "product_type" => "1", "rang" => "-1", "special_code" => "0", "fk_parent_line" => null, "fk_fournprice" => null, "pa_ht" => "0.00000000", "label" => "", "array_options" => [], "situation_percent" => "100", "fk_prev_id" => null, "fk_unit" => null, "price_base_type" => "HT"],
+				["desc" => "", "subprice" => "33.3333", "qty" => 1, "tva_tx" => "5.0", "localtax1_tx" => "0.000", "localtax2_tx" => "0.000", "fk_product" => "107", "remise_percent" => "0", "date_start" => "", "date_end" => "", "fk_code_ventilation" => 0, "info_bits" => "0", "fk_remise_except" => null, "product_type" => "1", "rang" => "-1", "special_code" => "0", "fk_parent_line" => null, "fk_fournprice" => null, "pa_ht" => "0.00000000", "label" => "", "array_options" => [], "situation_percent" => "100", "fk_prev_id" => null, "fk_unit" => null, "price_base_type" => "HT"]
+			];
+
+			$linesEURSimple = [
+				["desc" => "", "subprice" => "12.5984", "qty" => 1, "tva_tx" => "27.0", "localtax1_tx" => "0.000", "localtax2_tx" => "0.000", "fk_product" => "132", "remise_percent" => "0", "date_start" => "", "date_end" => "", "fk_code_ventilation" => 0, "info_bits" => "0", "fk_remise_except" => null, "product_type" => "1", "rang" => "-1", "special_code" => "0", "fk_parent_line" => null, "fk_fournprice" => null, "pa_ht" => "0.00000000", "label" => "", "array_options" => [], "situation_percent" => "100", "fk_prev_id" => null, "fk_unit" => null, "price_base_type" => "HT"],
+			];
+
+			$linesArray = [
+				0 => $linesEURBundle,
+				1 => $linesEURSimple,
+				2 => $linesHUFBundle
+			];
+			if ($currencyRand == 1) {
+				$lineRand = rand(0, 1);
+				$lines = $linesArray[$lineRand];
+			} else {
+				$lines = $linesArray[2];
+			}
+
+			$payment = ["datepaye" => time(), "paymentid" => $paymentid, "closepaidinvoices" => "yes", "accountid" => $accountid[$currencyRand]];
+			$comment = "{\"timestamp\":\"1704241200\",\"merchant_id\":\"000000000014202\",\"acquirer_id\":\"88105000003\",\"operation_number\":\"000037\",\"termid\":\"36002058\",\"pan\":\"428312******1789\",\"exp\":\"****\",\"stan\":\"000007\",\"authorization_number\":\"282597\",\"trans_type\":\"CLI\",\"amount\":\"2.0\",\"cvm\":\"2\",\"billnumber\":\"AcTbHmcWsMGcF6Nf\",\"card_transaction_id\":\"AcTbHmcWsMGcF6Nf\"}";
+			$payment['comment'] = $reglementRand == 15 ? '' : $comment;
+			//$payment['comment'] = $reglementRand == 107 ? '' : $comment;  ## STAGING
+
+			$cardPaymentLog = "";
+
+			/*  			print_r($invoice);
+								 print_r($lines);
+								 print_r($payment);
+								 exit; */
+
+			$invoiceNumber = $this->invoice($invoice, $lines, $payment, $cardPaymentLog, $sendId = '');
+			$ref = $invoiceNumber['invoice']['ref'];
+			$datetime = date("Y-m-d H:i:s", $payment['datepaye']);
+			$multiprint = rand(1, 5);
+			$this->dateHandler($ref, $datetime);
+			if ($multiprint == 2 || $multiprint == 3) {
+				$prints = rand(1, 5);
+				$dateofprint = $payment['datepaye'];
+				for ($i = 0; $i < $prints; $i++) {
+					$dateofprint = strtotime('+5 seconds', $dateofprint);
+					$datetime = date("Y-m-d H:i:s", $dateofprint);
+					$this->dateHandler($ref, $datetime);
+				}
+			}
+		}
+		return 'OK';
+	}
+
+	/**
+	 * Generate events	 
+	 *
+	 * @return array|mixed Data without useless information
+	 *
+	 * @url GET /getDatetime
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 403
+	 * @throws RestException 404
+	 */
+	public function getDatetime()
+	{
+		$dateArray = [];
+		$now = dol_now();
+		$dateArray = ["timestamp" => $now, "date" => date("Y-m-d", $now), "datetime" => date("Y-m-d H:i:s", $now)];
+		return $dateArray;
+	}
+
+	/**
+	 * Get the saled factures data by user
+	 * 
+	 * @param  string 	$ref			Date
+	 * 
+	 * @return array|mixed Data without useless information
+	 * 
+	 * @url POST /curlgetdatecfromfacture
+	 */
+	public function curlgetdatecfromfacture(string $ref)
+	{
+		$sql = "SELECT datec FROM llx_facture WHERE ref = '{$ref}'";
+		$result = $this->db->query($sql);
+		if ($this->db->num_rows($result) < 1) {
+			return [];
+		} else {
+			while ($row = $this->db->fetch_object($result)) {
+				return $row->datec;
+			}
+		}
+	}
+
+	/**
+	 * Get the saled factures data by user
+	 * 
+	 * @param  string 	$ref			Date
+	 * 
+	 * @return array|mixed Data without useless information
+	 * 
+	 * @url POST /curlgetproductidfromfacturedet
+	 */
+	public function curlgetproductidfromfacturedet(string $ref)
+	{
+		ApiBbusLog::appLog("curlgetproductidfromfacturedet");
+		$helper = new ApiBBusHelper();
+		$facture_id = $helper->getFactureIdForInvoicePrinting($ref);
+		global $db;
+		$array = [];
+		$sql = "SELECT fk_product FROM public.llx_facturedet WHERE fk_facture = " . $facture_id;
+		$result = $db->query($sql);
+		while ($facturedetrecord = pg_fetch_assoc($result)) {
+			$array[] = $facturedetrecord['fk_product'];
+		}
+		return $array;
+	}
+}

+ 1 - 0
custom/bbus/class/api_curl.class.php

@@ -71,6 +71,7 @@ trait CurlApi
 	public function curlCreateBBticket($product_id, $datec, $facture_id)
 	{
 		$postFields = '{"product_id":"' . $product_id . '","datec":"' . $datec . '","facture_id":"' . $facture_id . '"}';
+		ApiBbusLog::appLog("{$postFields}");
 		$curl = $this->curlInit('bookingapi/createbbticket', $postFields, 'POST');
 		$response = curl_exec($curl);
 		curl_close($curl);

+ 98 - 0
custom/bbus/class/api_curl.class.php.bak

@@ -0,0 +1,98 @@
+<?php
+
+use Luracast\Restler\RestException;
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/basicservices.class.php';
+
+//require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/api_bbus_log.class.php';
+
+trait CurlApi
+{
+
+	private function curlInit($route, $postFields = '', $request = 'POST')
+	{
+		global $conf;
+		$curl = curl_init();
+
+		curl_setopt_array($curl, array(
+			//CURLOPT_URL => 'http://dolibarr-bbusdev-imap-cron-soap/api/index.php/' . $route,
+			CURLOPT_URL => $conf->global->CURL_ROUTE . $route,
+			CURLOPT_RETURNTRANSFER => true,
+			CURLOPT_VERBOSE => true,
+			CURLOPT_SSL_VERIFYPEER => false,
+			CURLOPT_REFERER => true,
+			CURLOPT_ENCODING => '',
+			CURLOPT_MAXREDIRS => 10,
+			CURLOPT_TIMEOUT => 0,
+			CURLOPT_FOLLOWLOCATION => true,
+			CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
+			CURLOPT_CUSTOMREQUEST => $request,
+			CURLOPT_USERAGENT => "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
+			CURLOPT_POSTFIELDS => $postFields,
+			CURLOPT_HTTPHEADER => array(
+				'DOLAPIKEY: ' . $conf->global->CURL_USER_DOLAPIKEY,
+				'Content-Type: application/json'
+			),
+		));
+		return $curl;
+	}
+
+	private function curlRunner($route, $postFields, $request = 'POST', $decode = false)
+	{
+		ApiBbusLog::appLog("{$route}");
+		$curl = $this->curlInit($route, $postFields, $request);
+		$response = curl_exec($curl);
+		if($route != 'bookingapi/localavailableplaces'){
+			ApiBbusLog::appLog("{$response}");
+		}
+		curl_close($curl);
+		return $decode ? json_decode($response) : $response;
+	}
+
+	public function addCurlProducts($sortfield, $sortorder, $limit, $page, $mode, $category, $sqlfilters, $ids_only, $variant_filter, $pagination_data, $includestockdata)
+	{
+		$curlProductsArray = [];
+		$searchString = "ef.basic_service =";
+		$array = explode('AND', $sqlfilters);
+		foreach ($array as $key => $value) {
+			if (strpos($value, $searchString) !== false) {
+				$tmpArray = explode(" = ", $array[$key]);
+				$basic_service = $tmpArray[1];
+			}
+		}
+
+		if ($basic_service == "'3'") {
+			$params = compact('sortfield', 'sortorder', 'limit', 'page', 'mode', 'category', 'sqlfilters', 'ids_only', 'variant_filter', 'pagination_data', 'includestockdata');
+			$postFields = json_encode($params);
+			$curlProductsArray = $this->curlRunner('bbus/products', $postFields, 'GET', true);
+		}
+		return $curlProductsArray;
+	}
+
+	public function curlCreateBBticket($product_id, $datec, $facture_id)
+	{
+		$postFields = '{"product_id":"' . $product_id . '","datec":"' . $datec . '","facture_id":"' . $facture_id . '"}';
+		$curl = $this->curlInit('bookingapi/createbbticket', $postFields, 'POST');
+		$response = curl_exec($curl);
+		curl_close($curl);
+		return true;
+	}
+
+	public function curlCreateBBticketForMultipriniting($product_id, $datec, $ref)
+	{
+		$postFields = '{"product_id":"' . $product_id . '","datec":"' . $datec . '","ref":"' . $ref . '"}';
+		$curl = $this->curlInit('bookingapi/createbbticketmultiprinting', $postFields, 'POST');
+		$response = curl_exec($curl);
+		//print_r($response);exit;
+		curl_close($curl);
+		return $response;
+	}
+
+
+
+	public function getServerHost($type_id)
+	{
+		$basicServices = new BasicServices($this->db);
+		$resultBS = $basicServices->fetch($type_id);
+		return $basicServices->server_host;
+	}
+}

+ 24 - 20
custom/bbus/class/apiproductlisthelper.class.php

@@ -3,6 +3,8 @@ use Luracast\Restler\RestException;
 
 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
 require_once DOL_DOCUMENT_ROOT.'/api/class/api.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/bbus/class/api_curl.class.php';
+require_once DOL_DOCUMENT_ROOT . '/custom/booking/class/api_booking.class.php';
 
 class ApiProductListProduct extends Product
 {
@@ -11,6 +13,8 @@ class ApiProductListProduct extends Product
 
 class ApiProductListHelper
 {    
+	use CurlApi;
+    
     public $db;
     /**
      * 
@@ -150,32 +154,32 @@ class ApiProductListHelper
     }
 
 	private function checkAvailableSpaces($product_id){
-		if($this->isEvent($product_id)){
-			$sql = "SELECT 
-			ac.id, 
-			ac.datep, 
-			ace.max_num, 
-			ace.participants,
-			ace.buffer
-			FROM llx_actioncomm as ac 
-			LEFT JOIN llx_actioncomm_extrafields as ace ON ace.fk_object = ac.id
-			INNER JOIN llx_eventwizard_eventdetails as ed ON ac.fk_element = ed.rowid
-			INNER JOIN llx_eventwizard_eventproduct as ep ON ep.fk_eventdetails = ac.fk_element
-			WHERE ac.code = 'AC_EVENT'
-			AND ep.fk_product = {$product_id}
-			AND ac.datep > NOW()
-			ORDER BY ac.datep ASC";
-			$result = $this->db->query($sql);
-			return $this->db->num_rows($result) > 0;
+		global $conf;
+		$bookingApi = new BookingApi($this->db);
+		$eventData = $this->geteventData($product_id);
+		if($eventData->is_event){
+			
+			if($eventData->server_host == $conf->global->LOCAL_SERVER_HOST){
+				$result = $bookingApi->localCheckAvailablePlaces($product_id);
+				return $result;
+			} else {
+				$postFields = '{"product_id":"' . $product_id . '"}';
+				$result = $this->curlRunner('bookingapi/localCheckAvailablePlaces', $postFields, 'POST', true);
+				return $result;
+			}
 		}else{
 			return true;
 		}
 	}
 
-	private function isEvent($product_id){
-		$sql = "SELECT * FROM llx_eventwizard_eventproduct WHERE fk_product = {$product_id}";
+	private function geteventData($product_id){
+		$sql = "SELECT bs.is_event, server_host FROM llx_product_extrafields AS pre INNER JOIN llx_bbus_basicservices as bs ON bs.rowid = CAST(pre.basic_service AS integer) WHERE pre.fk_object = {$product_id}";
 		$result = $this->db->query($sql);
-		return $this->db->num_rows($result) > 0;
+		if($this->db->num_rows($result) > 0){
+			while($row = $this->db->fetch_object($result)){
+				return $row;
+			}
+		}
 	}
 
 	/**

+ 528 - 0
custom/bbus/class/apiproductlisthelper.class.php.bak

@@ -0,0 +1,528 @@
+<?php
+use Luracast\Restler\RestException;
+
+require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
+require_once DOL_DOCUMENT_ROOT.'/api/class/api.class.php';
+
+class ApiProductListProduct extends Product
+{
+    public $sub_products;
+}
+
+class ApiProductListHelper
+{    
+    public $db;
+    /**
+     * 
+     */
+    public function __construct()
+    {
+        global $db;
+
+        $this->db = $db;
+    }
+    /**
+     * 
+     */
+    public function list($sortfield = "t.ref", $sortorder = 'ASC', $limit = 100, $page = 0, $mode = 0, $category = 0, $sqlfilters = '', $ids_only = false, $variant_filter = 0, $pagination_data = false, $includestockdata = 0): array
+    {
+        $obj_ret = array();
+
+		//$socid = DolibarrApiAccess::$user->socid ? DolibarrApiAccess::$user->socid : '';
+
+		$sql = "SELECT t.rowid, t.ref, t.ref_ext";
+		$sql .= " FROM ".$this->db->prefix()."product as t";
+		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product_extrafields AS ef ON ef.fk_object = t.rowid";	// So we will be able to filter on extrafields
+		if ($category > 0) {
+			$sql .= ", ".$this->db->prefix()."categorie_product as c";
+		}
+		$sql .= ' WHERE t.entity IN ('.getEntity('product').')';
+		$sql .= ' AND ef.hotelsales IS NULL';
+
+		if ($variant_filter == 1) {
+			$sql .= ' AND t.rowid not in (select distinct fk_product_parent from '.$this->db->prefix().'product_attribute_combination)';
+			$sql .= ' AND t.rowid not in (select distinct fk_product_child from '.$this->db->prefix().'product_attribute_combination)';
+		}
+		if ($variant_filter == 2) {
+			$sql .= ' AND t.rowid in (select distinct fk_product_parent from '.$this->db->prefix().'product_attribute_combination)';
+		}
+		if ($variant_filter == 3) {
+			$sql .= ' AND t.rowid in (select distinct fk_product_child from '.$this->db->prefix().'product_attribute_combination)';
+		}
+
+		// Select products of given category
+		if ($category > 0) {
+			$sql .= " AND c.fk_categorie = ".((int) $category);
+			$sql .= " AND c.fk_product = t.rowid";
+		}
+		if ($mode == 1) {
+			// Show only products
+			$sql .= " AND t.fk_product_type = 0";
+		} elseif ($mode == 2) {
+			// Show only services
+			$sql .= " AND t.fk_product_type = 1";
+		}
+
+		// Add sql filters
+		if ($sqlfilters) {
+			$errormessage = '';
+			if (!$this->_checkFilters($sqlfilters, $errormessage)) {
+				throw new RestException(503, 'Error when validating parameter sqlfilters -> '.$errormessage);
+			}
+			$regexstring = '\(([^:\'\(\)]+:[^:\'\(\)]+:[^\(\)]+)\)';	// We must accept datc:<:2020-01-01 10:10:10
+			$sql .= " AND (".preg_replace_callback('/'.$regexstring.'/', 'ApiProductListHelper::_forge_criteria_callback', $sqlfilters).")";
+		}
+
+		//print $sql;exit;
+
+		//this query will return total products with the filters given
+		$sqlTotals =  str_replace('SELECT t.rowid, t.ref, t.ref_ext', 'SELECT count(t.rowid) as total', $sql);
+
+		$sql .= $this->db->order($sortfield, $sortorder);
+		if ($limit) {
+			if ($page < 0) {
+				$page = 0;
+			}
+			$offset = $limit * $page;
+
+			$sql .= $this->db->plimit($limit + 1, $offset);
+		}
+		$result = $this->db->query($sql);
+		if ($result) {
+			$num = $this->db->num_rows($result);
+			$min = min($num, ($limit <= 0 ? $num : $limit));
+			$i = 0;
+			while ($i < $min) {
+				$obj = $this->db->fetch_object($result);
+				if (!$ids_only) {
+					$product_static = new ApiProductListProduct($this->db);
+					if ($product_static->fetch($obj->rowid)) {
+						if ($includestockdata && DolibarrApiAccess::$user->rights->stock->lire) {
+							$product_static->load_stock();
+
+							if (is_array($product_static->stock_warehouse)) {
+								foreach ($product_static->stock_warehouse as $keytmp => $valtmp) {
+									if (isset($product_static->stock_warehouse[$keytmp]->detail_batch) && is_array($product_static->stock_warehouse[$keytmp]->detail_batch)) {
+										foreach ($product_static->stock_warehouse[$keytmp]->detail_batch as $keytmp2 => $valtmp2) {
+											unset($product_static->stock_warehouse[$keytmp]->detail_batch[$keytmp2]->db);
+										}
+									}
+								}
+							}
+						}
+
+                        // get child details
+                        $product_static->sub_products = $this->_getSubproducts($product_static);
+						if($this->checkAvailableSpaces($obj->rowid)){
+							$obj_ret[] = $this->_cleanObjectDatas($product_static);
+						}
+					}
+				} else {
+					$obj_ret[] = $obj->rowid;
+				}
+				$i++;
+			}
+		} else {
+			throw new RestException(503, 'Error when retrieve product list : '.$this->db->lasterror());
+		}
+		if (!count($obj_ret)) {
+			throw new RestException(404, 'No product found');
+		}
+
+		//if $pagination_data is true the response will contain element data with all values and element pagination with pagination data(total,page,limit)
+		if ($pagination_data) {
+			$totalsResult = $this->db->query($sqlTotals);
+			$total = $this->db->fetch_object($totalsResult)->total;
+
+			$tmp = $obj_ret;
+			$obj_ret = array();
+
+			$obj_ret['data'] = $tmp;
+			$obj_ret['pagination'] = array(
+				'total' => (int) $total,
+				'page' => $page, //count starts from 0
+				'page_count' => ceil((int) $total/$limit),
+				'limit' => $limit
+			);
+		}
+
+        return $obj_ret;
+    }
+
+	private function checkAvailableSpaces($product_id){
+		if($this->isEvent($product_id)){
+			$sql = "SELECT 
+			ac.id, 
+			ac.datep, 
+			ace.max_num, 
+			ace.participants,
+			ace.buffer
+			FROM llx_actioncomm as ac 
+			LEFT JOIN llx_actioncomm_extrafields as ace ON ace.fk_object = ac.id
+			INNER JOIN llx_eventwizard_eventdetails as ed ON ac.fk_element = ed.rowid
+			INNER JOIN llx_eventwizard_eventproduct as ep ON ep.fk_eventdetails = ac.fk_element
+			WHERE ac.code = 'AC_EVENT'
+			AND ep.fk_product = {$product_id}
+			AND ac.datep > NOW()
+			ORDER BY ac.datep ASC";
+			$result = $this->db->query($sql);
+			return $this->db->num_rows($result) > 0;
+		}else{
+			return true;
+		}
+	}
+
+	private function isEvent($product_id){
+		$sql = "SELECT * FROM llx_eventwizard_eventproduct WHERE fk_product = {$product_id}";
+		$result = $this->db->query($sql);
+		return $this->db->num_rows($result) > 0;
+	}
+
+	/**
+     * 
+     */
+    public function listhotel($sortfield = "t.ref", $sortorder = 'ASC', $limit = 100, $page = 0, $mode = 0, $category = 0, $sqlfilters = '', $ids_only = false, $variant_filter = 0, $pagination_data = false, $includestockdata = 0): array
+    {
+        $obj_ret = array();
+
+		//$socid = DolibarrApiAccess::$user->socid ? DolibarrApiAccess::$user->socid : '';
+
+		$sql = "SELECT t.rowid, t.ref, t.ref_ext";
+		$sql .= " FROM ".$this->db->prefix()."product as t";
+		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product_extrafields AS ef ON ef.fk_object = t.rowid";	// So we will be able to filter on extrafields
+		if ($category > 0) {
+			$sql .= ", ".$this->db->prefix()."categorie_product as c";
+		}
+		$sql .= ' WHERE t.entity IN ('.getEntity('product').')';
+
+		if ($variant_filter == 1) {
+			$sql .= ' AND t.rowid not in (select distinct fk_product_parent from '.$this->db->prefix().'product_attribute_combination)';
+			$sql .= ' AND t.rowid not in (select distinct fk_product_child from '.$this->db->prefix().'product_attribute_combination)';
+		}
+		if ($variant_filter == 2) {
+			$sql .= ' AND t.rowid in (select distinct fk_product_parent from '.$this->db->prefix().'product_attribute_combination)';
+		}
+		if ($variant_filter == 3) {
+			$sql .= ' AND t.rowid in (select distinct fk_product_child from '.$this->db->prefix().'product_attribute_combination)';
+		}
+
+		// Select products of given category
+		if ($category > 0) {
+			$sql .= " AND c.fk_categorie = ".((int) $category);
+			$sql .= " AND c.fk_product = t.rowid";
+		}
+		if ($mode == 1) {
+			// Show only products
+			$sql .= " AND t.fk_product_type = 0";
+		} elseif ($mode == 2) {
+			// Show only services
+			$sql .= " AND t.fk_product_type = 1";
+		}
+
+		// Add sql filters
+		if ($sqlfilters) {
+			$errormessage = '';
+			if (!$this->_checkFilters($sqlfilters, $errormessage)) {
+				throw new RestException(503, 'Error when validating parameter sqlfilters -> '.$errormessage);
+			}
+			$regexstring = '\(([^:\'\(\)]+:[^:\'\(\)]+:[^\(\)]+)\)';	// We must accept datc:<:2020-01-01 10:10:10
+			$sql .= " AND (".preg_replace_callback('/'.$regexstring.'/', 'ApiProductListHelper::_forge_criteria_callback', $sqlfilters).")";
+		}
+
+		//this query will return total products with the filters given
+		$sqlTotals =  str_replace('SELECT t.rowid, t.ref, t.ref_ext', 'SELECT count(t.rowid) as total', $sql);
+
+		$sql .= $this->db->order($sortfield, $sortorder);
+		if ($limit) {
+			if ($page < 0) {
+				$page = 0;
+			}
+			$offset = $limit * $page;
+
+			$sql .= $this->db->plimit($limit + 1, $offset);
+		}
+
+		//print $sql;exit;
+
+		$result = $this->db->query($sql);
+		if ($result) {
+			$num = $this->db->num_rows($result);
+			$min = min($num, ($limit <= 0 ? $num : $limit));
+			$i = 0;
+			while ($i < $min) {
+				$obj = $this->db->fetch_object($result);
+				if (!$ids_only) {
+					$product_static = new ApiProductListProduct($this->db);
+					if ($product_static->fetch($obj->rowid)) {
+						if ($includestockdata && DolibarrApiAccess::$user->rights->stock->lire) {
+							$product_static->load_stock();
+
+							if (is_array($product_static->stock_warehouse)) {
+								foreach ($product_static->stock_warehouse as $keytmp => $valtmp) {
+									if (isset($product_static->stock_warehouse[$keytmp]->detail_batch) && is_array($product_static->stock_warehouse[$keytmp]->detail_batch)) {
+										foreach ($product_static->stock_warehouse[$keytmp]->detail_batch as $keytmp2 => $valtmp2) {
+											unset($product_static->stock_warehouse[$keytmp]->detail_batch[$keytmp2]->db);
+										}
+									}
+								}
+							}
+						}
+
+                        // get child details
+                        $product_static->sub_products = $this->_getSubproducts($product_static);
+
+						$obj_ret[] = $this->_cleanObjectDatas($product_static);
+					}
+				} else {
+					$obj_ret[] = $obj->rowid;
+				}
+				$i++;
+			}
+		} else {
+			throw new RestException(503, 'Error when retrieve product list : '.$this->db->lasterror());
+		}
+		if (!count($obj_ret)) {
+			throw new RestException(404, 'No product found');
+		}
+
+		//if $pagination_data is true the response will contain element data with all values and element pagination with pagination data(total,page,limit)
+		if ($pagination_data) {
+			$totalsResult = $this->db->query($sqlTotals);
+			$total = $this->db->fetch_object($totalsResult)->total;
+
+			$tmp = $obj_ret;
+			$obj_ret = array();
+
+			$obj_ret['data'] = $tmp;
+			$obj_ret['pagination'] = array(
+				'total' => (int) $total,
+				'page' => $page, //count starts from 0
+				'page_count' => ceil((int) $total/$limit),
+				'limit' => $limit
+			);
+		}
+
+        return $obj_ret;
+    }
+    /**
+     * Return subproducts
+     * 
+     * @param   ApiProductListProduct   $product    Product
+     * 
+     * @return  array
+     */
+    protected function _getSubproducts(ApiProductListProduct $product): array
+    {
+        $children = [];
+
+        $subProducts = $product->getChildsArbo($product->id, 1);
+        $keys = ['rowid', 'qty', 'fk_product_type', 'label', 'incdec', 'ref', 'fk_association', 'rang'];
+		
+		foreach ($subProducts as $values) {
+			$subProductData = array_combine($keys, $values);
+            $subProduct = new Product($this->db);
+            if ($subProduct->fetch($subProductData['rowid']) > 0) {
+                $children[] = $this->_cleanObjectDatas($subProduct);
+            }
+		}
+
+        return $children;
+    }
+
+	/**
+	 * Return if a $sqlfilters parameter is valid
+	 *
+	 * @param  	string   		$sqlfilters     sqlfilter string
+	 * @param	string			$error			Error message
+	 * @return 	boolean|string   				True if valid, False if not valid
+	 */
+    protected function _checkFilters($sqlfilters, &$error = '')
+	{
+		// phpcs:enable
+
+		return dolCheckFilters($sqlfilters, $error);
+	}
+	/**
+	 * Clean sensible object datas
+	 *
+	 * @param   Object  $object	Object to clean
+	 * @return	Object			Object with cleaned properties
+	 */
+	protected function _cleanObjectDatas($object)
+	{
+		// phpcs:enable
+		// Remove $db object property for object
+		unset($object->db);
+		unset($object->isextrafieldmanaged);
+		unset($object->ismultientitymanaged);
+		unset($object->restrictiononfksoc);
+		unset($object->table_rowid);
+		unset($object->pass);
+		unset($object->pass_indatabase);
+
+		// Remove linkedObjects. We should already have linkedObjectsIds that avoid huge responses
+		unset($object->linkedObjects);
+
+		unset($object->fields);
+		unset($object->oldline);
+
+		unset($object->error);
+		unset($object->errors);
+		unset($object->errorhidden);
+
+		unset($object->ref_previous);
+		unset($object->ref_next);
+		unset($object->ref_int);
+		unset($object->imgWidth);
+		unset($object->imgHeight);
+		unset($object->barcode_type_code);
+		unset($object->barcode_type_label);
+
+		unset($object->mode_reglement);		// We use mode_reglement_id now
+		unset($object->cond_reglement);		// We use cond_reglement_id now
+		unset($object->note);				// We use note_public or note_private now
+		unset($object->contact);			// We use contact_id now
+		unset($object->thirdparty);			// We use thirdparty_id or fk_soc or socid now
+
+		unset($object->projet); // Should be fk_project
+		unset($object->project); // Should be fk_project
+		unset($object->author); // Should be fk_user_author
+		unset($object->timespent_old_duration);
+		unset($object->timespent_id);
+		unset($object->timespent_duration);
+		unset($object->timespent_date);
+		unset($object->timespent_datehour);
+		unset($object->timespent_withhour);
+		unset($object->timespent_fk_user);
+		unset($object->timespent_note);
+		unset($object->fk_delivery_address);
+		unset($object->modelpdf);
+		unset($object->sendtoid);
+		unset($object->name_bis);
+		unset($object->newref);
+		unset($object->alreadypaid);
+		unset($object->openid);
+
+		unset($object->statuts);
+		unset($object->statuts_short);
+		unset($object->statuts_logo);
+		unset($object->statuts_long);
+		unset($object->statutshorts);
+		unset($object->statutshort);
+		unset($object->labelStatus);
+		unset($object->labelStatusShort);
+
+		unset($object->stats_propale);
+		unset($object->stats_commande);
+		unset($object->stats_contrat);
+		unset($object->stats_facture);
+		unset($object->stats_commande_fournisseur);
+		unset($object->stats_reception);
+		unset($object->stats_mrptoconsume);
+		unset($object->stats_mrptoproduce);
+
+		unset($object->element);
+		unset($object->element_for_permission);
+		unset($object->fk_element);
+		unset($object->table_element);
+		unset($object->table_element_line);
+		unset($object->class_element_line);
+		unset($object->picto);
+
+		unset($object->fieldsforcombobox);
+		unset($object->regeximgext);
+
+		unset($object->skip_update_total);
+		unset($object->context);
+		unset($object->next_prev_filter);
+
+		unset($object->region);
+		unset($object->region_code);
+		unset($object->country);
+		unset($object->state);
+		unset($object->state_code);
+		unset($object->departement);
+		unset($object->departement_code);
+
+		unset($object->libelle_statut);
+		unset($object->libelle_paiement);
+
+		unset($object->prefix_comm);
+
+		if (!isset($object->table_element) || $object->table_element != 'ticket') {
+			unset($object->comments);
+		}
+
+		// Remove the $oldcopy property because it is not supported by the JSON
+		// encoder. The following error is generated when trying to serialize
+		// it: "Error encoding/decoding JSON: Type is not supported"
+		// Note: Event if this property was correctly handled by the JSON
+		// encoder, it should be ignored because keeping it would let the API
+		// have a very strange behavior: calling PUT and then GET on the same
+		// resource would give different results:
+		// PUT /objects/{id} -> returns object with oldcopy = previous version of the object
+		// GET /objects/{id} -> returns object with oldcopy empty
+		unset($object->oldcopy);
+
+		// If object has lines, remove $db property
+		if (isset($object->lines) && is_array($object->lines) && count($object->lines) > 0) {
+			$nboflines = count($object->lines);
+			for ($i = 0; $i < $nboflines; $i++) {
+				$this->_cleanObjectDatas($object->lines[$i]);
+
+				unset($object->lines[$i]->contact);
+				unset($object->lines[$i]->contact_id);
+				unset($object->lines[$i]->country);
+				unset($object->lines[$i]->country_id);
+				unset($object->lines[$i]->country_code);
+				unset($object->lines[$i]->mode_reglement_id);
+				unset($object->lines[$i]->mode_reglement_code);
+				unset($object->lines[$i]->mode_reglement);
+				unset($object->lines[$i]->cond_reglement_id);
+				unset($object->lines[$i]->cond_reglement_code);
+				unset($object->lines[$i]->cond_reglement);
+				unset($object->lines[$i]->fk_delivery_address);
+				unset($object->lines[$i]->fk_projet);
+				unset($object->lines[$i]->fk_project);
+				unset($object->lines[$i]->thirdparty);
+				unset($object->lines[$i]->user);
+				unset($object->lines[$i]->model_pdf);
+				unset($object->lines[$i]->modelpdf);
+				unset($object->lines[$i]->note_public);
+				unset($object->lines[$i]->note_private);
+				unset($object->lines[$i]->fk_incoterms);
+				unset($object->lines[$i]->label_incoterms);
+				unset($object->lines[$i]->location_incoterms);
+				unset($object->lines[$i]->name);
+				unset($object->lines[$i]->lastname);
+				unset($object->lines[$i]->firstname);
+				unset($object->lines[$i]->civility_id);
+				unset($object->lines[$i]->fk_multicurrency);
+				unset($object->lines[$i]->multicurrency_code);
+				unset($object->lines[$i]->shipping_method_id);
+			}
+		}
+
+		if (!empty($object->thirdparty) && is_object($object->thirdparty)) {
+			$this->_cleanObjectDatas($object->thirdparty);
+		}
+
+		if (!empty($object->product) && is_object($object->product)) {
+			$this->_cleanObjectDatas($object->product);
+		}
+
+		return $object;
+	}    
+	/**
+	 * Function to forge a SQL criteria from a Generic filter string
+	 *
+	 * @param  array    $matches    Array of found string by regex search.
+	 * 								Each entry is 1 and only 1 criteria.
+	 * 								Example: "t.ref:like:'SO-%'", "t.date_creation:<:'20160101'", "t.date_creation:<:'2016-01-01 12:30:00'", "t.nature:is:NULL", "t.field2:isnot:NULL"
+	 * @return string               Forged criteria. Example: "t.field like 'abc%'"
+	 */
+	protected static function _forge_criteria_callback($matches)
+	{
+		return dolForgeCriteriaCallback($matches);
+	}    
+}

+ 32 - 0
custom/booking/class/api_booking.class.php

@@ -1250,6 +1250,38 @@ class BookingApi extends DolibarrApi
 		return $array;
 	}
 
+	/**
+	 * localCheckAvailablePlaces
+	 *	 *
+	 * @return 	array|mixed data without useless information
+	 *
+	 * @param 	int 	product_id
+	 * 
+	 * @url	POST localCheckAvailablePlaces
+	 *
+	 * @throws RestException 401 Not allowed
+	 * @throws RestException 404 Not found
+	 */
+	public function localCheckAvailablePlaces($product_id){
+		$date_from = $this->getCutOffTimeDate($product_id);
+		$sql = "SELECT 
+		ac.id, 
+		ac.datep, 
+		ace.max_num, 
+		ace.participants,
+		ace.buffer
+		FROM llx_actioncomm as ac 
+		LEFT JOIN llx_actioncomm_extrafields as ace ON ace.fk_object = ac.id
+		INNER JOIN llx_eventwizard_eventdetails as ed ON ac.fk_element = ed.rowid
+		INNER JOIN llx_eventwizard_eventproduct as ep ON ep.fk_eventdetails = ac.fk_element
+		WHERE ac.code = 'AC_EVENT'
+		AND ep.fk_product = {$product_id}
+		AND ac.datep > '{$date_from}'
+		ORDER BY ac.datep ASC";
+		$result = $this->db->query($sql);
+		return $this->db->num_rows($result) > 0;
+	}
+
 	private function getCutOffTimeDate($product_id)
 	{
 		$date = date("Y-m-d H:i:s");

+ 87 - 32
custom/booking/class/api_booking.class.php.bak

@@ -544,6 +544,7 @@ class BookingApi extends DolibarrApi
 	 */
 	public function firstEventStep(int $type_id, int $fk_event, int $reservations, int $product_id, string $sendId)
 	{
+		global $conf;
 		ApiBbusLog::appLog("{$sendId}: firstEventStep");
 		if (!DolibarrApiAccess::$user->rights->facture->creer) {
 			ApiBbusLog::appLog("{$sendId} Insufficient rights");
@@ -553,13 +554,14 @@ class BookingApi extends DolibarrApi
 
 
 		$server_host = $this->getServerHost($type_id);
-		if ($server_host == 'excelia') {
+		if ($server_host == $conf->global->LOCAL_SERVER_HOST) {
 			if ($this->noLocalEmptySpaces($fk_event, $reservations)) {
 				throw new RestException(506, 'No available spaces');
 			}
+			ApiBbusLog::appLog("{$sendId}: firstEventStep: local");
 			$createdPreOrderOBJ = $this->localcreatedpreorderobj($sendId, $fk_event, $reservations, $product_id);
 		} else {
-			ApiBbusLog::appLog("{$sendId}: firstEventStep: Bbus");
+			ApiBbusLog::appLog("{$sendId}: firstEventStep: curl");
 			$params = compact('fk_event', 'reservations');
 			$postFields = json_encode($params);
 			$noavailableSpaces = json_decode($this->curlRunner('bookingapi/curlnoLocalEmptySpaces', $postFields, 'POST', true))->result;
@@ -575,8 +577,26 @@ class BookingApi extends DolibarrApi
 		return $createdPreOrderOBJ;
 	}
 
+	/**
+	 * Get all preorders
+	 *
+	 * Return an array with details
+	 *
+	 * @return 	array|mixed data without useless information
+	 *
+	 * @param 	string 	sendId
+	 * @param 	int 	fk_event
+	 * @param 	int 	reservations
+	 * @param 	int 	product_id
+	 * 
+	 * @url	POST localcreatedpreorderobj
+	 * @access protected
+	 * @throws RestException 401 Not allowed
+	 * @throws RestException 404 Not found
+	 */
 	function localcreatedpreorderobj($sendId, $fk_event, $reservations, $product_id)
 	{
+		global $user;
 		ApiBbusLog::appLog("{$sendId}: localcreatedpreorderobj");
 
 		$apiInvoiceHelper = new ApiInvoiceHelper;
@@ -592,14 +612,16 @@ class BookingApi extends DolibarrApi
 				$preOrderObj->ref = $this->generateRandomString();
 				$preOrderObj->fk_event = $fk_event;
 				$preOrderObj->fk_product = $product_id;
-				$result = $preOrderObj->create($this->user);
+				$result = $preOrderObj->create($user);
 				if ($result > 0) {
 					$createdPreOrderOBJ[] = [
 						'id' => $preOrderObj->id,
 						'ref' => $preOrderObj->ref,
 					];
 				} else {
-					ApiBbusLog::appLog("{$sendId}: " . json_encode(['error' => $preOrderObj->errors]));
+					foreach ($preOrderObj->errors as $error) {
+						ApiBbusLog::appLog("Error: " . $error);
+					}
 					ApiBbusLog::appLog("{$sendId}: Unsaved event: " . $fk_event);
 					throw new RestException(401, 'Unsaved event: ' . $fk_event);
 				}
@@ -648,16 +670,13 @@ class BookingApi extends DolibarrApi
 	 * @return 	array|mixed data without useless information
 	 *
 	 * @param 	int 	$fk_event			//selected evet from llx_event
-	 * @param 	int 	$fk_facture			//facture rowid from llx_facture
-	 * @param 	int 	$fk_eventproduct	//eventproduct rowid from llx_eventwizard_eventproduct
-	 * @param 	string 	$ref				//ref
 	 * 
 	 * @url	POST saveeventdata
 	 * @access protected
 	 * @throws RestException 401 Not allowed
 	 * @throws RestException 404 Not found
 	 */
-	public function saveEventData(int $fk_event, int $fk_facture = null, int $fk_eventproduct = null, string $ref = null)
+	public function saveEventData($fk_event, $fk_facture = null, $fk_eventproduct = null, $ref = null)
 	{
 
 		ApiBbusLog::appLog("saveEventData");
@@ -721,11 +740,11 @@ class BookingApi extends DolibarrApi
 	{
 		ApiBbusLog::appLog("validateandsaveinvoice");
 
-		global $db;
+		global $db, $conf;
 		$server_host = $this->getServerHost($type_id);
-		$createdInvoiceArray = $this->localValidateAndSaveInvoice($invoice, $preorder, $payment, $cardPaymentLog, $sendId, $server_host);
-		if ($server_host == 'excelia') {
-			ApiBbusLog::appLog("validateandsaveinvoice: Excelia");
+		$createdInvoiceArray = $this->localValidateAndSaveInvoice($invoice, $preorder, $payment, $cardPaymentLog, $sendId, $server_host, $type_id);
+		if ($server_host == $conf->global->LOCAL_SERVER_HOST) {
+			ApiBbusLog::appLog("validateandsaveinvoice: local");
 			ApiBbusLog::appLog(json_encode($preorder));
 			foreach ($preorder as $order) {
 				$sql = "SELECT fk_product, fk_event  FROM llx_booking_preorder WHERE rowid = {$order}";
@@ -734,18 +753,29 @@ class BookingApi extends DolibarrApi
 				if ($db->num_rows($data) > 0) {
 					while ($row = $db->fetch_object($data)) {
 						ApiBbusLog::appLog("while");
-						$bookingHistory = $this->saveEventData((int)$row->fk_event, $createdInvoiceArray[0]['invoice']['id']);
-						$this->updateBbticket($bookingHistory, $createdInvoiceArray[0]['invoice']);
+						$bookingHistory = $this->saveEventData((int)$row->fk_event, $createdInvoiceArray[0]['invoice']['id'], null, $createdInvoiceArray[0]['invoice']['ref']);
+
+						ApiBbusLog::appLog("______________________________________________________________________________");
+						$this->updateBbticket($createdInvoiceArray[0]['invoice'], $bookingHistory);
 						$this->deletePreorder($preorder);
 					}
 				}
 			}
 		} else {
-			ApiBbusLog::appLog("validateandsaveinvoice: bbus");
+			ApiBbusLog::appLog("validateandsaveinvoice: curl");
+			/* #-------------- UPDATE BTICKET --------------
+			ApiBbusLog::appLog("{$sendId}: Update bbticket with curl");
+			$invoiceForCurl['id'] = $createdInvoiceArray[0]['invoice']['id'];
+			$invoiceForCurl['ref'] = $createdInvoiceArray[0]['invoice']['ref'];
+			$array['fk_booking_history'] = null;
+			$array['invoice'] = $invoiceForCurl;
+			$updateBBticketPostFields = json_encode($array);
+			ApiBbusLog::appLog("{$updateBBticketPostFields}");
+			$this->curlRunner('bookingapi/curlUpdateBbticket', $updateBBticketPostFields, 'POST', true); */
 			foreach ($preorder as $rowid) {
 				$params = compact('rowid');
 				$postFields = json_encode($params);
-				$preorderArray = $this->curlRunner('bookingapi/localpreorderarray', $postFields, 'POST', true);
+				$preorderArray = $this->curlRunner('bookingapi/curllocalpreorderarray', $postFields, 'POST', true);
 				$saveEventdataPostFields = '{"fk_event":"' . (int)$preorderArray->fk_event . '","ref":"' . $createdInvoiceArray[0]['invoice']['ref'] . '","fk_facture":"' . null . '","fk_eventproduct":"' . null . '"}';
 				$bookingHistory = $this->curlRunner('bookingapi/saveEventData', $saveEventdataPostFields, 'POST', true);
 				$array['fk_booking_history'] = $bookingHistory;
@@ -761,11 +791,11 @@ class BookingApi extends DolibarrApi
 		return $createdInvoiceArray;
 	}
 
-	public function localValidateAndSaveInvoice($invoice, $preorder, $payment, $cardPaymentLog, $sendId, $server_host)
+	public function localValidateAndSaveInvoice($invoice, $preorder, $payment, $cardPaymentLog, $sendId, $server_host, $type_id)
 	{
-		ApiBbusLog::eventLog("localValidateAndSaveInvoice");
+		ApiBbusLog::appLog("localValidateAndSaveInvoice");
 
-		global $user, $db;
+		global $user, $db, $conf;
 		$server_host_curl = false;
 		$createdInvoiceData = [];
 		$apiInvoiceHelper = new ApiInvoiceHelper;
@@ -773,13 +803,15 @@ class BookingApi extends DolibarrApi
 		foreach ($preorder as $rowid) {
 			$params = compact('rowid');
 			$postFields = json_encode($params);
-			if ($server_host == 'excelia') {
-				ApiBbusLog::appLog("localValidateAndSaveInvoice: Excelia");
+			if ($server_host == $conf->global->LOCAL_SERVER_HOST) {
+				ApiBbusLog::appLog("localValidateAndSaveInvoice: local");
 				$preorderArray = $this->localpreorderarray($rowid);
 			} else {
-				ApiBbusLog::appLog("localValidateAndSaveInvoice: bbus");
-				$server_host_curl = true;
-				$preorderArray = $this->curlRunner('bookingapi/localpreorderarray', $postFields, 'POST', true);
+				ApiBbusLog::appLog("localValidateAndSaveInvoice: curl");
+				if ($type_id != 5 || $type_id != 3 || $type_id != 4) {
+					$server_host_curl = true;
+				}
+				$preorderArray = $this->curlRunner('bookingapi/curllocalpreorderarray', $postFields, 'POST', true);
 			}
 			$lines = $apiInvoiceHelper->getProductLinesWithoutDiscount($preorderArray->fk_product);
 			//$lines = $apiInvoiceHelper->createLinesForCrossShopping($preorderArray->fk_product);
@@ -844,6 +876,7 @@ class BookingApi extends DolibarrApi
 
 		global $user;
 		$sql = "SELECT rowid FROM llx_bbus_bbticket WHERE fk_facture = {$invoice['id']}";
+		ApiBbusLog::appLog("{$sql}");
 		$data = $this->db->query($sql);
 		if (!empty($this->db->error())) {
 			ApiBbusLog::appLog("{$this->db->error()}");
@@ -880,20 +913,20 @@ class BookingApi extends DolibarrApi
 	 * @throws RestException 506 No available spaces
 	 * 
 	 */
-	public function curlupdateBbticket(array $invoice, string $fk_booking_history = null)
+	public function curlupdateBbticket(array $invoice, $fk_booking_history = null)
 	{
-		ApiBbusLog::appLog("curlupdateBbticket______START");
 		global $user;
-		$sql = "SELECT rowid FROM llx_bbus_bbticket WHERE invoice_number = '{$invoice['id']}'";
+		ApiBbusLog::appLog("curlupdateBbticket______START");
+		$sql = "SELECT rowid FROM llx_bbus_bbticket WHERE invoice_number = '{$invoice['id']}' OR invoice_number = '{$invoice['ref']}'";
+		ApiBbusLog::appLog("{$sql}");
 		$data = $this->db->query($sql);
 		if (!empty($this->db->error())) {
 			ApiBbusLog::appLog("{$this->db->error()}");
 			throw new RestException(401, "{$this->db->error()}");
 		}
-		$data = $this->db->query($sql);
 		if ($this->db->num_rows($data) > 0) {
 			while ($row = $this->db->fetch_object($data)) {
-				$booking_history_id = is_null($fk_booking_history) ? 'booking_history_id = null,' : '';
+				$booking_history_id = is_null($fk_booking_history) ? 'booking_history_id = null,' : 'booking_history_id = ' . $fk_booking_history . ',';
 				$sql = "UPDATE llx_bbus_bbticket
 					SET {$booking_history_id} invoice_number = '{$invoice['ref']}'
 					WHERE rowid = {$row->rowid}";
@@ -1136,11 +1169,13 @@ class BookingApi extends DolibarrApi
 	 */
 	public function getAvailableSpaces(int $type_id, string $date_from, string $date_to, int $product_id, int $participant_number)
 	{
+		global $conf;
 		$server_host = $this->getServerHost($type_id);
-		if ($server_host == 'excelia') {
-			ApiBbusLog::appLog("getAvailableSpaces: bbus");
+		if ($server_host == $conf->global->LOCAL_SERVER_HOST) {
+			ApiBbusLog::appLog("getAvailableSpaces: local");
 			return $this->localAvailablePlaces($date_from, $date_to, $product_id, $participant_number);
 		} else {
+			ApiBbusLog::appLog("getAvailableSpaces: curl");
 			$params = compact('date_from', 'date_to', 'product_id', 'participant_number');
 			$postFields = json_encode($params);
 			return $this->curlRunner('bookingapi/localavailableplaces', $postFields, 'POST', true);
@@ -1178,6 +1213,7 @@ class BookingApi extends DolibarrApi
 		$date = new DateTime($date_to);
 		$date->modify('+1 day');
 		$date_to = $date->format('Y-m-d');
+		$date_from = $this->getCutOffTimeDate($product_id);
 		$sql = "SELECT 
 		ac.id, 
 		ac.datep, 
@@ -1190,7 +1226,7 @@ class BookingApi extends DolibarrApi
 		INNER JOIN llx_eventwizard_eventproduct as ep ON ep.fk_eventdetails = ac.fk_element
 		WHERE ac.code = 'AC_EVENT'
 		AND ep.fk_product = {$product_id}
-		AND ac.datep > '{$date_from} 00:00:00' 
+		AND ac.datep > '{$date_from}' 
 		AND ac.datep2 < '{$date_to} 00:00:00'
 		ORDER BY ac.datep ASC";
 		$result = $this->db->query($sql);
@@ -1213,6 +1249,25 @@ class BookingApi extends DolibarrApi
 		}
 		return $array;
 	}
+
+	private function getCutOffTimeDate($product_id)
+	{
+		$date = date("Y-m-d H:i:s");
+		ApiBbusLog::appLog("{$date}");
+		$sql = "SELECT cut_off_time_app FROM llx_product_extrafields WHERE fk_object = {$product_id}";
+		if ($result = $this->db->query($sql)) {
+			if ($this->db->num_rows($result) > 0) {
+				while ($row = $this->db->fetch_object($result)) {
+					$cutOffTime = $row->cut_off_time_app;
+				}
+				$dateTimestamp = strtotime($date);
+				$date = date("Y-m-d H:i:s", $dateTimestamp + $cutOffTime * 60);
+			}
+		}
+		ApiBbusLog::appLog("{$date}");
+		return $date;
+	}
+
 	private function noLocalEmptySpaces($event_id, $reservations)
 	{
 		$sql = "SELECT ace.max_num, ace.buffer, ace.participants FROM llx_actioncomm as ac