1<?php
2/* Copyright (C) 2017  Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2021 Alexis LAURIER <contact@alexislaurier.fr>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19/**
20 * \file        class/productfournisseurprice.class.php
21 * \ingroup     product
22 * \brief       This file is a CRUD class file for ProductFournisseurPrice (Create/Read/Update/Delete)
23 */
24
25// Put here all includes required by your class file
26require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
27
28/**
29 * Class for ProductFournisseurPrice
30 */
31class ProductFournisseurPrice extends CommonObject
32{
33	/**
34	 * @var string ID to identify managed object.
35	 */
36	public $element = 'productfournisseurprice';
37
38	/**
39	 * @var string Name of table without prefix where object is stored. This is also the key used for extrafields management.
40	 */
41	public $table_element = 'product_fournisseur_price';
42
43	/**
44	 * @var int  Does this object support multicompany module ?
45	 * 0=No test on entity, 1=Test with field entity, 'field@table'=Test with link by field@table
46	 */
47	public $ismultientitymanaged = 1;
48
49	/**
50	 * @var int  Does object support extrafields ? 0=No, 1=Yes
51	 */
52	public $isextrafieldmanaged = 1;
53
54	/**
55	 * @var string String with name of icon for productfournisseurprice. Must be the part after the 'object_' into object_productfournisseurprice.png
56	 */
57	public $picto = 'productfournisseurprice@buypricehistory';
58
59
60	const STATUS_DRAFT = 0;
61	const STATUS_VALIDATED = 1;
62	const STATUS_CANCELED = 9;
63
64
65	/**
66	 *  'type' field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter]]', 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter]]]', 'varchar(x)', 'double(24,8)', 'real', 'price', 'text', 'text:none', 'html', 'date', 'datetime', 'timestamp', 'duration', 'mail', 'phone', 'url', 'password')
67	 *         Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)"
68	 *  'label' the translation key.
69	 *  'picto' is code of a picto to show before value in forms
70	 *  'enabled' is a condition when the field must be managed (Example: 1 or '$conf->global->MY_SETUP_PARAM)
71	 *  'position' is the sort order of field.
72	 *  'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
73	 *  'visible' says if field is visible in list (Examples: 0=Not visible, 1=Visible on list and create/update/view forms, 2=Visible on list only, 3=Visible on create/update/view form only (not list), 4=Visible on list and update/view form only (not create). 5=Visible on list and view only (not create/not update). Using a negative value means field is not shown by default on list but can be selected for viewing)
74	 *  'noteditable' says if field is not editable (1 or 0)
75	 *  'default' is a default value for creation (can still be overwrote by the Setup of Default Values if field is editable in creation form). Note: If default is set to '(PROV)' and field is 'ref', the default value will be set to '(PROVid)' where id is rowid when a new record is created.
76	 *  'index' if we want an index in database.
77	 *  'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommanded to name the field fk_...).
78	 *  'searchall' is 1 if we want to search in this field when making a search from the quick search button.
79	 *  'isameasure' must be set to 1 if you want to have a total on list for this field. Field type must be summable like integer or double(24,8).
80	 *  'css' and 'cssview' and 'csslist' is the CSS style to use on field. 'css' is used in creation and update. 'cssview' is used in view mode. 'csslist' is used for columns in lists. For example: 'maxwidth200', 'wordbreak', 'tdoverflowmax200'
81	 *  'help' is a 'TranslationString' to use to show a tooltip on field. You can also use 'TranslationString:keyfortooltiponlick' for a tooltip on click.
82	 *  'showoncombobox' if value of the field must be visible into the label of the combobox that list record
83	 *  'disabled' is 1 if we want to have the field locked by a 'disabled' attribute. In most cases, this is never set into the definition of $fields into class, but is set dynamically by some part of code.
84	 *  'arrayofkeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel")
85	 *  'autofocusoncreate' to have field having the focus on a create form. Only 1 field should have this property set to 1.
86	 *  'comment' is not used. You can store here any text of your choice. It is not used by application.
87	 *
88	 *  Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
89	 */
90
91	// BEGIN MODULEBUILDER PROPERTIES
92	/**
93	 * @var array  Array with all fields and their property. Do not use it as a static var. It may be modified by constructor.
94	 */
95	public $fields=array(
96		'rowid' => array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>'1', 'position'=>10, 'notnull'=>1, 'visible'=>0,),
97		'entity' => array('type'=>'integer', 'label'=>'Entity', 'enabled'=>'1', 'position'=>15, 'notnull'=>1, 'visible'=>-2, 'default'=>'1', 'index'=>1,),
98		'datec' => array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>'1', 'position'=>20, 'notnull'=>0, 'visible'=>-1,),
99		'tms' => array('type'=>'timestamp', 'label'=>'DateModification', 'enabled'=>'1', 'position'=>25, 'notnull'=>1, 'visible'=>-1,),
100		'fk_product' => array('type'=>'integer:Product:product/class/product.class.php:1', 'label'=>'Fkproduct', 'enabled'=>'1', 'position'=>30, 'notnull'=>0, 'visible'=>-1,),
101		'fk_soc' => array('type'=>'integer:Societe:societe/class/societe.class.php', 'label'=>'ThirdParty', 'enabled'=>'1', 'position'=>35, 'notnull'=>0, 'visible'=>-1,),
102		'ref_fourn' => array('type'=>'varchar(255)', 'label'=>'Reffourn', 'enabled'=>'1', 'position'=>40, 'notnull'=>0, 'visible'=>-1,),
103		'desc_fourn' => array('type'=>'text', 'label'=>'Descfourn', 'enabled'=>'1', 'position'=>45, 'notnull'=>0, 'visible'=>-1,),
104		'fk_availability' => array('type'=>'integer', 'label'=>'Fkavailability', 'enabled'=>'1', 'position'=>50, 'notnull'=>0, 'visible'=>-1,),
105		'price' => array('type'=>'double(24,8)', 'label'=>'Price', 'enabled'=>'1', 'position'=>55, 'notnull'=>0, 'visible'=>-1,),
106		'quantity' => array('type'=>'double', 'label'=>'Quantity', 'enabled'=>'1', 'position'=>60, 'notnull'=>0, 'visible'=>-1,),
107		'remise_percent' => array('type'=>'double', 'label'=>'Remisepercent', 'enabled'=>'1', 'position'=>65, 'notnull'=>1, 'visible'=>-1,),
108		'remise' => array('type'=>'double', 'label'=>'Remise', 'enabled'=>'1', 'position'=>70, 'notnull'=>1, 'visible'=>-1,),
109		'unitprice' => array('type'=>'double(24,8)', 'label'=>'Unitprice', 'enabled'=>'1', 'position'=>75, 'notnull'=>0, 'visible'=>-1,),
110		'charges' => array('type'=>'double(24,8)', 'label'=>'Charges', 'enabled'=>'1', 'position'=>80, 'notnull'=>0, 'visible'=>-1,),
111		'default_vat_code' => array('type'=>'varchar(10)', 'label'=>'Defaultvatcode', 'enabled'=>'1', 'position'=>85, 'notnull'=>0, 'visible'=>-1,),
112		'tva_tx' => array('type'=>'double(6,3)', 'label'=>'Tvatx', 'enabled'=>'1', 'position'=>90, 'notnull'=>1, 'visible'=>-1,),
113		'info_bits' => array('type'=>'integer', 'label'=>'Infobits', 'enabled'=>'1', 'position'=>95, 'notnull'=>1, 'visible'=>-1,),
114		'fk_user' => array('type'=>'integer:User:user/class/user.class.php', 'label'=>'Fkuser', 'enabled'=>'1', 'position'=>100, 'notnull'=>0, 'visible'=>-1,),
115		'fk_supplier_price_expression' => array('type'=>'integer', 'label'=>'Fksupplierpriceexpression', 'enabled'=>'1', 'position'=>105, 'notnull'=>0, 'visible'=>-1,),
116		'import_key' => array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>'1', 'position'=>900, 'notnull'=>0, 'visible'=>-2,),
117		'delivery_time_days' => array('type'=>'integer', 'label'=>'Deliverytimedays', 'enabled'=>'1', 'position'=>115, 'notnull'=>0, 'visible'=>-1,),
118		'supplier_reputation' => array('type'=>'varchar(10)', 'label'=>'Supplierreputation', 'enabled'=>'1', 'position'=>120, 'notnull'=>0, 'visible'=>-1,),
119		'fk_multicurrency' => array('type'=>'integer', 'label'=>'Fkmulticurrency', 'enabled'=>'1', 'position'=>125, 'notnull'=>0, 'visible'=>-1,),
120		'multicurrency_code' => array('type'=>'varchar(255)', 'label'=>'Multicurrencycode', 'enabled'=>'1', 'position'=>130, 'notnull'=>0, 'visible'=>-1,),
121		'multicurrency_tx' => array('type'=>'double(24,8)', 'label'=>'Multicurrencytx', 'enabled'=>'1', 'position'=>135, 'notnull'=>0, 'visible'=>-1,),
122		'multicurrency_price' => array('type'=>'double(24,8)', 'label'=>'Multicurrencyprice', 'enabled'=>'1', 'position'=>140, 'notnull'=>0, 'visible'=>-1,),
123		'multicurrency_unitprice' => array('type'=>'double(24,8)', 'label'=>'Multicurrencyunitprice', 'enabled'=>'1', 'position'=>145, 'notnull'=>0, 'visible'=>-1,),
124		'localtax1_tx' => array('type'=>'double(6,3)', 'label'=>'Localtax1tx', 'enabled'=>'1', 'position'=>150, 'notnull'=>0, 'visible'=>-1,),
125		'localtax1_type' => array('type'=>'varchar(10)', 'label'=>'Localtax1type', 'enabled'=>'1', 'position'=>155, 'notnull'=>1, 'visible'=>-1,),
126		'localtax2_tx' => array('type'=>'double(6,3)', 'label'=>'Localtax2tx', 'enabled'=>'1', 'position'=>160, 'notnull'=>0, 'visible'=>-1,),
127		'localtax2_type' => array('type'=>'varchar(10)', 'label'=>'Localtax2type', 'enabled'=>'1', 'position'=>165, 'notnull'=>1, 'visible'=>-1,),
128		'barcode' => array('type'=>'varchar(180)', 'label'=>'Barcode', 'enabled'=>'1', 'position'=>170, 'notnull'=>0, 'visible'=>-1,),
129		'fk_barcode_type' => array('type'=>'integer', 'label'=>'Fkbarcodetype', 'enabled'=>'1', 'position'=>175, 'notnull'=>0, 'visible'=>-1,),
130		'packaging' => array('type'=>'varchar(64)', 'label'=>'Packaging', 'enabled'=>'1', 'position'=>180, 'notnull'=>0, 'visible'=>-1,),
131	);
132	public $rowid;
133	public $entity;
134	public $datec;
135	public $tms;
136	public $fk_product;
137	public $fk_soc;
138	public $ref_fourn;
139	public $desc_fourn;
140	public $fk_availability;
141	public $price;
142	public $quantity;
143	public $remise_percent;
144	public $remise;
145	public $unitprice;
146	public $charges;
147	public $default_vat_code;
148	public $tva_tx;
149	public $info_bits;
150	public $fk_user;
151	public $fk_supplier_price_expression;
152	public $import_key;
153	public $delivery_time_days;
154	public $supplier_reputation;
155	public $fk_multicurrency;
156	public $multicurrency_code;
157	public $multicurrency_tx;
158	public $multicurrency_price;
159	public $multicurrency_unitprice;
160	public $localtax1_tx;
161	public $localtax1_type;
162	public $localtax2_tx;
163	public $localtax2_type;
164	public $barcode;
165	public $fk_barcode_type;
166	public $packaging;
167	// END MODULEBUILDER PROPERTIES
168
169	/**
170	 * Constructor
171	 *
172	 * @param DoliDb $db Database handler
173	 */
174	public function __construct(DoliDB $db)
175	{
176		global $conf, $langs;
177
178		$this->db = $db;
179
180		if (empty($conf->global->MAIN_SHOW_TECHNICAL_ID) && isset($this->fields['rowid'])) $this->fields['rowid']['visible'] = 0;
181		if (empty($conf->multicompany->enabled) && isset($this->fields['entity'])) $this->fields['entity']['enabled'] = 0;
182
183		// Unset fields that are disabled
184		foreach ($this->fields as $key => $val) {
185			if (isset($val['enabled']) && empty($val['enabled'])) {
186				unset($this->fields[$key]);
187			}
188		}
189	}
190
191	/**
192	 * Create object into database
193	 *
194	 * @param  User $user      User that creates
195	 * @param  bool $notrigger false=launch triggers after, true=disable triggers
196	 * @return int             <0 if KO, Id of created object if OK
197	 */
198	public function create(User $user, $notrigger = false)
199	{
200		return $this->createCommon($user, $notrigger);
201	}
202
203	/**
204	 * Clone an object into another one
205	 *
206	 * @param  	User 	$user      	User that creates
207	 * @param  	int 	$fromid     Id of object to clone
208	 * @return 	mixed 				New object created, <0 if KO
209	 */
210	public function createFromClone(User $user, $fromid)
211	{
212		global $langs, $extrafields;
213		$error = 0;
214
215		dol_syslog(__METHOD__, LOG_DEBUG);
216
217		$object = new self($this->db);
218
219		$this->db->begin();
220
221		// Load source object
222		$result = $object->fetchCommon($fromid);
223		if ($result > 0 && !empty($object->table_element_line)) $object->fetchLines();
224
225		// get lines so they will be clone
226		//foreach($this->lines as $line)
227		//	$line->fetch_optionals();
228
229		// Reset some properties
230		unset($object->id);
231		unset($object->fk_user_creat);
232		unset($object->import_key);
233
234		// Clear fields
235		if (property_exists($object, 'ref')) $object->ref = empty($this->fields['ref']['default']) ? "Copy_Of_".$object->ref : $this->fields['ref']['default'];
236		if (property_exists($object, 'label')) $object->label = empty($this->fields['label']['default']) ? $langs->trans("CopyOf")." ".$object->label : $this->fields['label']['default'];
237		if (property_exists($object, 'status')) { $object->status = self::STATUS_DRAFT; }
238		if (property_exists($object, 'date_creation')) { $object->date_creation = dol_now(); }
239		if (property_exists($object, 'date_modification')) { $object->date_modification = null; }
240		// ...
241		// Clear extrafields that are unique
242		if (is_array($object->array_options) && count($object->array_options) > 0) {
243			$extrafields->fetch_name_optionals_label($this->table_element);
244			foreach ($object->array_options as $key => $option) {
245				$shortkey = preg_replace('/options_/', '', $key);
246				if (!empty($extrafields->attributes[$this->table_element]['unique'][$shortkey])) {
247					//var_dump($key); var_dump($clonedObj->array_options[$key]); exit;
248					unset($object->array_options[$key]);
249				}
250			}
251		}
252
253		// Create clone
254		$object->context['createfromclone'] = 'createfromclone';
255		$result = $object->createCommon($user);
256		if ($result < 0) {
257			$error++;
258			$this->error = $object->error;
259			$this->errors = $object->errors;
260		}
261
262		if (!$error) {
263			// copy internal contacts
264			if ($this->copy_linked_contact($object, 'internal') < 0) {
265				$error++;
266			}
267		}
268
269		if (!$error) {
270			// copy external contacts if same company
271			if (property_exists($this, 'socid') && $this->socid == $object->socid) {
272				if ($this->copy_linked_contact($object, 'external') < 0)
273					$error++;
274			}
275		}
276
277		unset($object->context['createfromclone']);
278
279		// End
280		if (!$error) {
281			$this->db->commit();
282			return $object;
283		} else {
284			$this->db->rollback();
285			return -1;
286		}
287	}
288
289	/**
290	 * Load object in memory from the database
291	 *
292	 * @param int    $id   Id object
293	 * @return int         <0 if KO, 0 if not found, >0 if OK
294	 */
295	public function fetch($id)
296	{
297		return $this->fetchCommon($id);
298	}
299
300	/**
301	 * Load list of objects in memory from the database.
302	 *
303	 * @param  string      $sortorder    Sort Order
304	 * @param  string      $sortfield    Sort field
305	 * @param  int         $limit        limit
306	 * @param  int         $offset       Offset
307	 * @param  array       $filter       Filter array. Example array('field'=>'valueforlike', 'customurl'=>...)
308	 * @param  string      $filtermode   Filter mode (AND or OR)
309	 * @return array|int                 int <0 if KO, array of pages if OK
310	 */
311	public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, array $filter = array(), $filtermode = 'AND')
312	{
313		global $conf;
314
315		dol_syslog(__METHOD__, LOG_DEBUG);
316
317		$records = array();
318
319		$sql = 'SELECT ';
320		$sql .= $this->getFieldList();
321		$sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
322		if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) $sql .= ' WHERE t.entity IN ('.getEntity($this->element).')';
323		else $sql .= ' WHERE 1 = 1';
324		// Manage filter
325		$sqlwhere = array();
326		if (count($filter) > 0) {
327			foreach ($filter as $key => $value) {
328				if ($key == 't.rowid') {
329					$sqlwhere[] = $key.' = '.((int) $value);
330				} elseif (in_array($this->fields[$key]['type'], array('date', 'datetime', 'timestamp'))) {
331					$sqlwhere[] = $key." = '".$this->db->idate($value)."'";
332				} elseif ($key == 'customsql') {
333					$sqlwhere[] = $value;
334				} elseif (strpos($value, '%') === false) {
335					$sqlwhere[] = $key.' IN ('.$this->db->sanitize($this->db->escape($value)).')';
336				} else {
337					$sqlwhere[] = $key." LIKE '%".$this->db->escape($value)."%'";
338				}
339			}
340		}
341		if (count($sqlwhere) > 0) {
342			$sql .= ' AND ('.implode(' '.$filtermode.' ', $sqlwhere).')';
343		}
344
345		if (!empty($sortfield)) {
346			$sql .= $this->db->order($sortfield, $sortorder);
347		}
348		if (!empty($limit)) {
349			$sql .= ' '.$this->db->plimit($limit, $offset);
350		}
351
352		$resql = $this->db->query($sql);
353		if ($resql) {
354			$num = $this->db->num_rows($resql);
355			$i = 0;
356			while ($i < ($limit ? min($limit, $num) : $num)) {
357				$obj = $this->db->fetch_object($resql);
358
359				$record = new self($this->db);
360				$record->setVarsFromFetchObj($obj);
361
362				$records[$record->id] = $record;
363
364				$i++;
365			}
366			$this->db->free($resql);
367
368			return $records;
369		} else {
370			$this->errors[] = 'Error '.$this->db->lasterror();
371			dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
372
373			return -1;
374		}
375	}
376
377	/**
378	 * Update object into database
379	 *
380	 * @param  User $user      User that modifies
381	 * @param  bool $notrigger false=launch triggers after, true=disable triggers
382	 * @return int             <0 if KO, >0 if OK
383	 */
384	public function update(User $user, $notrigger = false)
385	{
386		return $this->updateCommon($user, $notrigger);
387	}
388
389	/**
390	 * Delete object in database
391	 *
392	 * @param User $user       User that deletes
393	 * @param bool $notrigger  false=launch triggers after, true=disable triggers
394	 * @return int             <0 if KO, >0 if OK
395	 */
396	public function delete(User $user, $notrigger = false)
397	{
398		return $this->deleteCommon($user, $notrigger);
399	}
400
401	/**
402	 *	Validate object
403	 *
404	 *	@param		User	$user     		User making status change
405	 *  @param		int		$notrigger		1=Does not execute triggers, 0= execute triggers
406	 *	@return  	int						<=0 if OK, 0=Nothing done, >0 if KO
407	 */
408	public function validate($user, $notrigger = 0)
409	{
410		global $conf, $langs;
411
412		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
413
414		$error = 0;
415
416		// Protection
417		if ($this->status == self::STATUS_VALIDATED) {
418			dol_syslog(get_class($this)."::validate action abandonned: already validated", LOG_WARNING);
419			return 0;
420		}
421
422		$now = dol_now();
423
424		$this->db->begin();
425
426		// Define new ref
427		if (!$error && (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref))) { // empty should not happened, but when it occurs, the test save life
428			$num = $this->getNextNumRef();
429		} else {
430			$num = $this->ref;
431		}
432		$this->newref = $num;
433
434		if (!empty($num)) {
435			// Validate
436			$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
437			$sql .= " SET ref = '".$this->db->escape($num)."',";
438			$sql .= " status = ".self::STATUS_VALIDATED;
439			if (!empty($this->fields['date_validation'])) $sql .= ", date_validation = '".$this->db->idate($now)."'";
440			if (!empty($this->fields['fk_user_valid'])) $sql .= ", fk_user_valid = ".$user->id;
441			$sql .= " WHERE rowid = ".((int) $this->id);
442
443			dol_syslog(get_class($this)."::validate()", LOG_DEBUG);
444			$resql = $this->db->query($sql);
445			if (!$resql) {
446				dol_print_error($this->db);
447				$this->error = $this->db->lasterror();
448				$error++;
449			}
450
451			if (!$error && !$notrigger) {
452				// Call trigger
453				$result = $this->call_trigger('PRODUCTFOURNISSEURPRICE_VALIDATE', $user);
454				if ($result < 0) $error++;
455				// End call triggers
456			}
457		}
458
459		if (!$error) {
460			$this->oldref = $this->ref;
461
462			// Rename directory if dir was a temporary ref
463			if (preg_match('/^[\(]?PROV/i', $this->ref)) {
464				// Now we rename also files into index
465				$sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'productfournisseurprice/".$this->db->escape($this->newref)."'";
466				$sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'productfournisseurprice/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
467				$resql = $this->db->query($sql);
468				if (!$resql) { $error++; $this->error = $this->db->lasterror(); }
469
470				// We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
471				$oldref = dol_sanitizeFileName($this->ref);
472				$newref = dol_sanitizeFileName($num);
473				$dirsource = $conf->buypricehistory->dir_output.'/productfournisseurprice/'.$oldref;
474				$dirdest = $conf->buypricehistory->dir_output.'/productfournisseurprice/'.$newref;
475				if (!$error && file_exists($dirsource)) {
476					dol_syslog(get_class($this)."::validate() rename dir ".$dirsource." into ".$dirdest);
477
478					if (@rename($dirsource, $dirdest)) {
479						dol_syslog("Rename ok");
480						// Rename docs starting with $oldref with $newref
481						$listoffiles = dol_dir_list($conf->buypricehistory->dir_output.'/productfournisseurprice/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
482						foreach ($listoffiles as $fileentry) {
483							$dirsource = $fileentry['name'];
484							$dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
485							$dirsource = $fileentry['path'].'/'.$dirsource;
486							$dirdest = $fileentry['path'].'/'.$dirdest;
487							@rename($dirsource, $dirdest);
488						}
489					}
490				}
491			}
492		}
493
494		// Set new ref and current status
495		if (!$error) {
496			$this->ref = $num;
497			$this->status = self::STATUS_VALIDATED;
498		}
499
500		if (!$error) {
501			$this->db->commit();
502			return 1;
503		} else {
504			$this->db->rollback();
505			return -1;
506		}
507	}
508
509
510	/**
511	 *	Set draft status
512	 *
513	 *	@param	User	$user			Object user that modify
514	 *  @param	int		$notrigger		1=Does not execute triggers, 0=Execute triggers
515	 *	@return	int						<0 if KO, >0 if OK
516	 */
517	public function setDraft($user, $notrigger = 0)
518	{
519		// Protection
520		if ($this->status <= self::STATUS_DRAFT) {
521			return 0;
522		}
523
524		return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'PRODUCTFOURNISSEURPRICE_UNVALIDATE');
525	}
526
527	/**
528	 *	Set cancel status
529	 *
530	 *	@param	User	$user			Object user that modify
531	 *  @param	int		$notrigger		1=Does not execute triggers, 0=Execute triggers
532	 *	@return	int						<0 if KO, 0=Nothing done, >0 if OK
533	 */
534	public function cancel($user, $notrigger = 0)
535	{
536		// Protection
537		if ($this->status != self::STATUS_VALIDATED) {
538			return 0;
539		}
540
541		return $this->setStatusCommon($user, self::STATUS_CANCELED, $notrigger, 'PRODUCTFOURNISSEURPRICE_CANCEL');
542	}
543
544	/**
545	 *	Set back to validated status
546	 *
547	 *	@param	User	$user			Object user that modify
548	 *  @param	int		$notrigger		1=Does not execute triggers, 0=Execute triggers
549	 *	@return	int						<0 if KO, 0=Nothing done, >0 if OK
550	 */
551	public function reopen($user, $notrigger = 0)
552	{
553		// Protection
554		if ($this->status != self::STATUS_CANCELED) {
555			return 0;
556		}
557
558		return $this->setStatusCommon($user, self::STATUS_VALIDATED, $notrigger, 'PRODUCTFOURNISSEURPRICE_REOPEN');
559	}
560
561	/**
562	 *  Return a link to the object card (with optionaly the picto)
563	 *
564	 *  @param  int     $withpicto                  Include picto in link (0=No picto, 1=Include picto into link, 2=Only picto)
565	 *  @param  string  $option                     On what the link point to ('nolink', ...)
566	 *  @param  int     $notooltip                  1=Disable tooltip
567	 *  @param  string  $morecss                    Add more css on link
568	 *  @param  int     $save_lastsearch_value      -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
569	 *  @return	string                              String with URL
570	 */
571	public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
572	{
573		global $conf, $langs, $hookmanager;
574
575		if (!empty($conf->dol_no_mouse_hover)) $notooltip = 1; // Force disable tooltips
576
577		$result = '';
578
579		$label = img_picto('', $this->picto).' <u>'.$langs->trans("ProductFournisseurPrice").'</u>';
580		if (isset($this->status)) {
581			$label .= ' '.$this->getLibStatut(5);
582		}
583		$label .= '<br>';
584		$label .= '<b>'.$langs->trans('Ref').':</b> '.$this->ref;
585
586		$url = dol_buildpath('/buypricehistory/productfournisseurprice_card.php', 1).'?id='.$this->id;
587
588		if ($option != 'nolink') {
589			// Add param to save lastsearch_values or not
590			$add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
591			if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) $add_save_lastsearch_values = 1;
592			if ($add_save_lastsearch_values) $url .= '&save_lastsearch_values=1';
593		}
594
595		$linkclose = '';
596		if (empty($notooltip)) {
597			if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
598				$label = $langs->trans("ShowProductFournisseurPrice");
599				$linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
600			}
601			$linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
602			$linkclose .= ' class="classfortooltip'.($morecss ? ' '.$morecss : '').'"';
603		} else $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
604
605		$linkstart = '<a href="'.$url.'"';
606		$linkstart .= $linkclose.'>';
607		$linkend = '</a>';
608
609		$result .= $linkstart;
610
611		if (empty($this->showphoto_on_popup)) {
612			if ($withpicto) $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
613		} else {
614			if ($withpicto) {
615				require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
616
617				list($class, $module) = explode('@', $this->picto);
618				$upload_dir = $conf->$module->multidir_output[$conf->entity]."/$class/".dol_sanitizeFileName($this->ref);
619				$filearray = dol_dir_list($upload_dir, "files");
620				$filename = $filearray[0]['name'];
621				if (!empty($filename)) {
622					$pospoint = strpos($filearray[0]['name'], '.');
623
624					$pathtophoto = $class.'/'.$this->ref.'/thumbs/'.substr($filename, 0, $pospoint).'_mini'.substr($filename, $pospoint);
625					if (empty($conf->global->{strtoupper($module.'_'.$class).'_FORMATLISTPHOTOSASUSERS'})) {
626						$result .= '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref"><img class="photo'.$module.'" alt="No photo" border="0" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$module.'&entity='.$conf->entity.'&file='.urlencode($pathtophoto).'"></div></div>';
627					} else {
628						$result .= '<div class="floatleft inline-block valignmiddle divphotoref"><img class="photouserphoto userphoto" alt="No photo" border="0" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart='.$module.'&entity='.$conf->entity.'&file='.urlencode($pathtophoto).'"></div>';
629					}
630
631					$result .= '</div>';
632				} else {
633					$result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
634				}
635			}
636		}
637
638		if ($withpicto != 2) $result .= $this->ref;
639
640		$result .= $linkend;
641		//if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
642
643		global $action, $hookmanager;
644		$hookmanager->initHooks(array('productfournisseurpricedao'));
645		$parameters = array('id'=>$this->id, 'getnomurl'=>$result);
646		$reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
647		if ($reshook > 0) $result = $hookmanager->resPrint;
648		else $result .= $hookmanager->resPrint;
649
650		return $result;
651	}
652
653	/**
654	 *  Return the label of the status
655	 *
656	 *  @param  int		$mode          0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto
657	 *  @return	string 			       Label of status
658	 */
659	public function getLibStatut($mode = 0)
660	{
661		return $this->LibStatut($this->status, $mode);
662	}
663
664	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
665	/**
666	 *  Return the status
667	 *
668	 *  @param	int		$status        Id status
669	 *  @param  int		$mode          0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto
670	 *  @return string 			       Label of status
671	 */
672	public function LibStatut($status, $mode = 0)
673	{
674		// phpcs:enable
675		if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
676			global $langs;
677			//$langs->load("buypricehistory@buypricehistory");
678			$this->labelStatus[self::STATUS_DRAFT] = $langs->trans('Draft');
679			$this->labelStatus[self::STATUS_VALIDATED] = $langs->trans('Enabled');
680			$this->labelStatus[self::STATUS_CANCELED] = $langs->trans('Disabled');
681			$this->labelStatusShort[self::STATUS_DRAFT] = $langs->trans('Draft');
682			$this->labelStatusShort[self::STATUS_VALIDATED] = $langs->trans('Enabled');
683			$this->labelStatusShort[self::STATUS_CANCELED] = $langs->trans('Disabled');
684		}
685
686		$statusType = 'status'.$status;
687		//if ($status == self::STATUS_VALIDATED) $statusType = 'status1';
688		if ($status == self::STATUS_CANCELED) $statusType = 'status6';
689
690		return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
691	}
692
693	/**
694	 *	Load the info information in the object
695	 *
696	 *	@param  int		$id       Id of object
697	 *	@return	void
698	 */
699	public function info($id)
700	{
701		$sql = 'SELECT rowid, date_creation as datec, tms as datem,';
702		$sql .= ' fk_user_creat, fk_user_modif';
703		$sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
704		$sql .= ' WHERE t.rowid = '.((int) $id);
705		$result = $this->db->query($sql);
706		if ($result) {
707			if ($this->db->num_rows($result)) {
708				$obj = $this->db->fetch_object($result);
709				$this->id = $obj->rowid;
710				if ($obj->fk_user_author) {
711					$cuser = new User($this->db);
712					$cuser->fetch($obj->fk_user_author);
713					$this->user_creation = $cuser;
714				}
715
716				if ($obj->fk_user_valid) {
717					$vuser = new User($this->db);
718					$vuser->fetch($obj->fk_user_valid);
719					$this->user_validation = $vuser;
720				}
721
722				if ($obj->fk_user_cloture) {
723					$cluser = new User($this->db);
724					$cluser->fetch($obj->fk_user_cloture);
725					$this->user_cloture = $cluser;
726				}
727
728				$this->date_creation     = $this->db->jdate($obj->datec);
729				$this->date_modification = $this->db->jdate($obj->datem);
730				$this->date_validation   = $this->db->jdate($obj->datev);
731			}
732
733			$this->db->free($result);
734		} else {
735			dol_print_error($this->db);
736		}
737	}
738
739	/**
740	 * Initialise object with example values
741	 * Id must be 0 if object instance is a specimen
742	 *
743	 * @return void
744	 */
745	public function initAsSpecimen()
746	{
747		$this->initAsSpecimenCommon();
748	}
749
750	/**
751	 *  Returns the reference to the following non used object depending on the active numbering module.
752	 *
753	 *  @return string      		Object free reference
754	 */
755	public function getNextNumRef()
756	{
757		global $langs, $conf;
758		$langs->load("buypricehistory@buypricehistory");
759
760		if (empty($conf->global->BUYPRICEHISTORY_PRODUCTFOURNISSEURPRICE_ADDON)) {
761			$conf->global->BUYPRICEHISTORY_PRODUCTFOURNISSEURPRICE_ADDON = 'mod_productfournisseurprice_standard';
762		}
763
764		if (!empty($conf->global->BUYPRICEHISTORY_PRODUCTFOURNISSEURPRICE_ADDON)) {
765			$mybool = false;
766
767			$file = $conf->global->BUYPRICEHISTORY_PRODUCTFOURNISSEURPRICE_ADDON.".php";
768			$classname = $conf->global->BUYPRICEHISTORY_PRODUCTFOURNISSEURPRICE_ADDON;
769
770			// Include file with class
771			$dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
772			foreach ($dirmodels as $reldir) {
773				$dir = dol_buildpath($reldir."core/modules/buypricehistory/");
774
775				// Load file with numbering class (if found)
776				$mybool |= @include_once $dir.$file;
777			}
778
779			if ($mybool === false) {
780				dol_print_error('', "Failed to include file ".$file);
781				return '';
782			}
783
784			if (class_exists($classname)) {
785				$obj = new $classname();
786				$numref = $obj->getNextValue($this);
787
788				if ($numref != '' && $numref != '-1') {
789					return $numref;
790				} else {
791					$this->error = $obj->error;
792					//dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error);
793					return "";
794				}
795			} else {
796				print $langs->trans("Error")." ".$langs->trans("ClassNotFound").' '.$classname;
797				return "";
798			}
799		} else {
800			print $langs->trans("ErrorNumberingModuleNotSetup", $this->element);
801			return "";
802		}
803	}
804
805	/**
806	 *  Create a document onto disk according to template module.
807	 *
808	 *  @param	    string		$modele			Force template to use ('' to not force)
809	 *  @param		Translate	$outputlangs	objet lang a utiliser pour traduction
810	 *  @param      int			$hidedetails    Hide details of lines
811	 *  @param      int			$hidedesc       Hide description
812	 *  @param      int			$hideref        Hide ref
813	 *  @param      null|array  $moreparams     Array to provide more information
814	 *  @return     int         				0 if KO, 1 if OK
815	 */
816	public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
817	{
818		global $conf, $langs;
819
820		$result = 0;
821		$includedocgeneration = 0;
822
823		$langs->load("buypricehistory@buypricehistory");
824
825		if (!dol_strlen($modele)) {
826			$modele = 'standard_productfournisseurprice';
827
828			if (!empty($this->model_pdf)) {
829				$modele = $this->model_pdf;
830			} elseif (!empty($conf->global->PRODUCTFOURNISSEURPRICE_ADDON_PDF)) {
831				$modele = $conf->global->PRODUCTFOURNISSEURPRICE_ADDON_PDF;
832			}
833		}
834
835		$modelpath = "core/modules/buypricehistory/doc/";
836
837		if ($includedocgeneration && !empty($modele)) {
838			$result = $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
839		}
840
841		return $result;
842	}
843}
844