Passed
Branch develop (356b3a)
by
unknown
98:06
created

CommandeFournisseur::getTooltipContentArray()   C

Complexity

Conditions 10
Paths 257

Size

Total Lines 35
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 10
eloc 22
nc 257
nop 1
dl 0
loc 35
rs 6.1208
c 1
b 0
f 1

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/* Copyright (C) 2003-2006	Rodolphe Quiedeville	<[email protected]>
3
 * Copyright (C) 2004-2017	Laurent Destailleur		<[email protected]>
4
 * Copyright (C) 2005-2012	Regis Houssin			<[email protected]>
5
 * Copyright (C) 2007		Franky Van Liedekerke	<[email protected]>
6
 * Copyright (C) 2010-2020	Juanjo Menent			<[email protected]>
7
 * Copyright (C) 2010-2018	Philippe Grand			<[email protected]>
8
 * Copyright (C) 2012-2015  Marcos García           <[email protected]>
9
 * Copyright (C) 2013       Florian Henry		  	<[email protected]>
10
 * Copyright (C) 2013       Cédric Salvador         <[email protected]>
11
 * Copyright (C) 2018       Nicolas ZABOURI			<[email protected]>
12
 * Copyright (C) 2018-2023  Frédéric France         <[email protected]>
13
 * Copyright (C) 2018-2022  Ferran Marcet         	<[email protected]>
14
 * Copyright (C) 2021       Josep Lluís Amador      <[email protected]>
15
 * Copyright (C) 2022       Gauthier VERDOL         <[email protected]>
16
 *
17
 * This program is free software; you can redistribute it and/or modify
18
 * it under the terms of the GNU General Public License as published by
19
 * the Free Software Foundation; either version 3 of the License, or
20
 * (at your option) any later version.
21
 *
22
 * This program is distributed in the hope that it will be useful,
23
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25
 * GNU General Public License for more details.
26
 *
27
 * You should have received a copy of the GNU General Public License
28
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
29
 */
30
31
/**
32
 *	\file       htdocs/fourn/class/fournisseur.commande.class.php
33
 *	\ingroup    fournisseur,commande
34
 *	\brief      File of class to manage suppliers orders
35
 */
36
37
require_once DOL_DOCUMENT_ROOT.'/core/class/commonorder.class.php';
38
require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
39
require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php';
40
if (isModEnabled('productbatch')) {
41
	require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
42
}
43
44
45
/**
46
 *	Class to manage predefined suppliers products
47
 */
48
class CommandeFournisseur extends CommonOrder
49
{
50
	/**
51
	 * @var string ID to identify managed object
52
	 */
53
	public $element = 'order_supplier';
54
55
	/**
56
	 * @var string Name of table without prefix where object is stored
57
	 */
58
	public $table_element = 'commande_fournisseur';
59
60
	/**
61
	 * @var string    Name of subtable line
62
	 */
63
	public $table_element_line = 'commande_fournisseurdet';
64
65
	/**
66
	 * @var string Field with ID of parent key if this field has a parent
67
	 */
68
	public $fk_element = 'fk_commande';
69
70
	/**
71
	 * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
72
	 */
73
	public $picto = 'supplier_order';
74
75
	/**
76
	 * 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
77
	 * @var int
78
	 */
79
	public $ismultientitymanaged = 1;
80
81
	/**
82
	 * 0=Default, 1=View may be restricted to sales representative only if no permission to see all or to company of external user if external user
83
	 * @var integer
84
	 */
85
	public $restrictiononfksoc = 1;
86
87
	/**
88
	 * {@inheritdoc}
89
	 */
90
	protected $table_ref_field = 'ref';
91
92
	/**
93
	 * @var int ID
94
	 */
95
	public $id;
96
97
	/**
98
	 * Supplier order reference
99
	 * @var string
100
	 */
101
	public $ref;
102
103
	/**
104
	 * @var string ref supplier
105
	 */
106
	public $ref_supplier;
107
108
	/**
109
	 * @var string ref supplier
110
	 * @deprecated
111
	 * @see $ref_supplier
112
	 */
113
	public $ref_fourn;
114
115
	public $brouillon;
116
	/**
117
	 * @var int
118
	 */
119
	public $statut; // 0=Draft -> 1=Validated -> 2=Approved -> 3=Ordered/Process runing -> 4=Received partially -> 5=Received totally -> (reopen) 4=Received partially
120
	//                                                                                          -> 7=Canceled/Never received -> (reopen) 3=Process runing
121
	//									                            -> 6=Canceled -> (reopen) 2=Approved
122
	//  		                                      -> 9=Refused  -> (reopen) 1=Validated
123
	//  Note: billed or not is on another field "billed"
124
125
	/**
126
	 * @var array List of status
127
	 */
128
	public $statuts;
129
130
	/**
131
	 * @var array List of status short
132
	 */
133
	public $statuts_short;
134
135
	public $billed;
136
137
	public $socid;
138
	public $fourn_id;
139
	public $date;
140
	public $date_creation;
141
	public $date_valid;
142
	public $date_approve;
143
	public $date_approve2; // Used when SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED is set
144
	public $date_commande;
145
146
	/**
147
	 * @var int	Date expected for delivery
148
	 * @deprecated		See delivery_date
149
	 */
150
	public $date_livraison;
151
152
	/**
153
	 *  @var int Date expected for delivery
154
	 */
155
	public $delivery_date;
156
157
	public $total_ht;
158
	public $total_tva;
159
	public $total_localtax1; // Total Local tax 1
160
	public $total_localtax2; // Total Local tax 2
161
	public $total_ttc;
162
	public $source;
163
164
	/**
165
	 * @var int ID
166
	 */
167
	public $fk_project;
168
169
	public $cond_reglement_id;
170
	public $cond_reglement_code;
171
	public $cond_reglement_label;	// Label
172
	public $cond_reglement_doc;		// Label on documents
173
174
	/**
175
	 * @var int ID
176
	 */
177
	public $fk_account;
178
179
	public $mode_reglement_id;
180
	public $mode_reglement_code;
181
	public $user_author_id;
182
	public $user_valid_id;
183
	public $user_approve_id;
184
	public $user_approve_id2; // Used when SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED is set
185
186
	public $refuse_note;
187
188
	public $extraparams = array();
189
190
	/**
191
	 * @var CommandeFournisseurLigne[]
192
	 */
193
	public $lines = array();
194
195
	/**
196
	 * @var CommandeFournisseurLigne
0 ignored issues
show
Bug introduced by
The type CommandeFournisseurLigne was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
197
	 */
198
	public $line;
199
200
	// Add for supplier_proposal
201
	public $origin;
202
	public $origin_id;
203
	public $linked_objects = array();
204
205
	// Multicurrency
206
	/**
207
	 * @var int ID
208
	 */
209
	public $fk_multicurrency;
210
211
	public $multicurrency_code;
212
	public $multicurrency_tx;
213
	public $multicurrency_total_ht;
214
	public $multicurrency_total_tva;
215
	public $multicurrency_total_ttc;
216
217
218
	/**
219
	 *  'type' field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]', 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter[:Sortfield]]]]', 'varchar(x)', 'double(24,8)', 'real', 'price', 'text', 'text:none', 'html', 'date', 'datetime', 'timestamp', 'duration', 'mail', 'phone', 'url', 'password')
220
	 *         Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)"
221
	 *  'label' the translation key.
222
	 *  'picto' is code of a picto to show before value in forms
223
	 *  'enabled' is a condition when the field must be managed (Example: 1 or '$conf->global->MY_SETUP_PARAM' or 'isModEnabled("multicurrency")' ...)
224
	 *  'position' is the sort order of field.
225
	 *  'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
226
	 *  '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)
227
	 *  'noteditable' says if field is not editable (1 or 0)
228
	 *  '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.
229
	 *  'index' if we want an index in database.
230
	 *  'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommanded to name the field fk_...).
231
	 *  'searchall' is 1 if we want to search in this field when making a search from the quick search button.
232
	 *  'isameasure' must be set to 1 or 2 if field can be used for measure. Field type must be summable like integer or double(24,8). Use 1 in most cases, or 2 if you don't want to see the column total into list (for example for percentage)
233
	 *  '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: 'css'=>'minwidth300 maxwidth500 widthcentpercentminusx', 'cssview'=>'wordbreak', 'csslist'=>'tdoverflowmax200'
234
	 *  'help' is a 'TranslationString' to use to show a tooltip on field. You can also use 'TranslationString:keyfortooltiponlick' for a tooltip on click.
235
	 *  'showoncombobox' if value of the field must be visible into the label of the combobox that list record
236
	 *  '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.
237
	 *  'arrayofkeyval' to set a list of values if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel"). Note that type can be 'integer' or 'varchar'
238
	 *  'autofocusoncreate' to have field having the focus on a create form. Only 1 field should have this property set to 1.
239
	 *  'comment' is not used. You can store here any text of your choice. It is not used by application.
240
	 *	'validate' is 1 if need to validate with $this->validateField()
241
	 *  'copytoclipboard' is 1 or 2 to allow to add a picto to copy value into clipboard (1=picto after label, 2=picto after value)
242
	 *
243
	 *  Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
244
	 */
245
	public $fields = array(
246
		'rowid' =>array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>0, 'notnull'=>1, 'position'=>10),
247
		'ref' =>array('type'=>'varchar(255)', 'label'=>'Ref', 'enabled'=>1, 'visible'=>1, 'showoncombobox'=>1, 'position'=>25, 'searchall'=>1),
248
		'ref_ext' =>array('type'=>'varchar(255)', 'label'=>'Ref ext', 'enabled'=>1, 'visible'=>0, 'position'=>35),
249
		'ref_supplier' =>array('type'=>'varchar(255)', 'label'=>'RefOrderSupplierShort', 'enabled'=>1, 'visible'=>1, 'position'=>40, 'searchall'=>1),
250
		'fk_projet' =>array('type'=>'integer:Project:projet/class/project.class.php:1:fk_statut=1', 'label'=>'Project', 'enabled'=>"isModEnabled('project')", 'visible'=>-1, 'position'=>45),
251
		'date_valid' =>array('type'=>'datetime', 'label'=>'DateValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>60),
252
		'date_approve' =>array('type'=>'datetime', 'label'=>'DateApprove', 'enabled'=>1, 'visible'=>-1, 'position'=>62),
253
		'date_approve2' =>array('type'=>'datetime', 'label'=>'DateApprove2', 'enabled'=>1, 'visible'=>3, 'position'=>64),
254
		'date_commande' =>array('type'=>'date', 'label'=>'OrderDateShort', 'enabled'=>1, 'visible'=>1, 'position'=>70),
255
		'date_livraison' =>array('type'=>'datetime', 'label'=>'DeliveryDate', 'enabled'=>'empty($conf->global->ORDER_DISABLE_DELIVERY_DATE)', 'visible'=>1, 'position'=>74),
256
		'fk_user_author' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserAuthor', 'enabled'=>1, 'visible'=>3, 'position'=>75),
257
		'fk_user_modif' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserModif', 'enabled'=>1, 'visible'=>3, 'notnull'=>-1, 'position'=>80),
258
		'fk_user_valid' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserValidation', 'enabled'=>1, 'visible'=>3, 'position'=>85),
259
		'fk_user_approve' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserApproval', 'enabled'=>1, 'visible'=>3, 'position'=>90),
260
		'fk_user_approve2' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserApproval2', 'enabled'=>1, 'visible'=>3, 'position'=>95),
261
		'source' =>array('type'=>'smallint(6)', 'label'=>'Source', 'enabled'=>1, 'visible'=>3, 'notnull'=>1, 'position'=>100),
262
		'billed' =>array('type'=>'smallint(6)', 'label'=>'Billed', 'enabled'=>1, 'visible'=>1, 'position'=>110),
263
		'total_tva' =>array('type'=>'double(24,8)', 'label'=>'Tva', 'enabled'=>1, 'visible'=>1, 'position'=>130, 'isameasure'=>1),
264
		'localtax1' =>array('type'=>'double(24,8)', 'label'=>'Localtax1', 'enabled'=>1, 'visible'=>3, 'position'=>135, 'isameasure'=>1),
265
		'localtax2' =>array('type'=>'double(24,8)', 'label'=>'Localtax2', 'enabled'=>1, 'visible'=>3, 'position'=>140, 'isameasure'=>1),
266
		'total_ht' =>array('type'=>'double(24,8)', 'label'=>'TotalHT', 'enabled'=>1, 'visible'=>1, 'position'=>145, 'isameasure'=>1),
267
		'total_ttc' =>array('type'=>'double(24,8)', 'label'=>'TotalTTC', 'enabled'=>1, 'visible'=>-1, 'position'=>150, 'isameasure'=>1),
268
		'note_public' =>array('type'=>'html', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>155, 'searchall'=>1),
269
		'note_private' =>array('type'=>'html', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>160, 'searchall'=>1),
270
		'model_pdf' =>array('type'=>'varchar(255)', 'label'=>'ModelPDF', 'enabled'=>1, 'visible'=>0, 'position'=>165),
271
		'fk_input_method' =>array('type'=>'integer', 'label'=>'OrderMode', 'enabled'=>1, 'visible'=>3, 'position'=>170),
272
		'fk_cond_reglement' =>array('type'=>'integer', 'label'=>'PaymentTerm', 'enabled'=>1, 'visible'=>3, 'position'=>175),
273
		'fk_mode_reglement' =>array('type'=>'integer', 'label'=>'PaymentMode', 'enabled'=>1, 'visible'=>3, 'position'=>180),
274
		'extraparams' =>array('type'=>'varchar(255)', 'label'=>'Extraparams', 'enabled'=>1, 'visible'=>0, 'position'=>190),
275
		'fk_account' =>array('type'=>'integer', 'label'=>'BankAccount', 'enabled'=>'$conf->banque->enabled', 'visible'=>3, 'position'=>200),
276
		'fk_incoterms' =>array('type'=>'integer', 'label'=>'IncotermCode', 'enabled'=>1, 'visible'=>3, 'position'=>205),
277
		'location_incoterms' =>array('type'=>'varchar(255)', 'label'=>'IncotermLocation', 'enabled'=>1, 'visible'=>3, 'position'=>210),
278
		'fk_multicurrency' =>array('type'=>'integer', 'label'=>'Fk multicurrency', 'enabled'=>1, 'visible'=>0, 'position'=>215),
279
		'multicurrency_code' =>array('type'=>'varchar(255)', 'label'=>'Currency', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>220),
280
		'multicurrency_tx' =>array('type'=>'double(24,8)', 'label'=>'CurrencyRate', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>225),
281
		'multicurrency_total_ht' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountHT', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>230),
282
		'multicurrency_total_tva' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountVAT', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>235),
283
		'multicurrency_total_ttc' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountTTC', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>240),
284
		'date_creation' =>array('type'=>'datetime', 'label'=>'Date creation', 'enabled'=>1, 'visible'=>-1, 'position'=>500),
285
		'fk_soc' =>array('type'=>'integer:Societe:societe/class/societe.class.php', 'label'=>'ThirdParty', 'enabled'=>'isModEnabled("societe")', 'visible'=>1, 'notnull'=>1, 'position'=>46),
286
		'entity' =>array('type'=>'integer', 'label'=>'Entity', 'default'=>1, 'enabled'=>1, 'visible'=>0, 'notnull'=>1, 'position'=>1000, 'index'=>1),
287
		'tms'=>array('type'=>'datetime', 'label'=>"DateModificationShort", 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>501),
288
		'last_main_doc' =>array('type'=>'varchar(255)', 'label'=>'LastMainDoc', 'enabled'=>1, 'visible'=>0, 'position'=>700),
289
		'fk_statut' =>array('type'=>'smallint(6)', 'label'=>'Status', 'enabled'=>1, 'visible'=>1, 'position'=>701),
290
		'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>0, 'position'=>900),
291
	);
292
293
294
	/**
295
	 * Draft status
296
	 */
297
	const STATUS_DRAFT = 0;
298
299
	/**
300
	 * Validated status
301
	 */
302
	const STATUS_VALIDATED = 1;
303
304
	/**
305
	 * Accepted
306
	 */
307
	const STATUS_ACCEPTED = 2;
308
309
	/**
310
	 * Order sent, shipment on process
311
	 */
312
	const STATUS_ORDERSENT = 3;
313
314
	/**
315
	 * Received partially
316
	 */
317
	const STATUS_RECEIVED_PARTIALLY = 4;
318
319
	/**
320
	 * Received completely
321
	 */
322
	const STATUS_RECEIVED_COMPLETELY = 5;
323
324
	/**
325
	 * Order canceled
326
	 */
327
	const STATUS_CANCELED = 6;
328
329
	/**
330
	 * Order canceled/never received
331
	 */
332
	const STATUS_CANCELED_AFTER_ORDER = 7;
333
334
	/**
335
	 * Refused
336
	 */
337
	const STATUS_REFUSED = 9;
338
339
340
	/**
341
	 * The constant used into source field to track the order was generated by the replenishement feature
342
	 */
343
	const SOURCE_ID_REPLENISHMENT = 42;
344
345
346
347
	/**
348
	 * 	Constructor
349
	 *
350
	 *  @param      DoliDB		$db      Database handler
351
	 */
352
	public function __construct($db)
353
	{
354
		$this->db = $db;
355
	}
356
357
358
	/**
359
	 *	Get object and lines from database
360
	 *
361
	 * 	@param	int		$id			Id of order to load
362
	 * 	@param	string	$ref		Ref of object
363
	 *	@return int 		        >0 if OK, <0 if KO, 0 if not found
364
	 */
365
	public function fetch($id, $ref = '')
366
	{
367
		global $conf;
368
369
		// Check parameters
370
		if (empty($id) && empty($ref)) {
371
			return -1;
372
		}
373
374
		$sql = "SELECT c.rowid, c.entity, c.ref, ref_supplier, c.fk_soc, c.fk_statut, c.amount_ht, c.total_ht, c.total_ttc, c.total_tva,";
375
		$sql .= " c.localtax1, c.localtax2, ";
376
		$sql .= " c.date_creation, c.date_valid, c.date_approve, c.date_approve2,";
377
		$sql .= " c.fk_user_author, c.fk_user_valid, c.fk_user_approve, c.fk_user_approve2,";
378
		$sql .= " c.date_commande as date_commande, c.date_livraison as delivery_date, c.fk_cond_reglement, c.fk_mode_reglement, c.fk_projet as fk_project, c.remise_percent, c.source, c.fk_input_method,";
379
		$sql .= " c.fk_account,";
380
		$sql .= " c.note_private, c.note_public, c.model_pdf, c.extraparams, c.billed,";
381
		$sql .= " c.fk_multicurrency, c.multicurrency_code, c.multicurrency_tx, c.multicurrency_total_ht, c.multicurrency_total_tva, c.multicurrency_total_ttc,";
382
		$sql .= " cm.libelle as methode_commande,";
383
		$sql .= " cr.code as cond_reglement_code, cr.libelle as cond_reglement_label, cr.libelle_facture as cond_reglement_doc,";
384
		$sql .= " p.code as mode_reglement_code, p.libelle as mode_reglement_libelle";
385
		$sql .= ', c.fk_incoterms, c.location_incoterms';
386
		$sql .= ', i.libelle as label_incoterms';
387
		$sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseur as c";
388
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_payment_term as cr ON c.fk_cond_reglement = cr.rowid";
389
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_paiement as p ON c.fk_mode_reglement = p.id";
390
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_input_method as cm ON cm.rowid = c.fk_input_method";
391
		$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON c.fk_incoterms = i.rowid';
392
393
		if (empty($id)) {
394
			$sql .= " WHERE c.entity IN (".getEntity('supplier_order').")";
395
		} else {
396
			$sql .= " WHERE c.rowid=".((int) $id);
397
		}
398
399
		if ($ref) {
400
			$sql .= " AND c.ref='".$this->db->escape($ref)."'";
401
		}
402
403
		dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
404
		$resql = $this->db->query($sql);
405
		if ($resql) {
406
			$obj = $this->db->fetch_object($resql);
407
			if (!$obj) {
408
				$this->error = 'Bill with id '.$id.' not found';
409
				dol_syslog(get_class($this).'::fetch '.$this->error);
410
				return 0;
411
			}
412
413
			$this->id = $obj->rowid;
414
			$this->entity = $obj->entity;
415
416
			$this->ref = $obj->ref;
417
			$this->ref_supplier = $obj->ref_supplier;
418
			$this->socid = $obj->fk_soc;
419
			$this->fourn_id = $obj->fk_soc;
420
			$this->statut = $obj->fk_statut;
421
			$this->status = $obj->fk_statut;
422
			$this->billed = $obj->billed;
423
			$this->user_author_id = $obj->fk_user_author;
424
			$this->user_valid_id = $obj->fk_user_valid;
425
			$this->user_approve_id = $obj->fk_user_approve;
426
			$this->user_approve_id2 = $obj->fk_user_approve2;
427
			$this->total_ht				= $obj->total_ht;
428
			$this->total_tva			= $obj->total_tva;
429
			$this->total_localtax1		= $obj->localtax1;
430
			$this->total_localtax2		= $obj->localtax2;
431
			$this->total_ttc			= $obj->total_ttc;
432
			$this->date_creation = $this->db->jdate($obj->date_creation);
433
			$this->date_valid = $this->db->jdate($obj->date_valid);
434
			$this->date_approve			= $this->db->jdate($obj->date_approve);
435
			$this->date_approve2		= $this->db->jdate($obj->date_approve2);
436
			$this->date_commande		= $this->db->jdate($obj->date_commande); // date we make the order to supplier
437
			if (isset($obj->date_commande)) {
438
				$this->date = $this->date_commande;
439
			} else {
440
				$this->date = $this->date_creation;
441
			}
442
			$this->date_livraison = $this->db->jdate($obj->delivery_date); // deprecated
443
			$this->delivery_date = $this->db->jdate($obj->delivery_date);
444
			$this->remise_percent = $obj->remise_percent;
445
			$this->methode_commande_id = $obj->fk_input_method;
446
			$this->methode_commande = $obj->methode_commande;
447
448
			$this->source = $obj->source;
449
			$this->fk_project = $obj->fk_project;
450
			$this->cond_reglement_id = $obj->fk_cond_reglement;
451
			$this->cond_reglement_code = $obj->cond_reglement_code;
452
			$this->cond_reglement = $obj->cond_reglement_label;			// deprecated
453
			$this->cond_reglement_label = $obj->cond_reglement_label;
454
			$this->cond_reglement_doc = $obj->cond_reglement_doc;
455
			$this->fk_account = $obj->fk_account;
456
			$this->mode_reglement_id = $obj->fk_mode_reglement;
457
			$this->mode_reglement_code = $obj->mode_reglement_code;
458
			$this->mode_reglement = $obj->mode_reglement_libelle;
459
			$this->note = $obj->note_private; // deprecated
460
			$this->note_private = $obj->note_private;
461
			$this->note_public = $obj->note_public;
462
			$this->model_pdf = $obj->model_pdf;
463
			$this->modelpdf = $obj->model_pdf; // deprecated
464
465
			//Incoterms
466
			$this->fk_incoterms = $obj->fk_incoterms;
467
			$this->location_incoterms = $obj->location_incoterms;
468
			$this->label_incoterms = $obj->label_incoterms;
469
470
			// Multicurrency
471
			$this->fk_multicurrency 		= $obj->fk_multicurrency;
472
			$this->multicurrency_code = $obj->multicurrency_code;
473
			$this->multicurrency_tx 		= $obj->multicurrency_tx;
474
			$this->multicurrency_total_ht = $obj->multicurrency_total_ht;
475
			$this->multicurrency_total_tva 	= $obj->multicurrency_total_tva;
476
			$this->multicurrency_total_ttc 	= $obj->multicurrency_total_ttc;
477
478
			$this->extraparams = (array) json_decode($obj->extraparams, true);
479
480
			$this->db->free($resql);
481
482
			// Retrieve all extrafield
483
			// fetch optionals attributes and labels
484
			$this->fetch_optionals();
485
486
			if ($this->statut == 0) {
487
				$this->brouillon = 1;
488
			}
489
490
			/*
491
			 * Lines
492
			 */
493
			$result = $this->fetch_lines();
494
495
			if ($result < 0) {
496
				return -1;
497
			} else {
498
				return 1;
499
			}
500
		} else {
501
			$this->error = $this->db->error()." sql=".$sql;
502
			return -1;
503
		}
504
	}
505
506
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
507
	/**
508
	 * Load array lines
509
	 *
510
	 * @param		int		$only_product	Return only physical products
511
	 * @return		int						<0 if KO, >0 if OK
512
	 */
513
	public function fetch_lines($only_product = 0)
514
	{
515
		global $conf;
516
		// phpcs:enable
517
518
		$this->lines = array();
519
520
		$sql = "SELECT l.rowid, l.ref as ref_supplier, l.fk_product, l.product_type, l.label, l.description, l.qty,";
521
		$sql .= " l.vat_src_code, l.tva_tx, l.remise_percent, l.subprice,";
522
		$sql .= " l.localtax1_tx, l. localtax2_tx, l.localtax1_type, l. localtax2_type, l.total_localtax1, l.total_localtax2,";
523
		$sql .= " l.total_ht, l.total_tva, l.total_ttc, l.special_code, l.fk_parent_line, l.rang,";
524
		$sql .= " p.rowid as product_id, p.ref as product_ref, p.label as product_label, p.description as product_desc, p.tobatch as product_tobatch, p.barcode as product_barcode,";
525
		$sql .= " l.fk_unit,";
526
		$sql .= " l.date_start, l.date_end,";
527
		$sql .= ' l.fk_multicurrency, l.multicurrency_code, l.multicurrency_subprice, l.multicurrency_total_ht, l.multicurrency_total_tva, l.multicurrency_total_ttc';
528
		$sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseurdet as l";
529
		$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON l.fk_product = p.rowid';
530
		$sql .= " WHERE l.fk_commande = ".((int) $this->id);
531
		if ($only_product) {
532
			$sql .= ' AND p.fk_product_type = 0';
533
		}
534
		$sql .= " ORDER BY l.rang, l.rowid";
535
		//print $sql;
536
537
		dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
538
539
		$result = $this->db->query($sql);
540
		if ($result) {
541
			$num = $this->db->num_rows($result);
542
			$i = 0;
543
544
			while ($i < $num) {
545
				$objp = $this->db->fetch_object($result);
546
547
				$line = new CommandeFournisseurLigne($this->db);
548
549
				$line->id                  = $objp->rowid;
550
				$line->desc                = $objp->description;
551
				$line->description         = $objp->description;
552
				$line->qty                 = $objp->qty;
553
				$line->tva_tx              = $objp->tva_tx;
554
				$line->localtax1_tx		   = $objp->localtax1_tx;
555
				$line->localtax2_tx		   = $objp->localtax2_tx;
556
				$line->localtax1_type	   = $objp->localtax1_type;
557
				$line->localtax2_type	   = $objp->localtax2_type;
558
				$line->subprice            = $objp->subprice;
559
				$line->pu_ht = $objp->subprice;
560
				$line->remise_percent      = $objp->remise_percent;
561
562
				$line->vat_src_code        = $objp->vat_src_code;
563
				$line->total_ht            = $objp->total_ht;
564
				$line->total_tva           = $objp->total_tva;
565
				$line->total_localtax1	   = $objp->total_localtax1;
566
				$line->total_localtax2	   = $objp->total_localtax2;
567
				$line->total_ttc           = $objp->total_ttc;
568
				$line->product_type        = $objp->product_type;
569
570
				$line->fk_product          = $objp->fk_product;
571
572
				$line->libelle             = $objp->product_label; // deprecated
573
				$line->product_label       = $objp->product_label;
574
				$line->product_desc        = $objp->product_desc;
575
				$line->product_tobatch     = $objp->product_tobatch;
576
				$line->product_barcode     = $objp->product_barcode;
577
578
				$line->ref                 = $objp->product_ref; // Ref of product
579
				$line->product_ref         = $objp->product_ref; // Ref of product
580
				$line->ref_fourn           = $objp->ref_supplier; // The supplier ref of price when product was added. May have change since
581
				$line->ref_supplier        = $objp->ref_supplier; // The supplier ref of price when product was added. May have change since
582
583
				if (!empty($conf->global->PRODUCT_USE_SUPPLIER_PACKAGING)) {
584
					// TODO We should not fetch this properties into the fetch_lines. This is NOT properties of a line.
585
					// Move this into another method and call it when required.
586
587
					// Take better packaging for $objp->qty (first supplier ref quantity <= $objp->qty)
588
					$sqlsearchpackage = 'SELECT rowid, packaging FROM '.MAIN_DB_PREFIX."product_fournisseur_price";
589
					$sqlsearchpackage .= ' WHERE entity IN ('.getEntity('product_fournisseur_price').")";
590
					$sqlsearchpackage .= " AND fk_product = ".((int) $objp->fk_product);
591
					$sqlsearchpackage .= " AND ref_fourn = '".$this->db->escape($objp->ref_supplier)."'";
592
					$sqlsearchpackage .= " AND quantity <= ".((float) $objp->qty);	// required to be qualified
593
					$sqlsearchpackage .= " AND (packaging IS NULL OR packaging = 0 OR packaging <= ".((float) $objp->qty).")";	// required to be qualified
594
					$sqlsearchpackage .= " AND fk_soc = ".((int) $this->socid);
595
					$sqlsearchpackage .= " ORDER BY packaging ASC";		// Take the smaller package first
596
					$sqlsearchpackage .= " LIMIT 1";
597
598
					$resqlsearchpackage = $this->db->query($sqlsearchpackage);
599
					if ($resqlsearchpackage) {
600
						$objsearchpackage = $this->db->fetch_object($resqlsearchpackage);
601
						if ($objsearchpackage) {
602
							$line->fk_fournprice = $objsearchpackage->rowid;
603
							$line->packaging     = $objsearchpackage->packaging;
604
						}
605
					} else {
606
						$this->error = $this->db->lasterror();
607
						return -1;
608
					}
609
				}
610
611
				$line->date_start          = $this->db->jdate($objp->date_start);
612
				$line->date_end            = $this->db->jdate($objp->date_end);
613
				$line->fk_unit             = $objp->fk_unit;
614
615
				// Multicurrency
616
				$line->fk_multicurrency = $objp->fk_multicurrency;
617
				$line->multicurrency_code = $objp->multicurrency_code;
618
				$line->multicurrency_subprice = $objp->multicurrency_subprice;
619
				$line->multicurrency_total_ht = $objp->multicurrency_total_ht;
620
				$line->multicurrency_total_tva = $objp->multicurrency_total_tva;
621
				$line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
622
623
				$line->special_code        = $objp->special_code;
624
				$line->fk_parent_line      = $objp->fk_parent_line;
625
626
				$line->rang                = $objp->rang;
627
628
				// Retrieve all extrafield
629
				// fetch optionals attributes and labels
630
				$line->fetch_optionals();
631
632
				$this->lines[$i] = $line;
633
634
				$i++;
635
			}
636
			$this->db->free($result);
637
638
			return $num;
639
		} else {
640
			$this->error = $this->db->error()." sql=".$sql;
641
			return -1;
642
		}
643
	}
644
645
	/**
646
	 *	Validate an order
647
	 *
648
	 *	@param	User	$user			Validator User
649
	 *	@param	int		$idwarehouse	Id of warehouse to use for stock decrease
650
	 *  @param	int		$notrigger		1=Does not execute triggers, 0= execute triggers
651
	 *	@return	int						<0 if KO, >0 if OK
652
	 */
653
	public function valid($user, $idwarehouse = 0, $notrigger = 0)
654
	{
655
		global $langs, $conf;
656
		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
657
658
		$error = 0;
659
660
		dol_syslog(get_class($this)."::valid");
661
		$result = 0;
662
		if ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && (!empty($user->rights->fournisseur->commande->creer) || !empty($user->rights->supplier_order->creer)))
663
			|| (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && !empty($user->rights->fournisseur->supplier_order_advance->validate))) {
664
				$this->db->begin();
665
666
				// Definition of supplier order numbering model name
667
				$soc = new Societe($this->db);
668
				$soc->fetch($this->fourn_id);
669
670
				// Check if object has a temporary ref
671
			if (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
672
				$num = $this->getNextNumRef($soc);
673
			} else {
674
				$num = $this->ref;
675
			}
676
				$this->newref = dol_sanitizeFileName($num);
677
678
				$sql = 'UPDATE '.MAIN_DB_PREFIX."commande_fournisseur";
679
				$sql .= " SET ref='".$this->db->escape($num)."',";
680
				$sql .= " fk_statut = ".self::STATUS_VALIDATED.",";
681
				$sql .= " date_valid='".$this->db->idate(dol_now())."',";
682
				$sql .= " fk_user_valid = ".((int) $user->id);
683
				$sql .= " WHERE rowid = ".((int) $this->id);
684
				$sql .= " AND fk_statut = ".self::STATUS_DRAFT;
685
686
				$resql = $this->db->query($sql);
687
			if (!$resql) {
688
				dol_print_error($this->db);
689
				$error++;
690
			}
691
692
			if (!$error && !$notrigger) {
693
				// Call trigger
694
				$result = $this->call_trigger('ORDER_SUPPLIER_VALIDATE', $user);
695
				if ($result < 0) {
696
					$error++;
697
				}
698
				// End call triggers
699
			}
700
701
			if (!$error) {
702
				$this->oldref = $this->ref;
703
704
				// Rename directory if dir was a temporary ref
705
				if (preg_match('/^[\(]?PROV/i', $this->ref)) {
706
					// Now we rename also files into index
707
					$sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'fournisseur/commande/".$this->db->escape($this->newref)."'";
708
					$sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'fournisseur/commande/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
709
					$resql = $this->db->query($sql);
710
					if (!$resql) {
711
						$error++; $this->error = $this->db->lasterror();
712
					}
713
714
					// We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
715
					$oldref = dol_sanitizeFileName($this->ref);
716
					$newref = dol_sanitizeFileName($num);
717
					$dirsource = $conf->fournisseur->commande->dir_output.'/'.$oldref;
718
					$dirdest = $conf->fournisseur->commande->dir_output.'/'.$newref;
719
					if (!$error && file_exists($dirsource)) {
720
						dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
721
722
						if (@rename($dirsource, $dirdest)) {
723
							dol_syslog("Rename ok");
724
							// Rename docs starting with $oldref with $newref
725
							$listoffiles = dol_dir_list($conf->fournisseur->commande->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
726
							foreach ($listoffiles as $fileentry) {
727
								$dirsource = $fileentry['name'];
728
								$dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
729
								$dirsource = $fileentry['path'].'/'.$dirsource;
730
								$dirdest = $fileentry['path'].'/'.$dirdest;
731
								@rename($dirsource, $dirdest);
732
							}
733
						}
734
					}
735
				}
736
			}
737
738
			if (!$error) {
739
				$result = 1;
740
				$this->statut = self::STATUS_VALIDATED;
741
				$this->ref = $num;
742
			}
743
744
			if (!$error) {
745
				$this->db->commit();
746
				return 1;
747
			} else {
748
				$this->db->rollback();
749
				return -1;
750
			}
751
		} else {
752
			$this->error = 'NotAuthorized';
753
			dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
754
			return -1;
755
		}
756
	}
757
758
	/**
759
	 *  Return label of the status of object
760
	 *
761
	 *  @param      int		$mode			0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=short label + picto
762
	 *  @return 	string        			Label
763
	 */
764
	public function getLibStatut($mode = 0)
765
	{
766
		return $this->LibStatut($this->statut, $mode, $this->billed);
767
	}
768
769
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
770
	/**
771
	 *  Return label of a status
772
	 *
773
	 * 	@param  int		$status		Id statut
774
	 *  @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
775
	 *  @param  int     $billed     1=Billed
776
	 *  @return string				Label of status
777
	 */
778
	public function LibStatut($status, $mode = 0, $billed = 0)
779
	{
780
		// phpcs:enable
781
		global $conf, $langs, $hookmanager;
782
783
		if (empty($this->statuts) || empty($this->statuts_short)) {
784
			$langs->load('orders');
785
786
			$this->statuts[0] = 'StatusSupplierOrderDraft';
787
			$this->statuts[1] = 'StatusSupplierOrderValidated';
788
			$this->statuts[2] = 'StatusSupplierOrderApproved';
789
			if (empty($conf->global->SUPPLIER_ORDER_USE_DISPATCH_STATUS)) {
790
				$this->statuts[3] = 'StatusSupplierOrderOnProcess';
791
			} else {
792
				$this->statuts[3] = 'StatusSupplierOrderOnProcessWithValidation';
793
			}
794
			$this->statuts[4] = 'StatusSupplierOrderReceivedPartially';
795
			$this->statuts[5] = 'StatusSupplierOrderReceivedAll';
796
			$this->statuts[6] = 'StatusSupplierOrderCanceled'; // Approved->Canceled
797
			$this->statuts[7] = 'StatusSupplierOrderCanceled'; // Process running->canceled
798
			$this->statuts[9] = 'StatusSupplierOrderRefused';
799
800
			// List of language codes for status
801
			$this->statuts_short[0] = 'StatusSupplierOrderDraftShort';
802
			$this->statuts_short[1] = 'StatusSupplierOrderValidatedShort';
803
			$this->statuts_short[2] = 'StatusSupplierOrderApprovedShort';
804
			$this->statuts_short[3] = 'StatusSupplierOrderOnProcessShort';
805
			$this->statuts_short[4] = 'StatusSupplierOrderReceivedPartiallyShort';
806
			$this->statuts_short[5] = 'StatusSupplierOrderReceivedAllShort';
807
			$this->statuts_short[6] = 'StatusSupplierOrderCanceledShort';
808
			$this->statuts_short[7] = 'StatusSupplierOrderCanceledShort';
809
			$this->statuts_short[9] = 'StatusSupplierOrderRefusedShort';
810
		}
811
812
		$statustrans = array(
813
			0 => 'status0',
814
			1 => 'status1b',
815
			2 => 'status1',
816
			3 => 'status4',
817
			4 => 'status4b',
818
			5 => 'status6',
819
			6 => 'status9',
820
			7 => 'status9',
821
			9 => 'status9',
822
		);
823
824
		$statusClass = 'status0';
825
		if (!empty($statustrans[$status])) {
826
			$statusClass = $statustrans[$status];
827
		}
828
829
		$billedtext = '';
830
		if ($billed) {
831
			$billedtext = ' - '.$langs->trans("Billed");
832
		}
833
		if ($status == 5 && $billed) {
834
			$statusClass = 'status6';
835
		}
836
837
		$statusLong = $langs->transnoentitiesnoconv($this->statuts[$status]).$billedtext;
838
		$statusShort = $langs->transnoentitiesnoconv($this->statuts_short[$status]);
839
840
		$parameters = array('status' => $status, 'mode' => $mode, 'billed' => $billed);
841
		$reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
842
		if ($reshook > 0) {
843
			return $hookmanager->resPrint;
844
		}
845
846
		return dolGetStatus($statusLong, $statusShort, '', $statusClass, $mode);
847
	}
848
849
	/**
850
	 * getTooltipContentArray
851
	 *
852
	 * @param array $params ex option, infologin
853
	 * @since v18
854
	 * @return array
855
	 */
856
	public function getTooltipContentArray($params)
857
	{
858
		global $conf, $langs, $user;
859
860
		$langs->loadLangs(['bills', 'orders']);
861
862
		$datas = [];
863
		if ($user->hasRight("fournisseur", "commande", "read")) {
864
			$datas['picto'] = '<u class="paddingrightonly">'.$langs->trans("SupplierOrder").'</u>';
865
			if (isset($this->statut)) {
866
				$datas['picto'] .= ' '.$this->getLibStatut(5);
867
			}
868
			if (!empty($this->ref)) {
869
				$datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
870
			}
871
			if (!empty($this->ref_supplier)) {
872
				$datas['refsupplier'] = '<br><b>'.$langs->trans('RefSupplier').':</b> '.$this->ref_supplier;
873
			}
874
			if (!empty($this->total_ht)) {
875
				$datas['totalht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
876
			}
877
			if (!empty($this->total_tva)) {
878
				$datas['totaltva'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
879
			}
880
			if (!empty($this->total_ttc)) {
881
				$datas['totalttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
882
			}
883
			if (!empty($this->date)) {
884
				$datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
885
			}
886
			if (!empty($this->delivery_date)) {
887
				$datas['deliverydate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
888
			}
889
		}
890
		return $datas;
891
	}
892
893
	/**
894
	 *	Return clicable name (with picto eventually)
895
	 *
896
	 *	@param		int		$withpicto					0=No picto, 1=Include picto into link, 2=Only picto
897
	 *	@param		string	$option						On what the link points
898
	 *  @param	    int   	$notooltip					1=Disable tooltip
899
	 *  @param      int     $save_lastsearch_value		-1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
900
	 *  @param		int		$addlinktonotes				Add link to show notes
901
	 *	@return		string								Chain with URL
902
	 */
903
	public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = 0)
904
	{
905
		global $langs, $conf, $user, $hookmanager;
906
907
		$result = '';
908
		$params = [
909
			'id' => $this->id,
910
			'objecttype' => $this->element,
911
			'option' => $option,
912
		];
913
		$classfortooltip = 'classfortooltip';
914
		$dataparams = '';
915
		if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
916
			$classfortooltip = 'classforajaxtooltip';
917
			$dataparams = ' data-params='.json_encode($params);
918
			// $label = $langs->trans('Loading');
919
		}
920
921
		$label = implode($this->getTooltipContentArray($params));
922
923
		$url = DOL_URL_ROOT.'/fourn/commande/card.php?id='.$this->id;
924
925
		if ($option !== 'nolink') {
926
			// Add param to save lastsearch_values or not
927
			$add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
928
			if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
929
				$add_save_lastsearch_values = 1;
930
			}
931
			if ($add_save_lastsearch_values) {
932
				$url .= '&save_lastsearch_values=1';
933
			}
934
		}
935
936
		$linkclose = '';
937
		if (empty($notooltip)) {
938
			if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
939
				$label = $langs->trans("ShowOrder");
940
				$linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
941
			}
942
			$linkclose .= $dataparams.' title="'.dol_escape_htmltag($label, 1).'"';
943
			$linkclose .= ' class="'.$classfortooltip.'"';
944
		}
945
946
		$linkstart = '<a href="'.$url.'"';
947
		$linkstart .= $linkclose.'>';
948
		$linkend = '</a>';
949
950
		$result .= $linkstart;
951
		if ($withpicto) {
952
			$result .= img_object(($notooltip ? '' : $label), $this->picto, ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : $dataparams.' class="'.(($withpicto != 2) ? 'paddingright ' : '').$classfortooltip.'"'), 0, 0, $notooltip ? 0 : 1);
953
		}
954
		if ($withpicto != 2) {
955
			$result .= $this->ref;
956
		}
957
		$result .= $linkend;
958
959
		if ($addlinktonotes) {
960
			$txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
961
			if ($txttoshow) {
962
				$notetoshow = $langs->trans("ViewPrivateNote").':<br>'.dol_string_nohtmltag($txttoshow, 1);
963
				$result .= ' <span class="note inline-block">';
964
				$result .= '<a href="'.DOL_URL_ROOT.'/fourn/commande/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($notetoshow).'">';
965
				$result .= img_picto('', 'note');
966
				$result .= '</a>';
967
				//$result.=img_picto($langs->trans("ViewNote"),'object_generic');
968
				//$result.='</a>';
969
				$result .= '</span>';
970
			}
971
		}
972
973
		global $action;
974
		$hookmanager->initHooks(array($this->element . 'dao'));
975
		$parameters = array('id'=>$this->id, 'getnomurl' => &$result);
976
		$reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
977
		if ($reshook > 0) {
978
			$result = $hookmanager->resPrint;
979
		} else {
980
			$result .= $hookmanager->resPrint;
981
		}
982
		return $result;
983
	}
984
985
986
	/**
987
	 *  Returns the following order reference not used depending on the numbering model activated
988
	 *                  defined within COMMANDE_SUPPLIER_ADDON_NUMBER
989
	 *
990
	 *  @param	    Societe		$soc  		company object
991
	 *  @return     string                  free reference for the invoice
992
	 */
993
	public function getNextNumRef($soc)
994
	{
995
		global $db, $langs, $conf;
996
		$langs->load("orders");
997
998
		if (!empty($conf->global->COMMANDE_SUPPLIER_ADDON_NUMBER)) {
999
			$mybool = false;
1000
1001
			$file = $conf->global->COMMANDE_SUPPLIER_ADDON_NUMBER.'.php';
1002
			$classname = $conf->global->COMMANDE_SUPPLIER_ADDON_NUMBER;
1003
1004
			// Include file with class
1005
			$dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
1006
1007
			foreach ($dirmodels as $reldir) {
1008
				$dir = dol_buildpath($reldir."core/modules/supplier_order/");
1009
1010
				// Load file with numbering class (if found)
1011
				$mybool |= @include_once $dir.$file;
1012
			}
1013
1014
			if ($mybool === false) {
1015
				dol_print_error('', "Failed to include file ".$file);
1016
				return '';
1017
			}
1018
1019
			$obj = new $classname();
1020
			$numref = $obj->getNextValue($soc, $this);
1021
1022
			if ($numref != "") {
1023
				return $numref;
1024
			} else {
1025
				$this->error = $obj->error;
1026
				return -1;
1027
			}
1028
		} else {
1029
			$this->error = "Error_COMMANDE_SUPPLIER_ADDON_NotDefined";
1030
			return -2;
1031
		}
1032
	}
1033
	/**
1034
	 *	Class invoiced the supplier order
1035
	 *
1036
	 *  @param      User        $user       Object user making the change
1037
	 *	@return     int     	            <0 if KO, 0 if already billed,  >0 if OK
1038
	 */
1039
	public function classifyBilled(User $user)
1040
	{
1041
		$error = 0;
1042
1043
		if ($this->billed) {
1044
			return 0;
1045
		}
1046
1047
		$this->db->begin();
1048
1049
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'commande_fournisseur SET billed = 1';
1050
		$sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > '.self::STATUS_DRAFT;
1051
1052
		if ($this->db->query($sql)) {
1053
			if (!$error) {
1054
				// Call trigger
1055
				$result = $this->call_trigger('ORDER_SUPPLIER_CLASSIFY_BILLED', $user);
1056
				if ($result < 0) {
1057
					$error++;
1058
				}
1059
				// End call triggers
1060
			}
1061
1062
			if (!$error) {
1063
				$this->billed = 1;
1064
1065
				$this->db->commit();
1066
				return 1;
1067
			} else {
1068
				$this->db->rollback();
1069
				return -1;
1070
			}
1071
		} else {
1072
			dol_print_error($this->db);
1073
1074
			$this->db->rollback();
1075
			return -1;
1076
		}
1077
	}
1078
1079
	/**
1080
	 * 	Approve a supplier order
1081
	 *
1082
	 *	@param	User	$user			Object user
1083
	 *	@param	int		$idwarehouse	Id of warhouse for stock change
1084
	 *  @param	int		$secondlevel	0=Standard approval, 1=Second level approval (used when option SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED is set)
1085
	 *	@return	int						<0 if KO, >0 if OK
1086
	 */
1087
	public function approve($user, $idwarehouse = 0, $secondlevel = 0)
1088
	{
1089
		global $langs, $conf;
1090
		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1091
1092
		$error = 0;
1093
1094
		dol_syslog(get_class($this)."::approve");
1095
1096
		if ($user->rights->fournisseur->commande->approuver) {
1097
			$now = dol_now();
1098
1099
			$this->db->begin();
1100
1101
			// Definition of order numbering model name
1102
			$soc = new Societe($this->db);
1103
			$soc->fetch($this->fourn_id);
1104
1105
			// Check if object has a temporary ref
1106
			if (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
1107
				$num = $this->getNextNumRef($soc);
1108
			} else {
1109
				$num = $this->ref;
1110
			}
1111
			$this->newref = dol_sanitizeFileName($num);
1112
1113
			// Do we have to change status now ? (If double approval is required and first approval, we keep status to 1 = validated)
1114
			$movetoapprovestatus = true;
1115
			$comment = '';
1116
1117
			$sql = "UPDATE ".MAIN_DB_PREFIX."commande_fournisseur";
1118
			$sql .= " SET ref='".$this->db->escape($num)."',";
1119
			if (empty($secondlevel)) {	// standard or first level approval
1120
				$sql .= " date_approve='".$this->db->idate($now)."',";
1121
				$sql .= " fk_user_approve = ".$user->id;
1122
				if (!empty($conf->global->SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED) && $this->total_ht >= $conf->global->SUPPLIER_ORDER_3_STEPS_TO_BE_APPROVED) {
1123
					if (empty($this->user_approve_id2)) {
1124
						$movetoapprovestatus = false; // second level approval not done
1125
						$comment = ' (first level)';
1126
					}
1127
				}
1128
			} else // request a second level approval
1129
			{
1130
				$sql .= " date_approve2='".$this->db->idate($now)."',";
1131
				$sql .= " fk_user_approve2 = ".((int) $user->id);
1132
				if (empty($this->user_approve_id)) {
1133
					$movetoapprovestatus = false; // first level approval not done
1134
				}
1135
				$comment = ' (second level)';
1136
			}
1137
			// If double approval is required and first approval, we keep status to 1 = validated
1138
			if ($movetoapprovestatus) {
1139
				$sql .= ", fk_statut = ".self::STATUS_ACCEPTED;
1140
			} else {
1141
				$sql .= ", fk_statut = ".self::STATUS_VALIDATED;
1142
			}
1143
			$sql .= " WHERE rowid = ".((int) $this->id);
1144
			$sql .= " AND fk_statut = ".self::STATUS_VALIDATED;
1145
1146
			if ($this->db->query($sql)) {
1147
				if (!empty($conf->global->SUPPLIER_ORDER_AUTOADD_USER_CONTACT)) {
1148
					$result = $this->add_contact($user->id, 'SALESREPFOLL', 'internal', 1);
1149
					if ($result < 0 && $result != -2) {	// -2 means already exists
1150
						$error++;
1151
					}
1152
				}
1153
1154
				// If stock is incremented on validate order, we must increment it
1155
				if (!$error && $movetoapprovestatus && isModEnabled('stock') && !empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER)) {
1156
					require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
1157
					$langs->load("agenda");
1158
1159
					$cpt = count($this->lines);
1160
					for ($i = 0; $i < $cpt; $i++) {
1161
						// Product with reference
1162
						if ($this->lines[$i]->fk_product > 0) {
1163
							$this->line = $this->lines[$i];
1164
							$mouvP = new MouvementStock($this->db);
1165
							$mouvP->origin = &$this;
1166
							$mouvP->setOrigin($this->element, $this->id);
1167
							// We decrement stock of product (and sub-products)
1168
							$up_ht_disc = $this->lines[$i]->subprice;
1169
							if (!empty($this->lines[$i]->remise_percent) && empty($conf->global->STOCK_EXCLUDE_DISCOUNT_FOR_PMP)) {
1170
								$up_ht_disc = price2num($up_ht_disc * (100 - $this->lines[$i]->remise_percent) / 100, 'MU');
1171
							}
1172
							$result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $up_ht_disc, $langs->trans("OrderApprovedInDolibarr", $this->ref));
1173
							if ($result < 0) {
1174
								$error++;
1175
							}
1176
							unset($this->line);
1177
						}
1178
					}
1179
				}
1180
1181
				if (!$error) {
1182
					// Call trigger
1183
					$result = $this->call_trigger('ORDER_SUPPLIER_APPROVE', $user);
1184
					if ($result < 0) {
1185
						$error++;
1186
					}
1187
					// End call triggers
1188
				}
1189
1190
				if (!$error) {
1191
					$this->ref = $this->newref;
1192
1193
					if ($movetoapprovestatus) {
1194
						$this->statut = self::STATUS_ACCEPTED;
1195
					} else {
1196
						$this->statut = self::STATUS_VALIDATED;
1197
					}
1198
					if (empty($secondlevel)) {	// standard or first level approval
1199
						$this->date_approve = $now;
1200
						$this->user_approve_id = $user->id;
1201
					} else // request a second level approval
1202
					{
1203
						$this->date_approve2 = $now;
1204
						$this->user_approve_id2 = $user->id;
1205
					}
1206
1207
					$this->db->commit();
1208
					return 1;
1209
				} else {
1210
					$this->db->rollback();
1211
					return -1;
1212
				}
1213
			} else {
1214
				$this->db->rollback();
1215
				$this->error = $this->db->lasterror();
1216
				return -1;
1217
			}
1218
		} else {
1219
			dol_syslog(get_class($this)."::approve Not Authorized", LOG_ERR);
1220
		}
1221
		return -1;
1222
	}
1223
1224
	/**
1225
	 * 	Refuse an order
1226
	 *
1227
	 * 	@param		User	$user		User making action
1228
	 *	@return		int					0 if Ok, <0 if Ko
1229
	 */
1230
	public function refuse($user)
1231
	{
1232
		global $conf, $langs;
1233
1234
		$error = 0;
1235
1236
		dol_syslog(get_class($this)."::refuse");
1237
		$result = 0;
1238
		if ($user->rights->fournisseur->commande->approuver) {
1239
			$this->db->begin();
1240
1241
			$sql = "UPDATE ".MAIN_DB_PREFIX."commande_fournisseur SET fk_statut = ".self::STATUS_REFUSED;
1242
			$sql .= " WHERE rowid = ".((int) $this->id);
1243
1244
			if ($this->db->query($sql)) {
1245
				$result = 0;
1246
1247
				if ($error == 0) {
1248
					// Call trigger
1249
					$result = $this->call_trigger('ORDER_SUPPLIER_REFUSE', $user);
1250
					if ($result < 0) {
1251
						$error++;
1252
						$this->db->rollback();
1253
					} else {
1254
						$this->db->commit();
1255
					}
1256
					// End call triggers
1257
				}
1258
			} else {
1259
				$this->db->rollback();
1260
				$this->error = $this->db->lasterror();
1261
				dol_syslog(get_class($this)."::refuse Error -1");
1262
				$result = -1;
1263
			}
1264
		} else {
1265
			dol_syslog(get_class($this)."::refuse Not Authorized");
1266
		}
1267
		return $result;
1268
	}
1269
1270
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1271
	/**
1272
	 * 	Cancel an approved order.
1273
	 *	The cancellation is done after approval
1274
	 *
1275
	 * 	@param	User	$user			User making action
1276
	 *	@param	int		$idwarehouse	Id warehouse to use for stock change (not used for supplier orders).
1277
	 * 	@return	int						>0 if Ok, <0 if Ko
1278
	 */
1279
	public function Cancel($user, $idwarehouse = -1)
1280
	{
1281
		// phpcs:enable
1282
		global $langs, $conf;
1283
1284
		$error = 0;
1285
1286
		//dol_syslog("CommandeFournisseur::Cancel");
1287
		$result = 0;
1288
		if ($user->rights->fournisseur->commande->commander) {
1289
			$statut = self::STATUS_CANCELED;
1290
1291
			$this->db->begin();
1292
1293
			$sql = "UPDATE ".MAIN_DB_PREFIX."commande_fournisseur SET fk_statut = ".((int) $statut);
1294
			$sql .= " WHERE rowid = ".((int) $this->id);
1295
			dol_syslog(get_class($this)."::cancel", LOG_DEBUG);
1296
			if ($this->db->query($sql)) {
1297
				$result = 0;
1298
1299
				// Call trigger
1300
				$result = $this->call_trigger('ORDER_SUPPLIER_CANCEL', $user);
1301
				if ($result < 0) {
1302
					$error++;
1303
				}
1304
				// End call triggers
1305
1306
				if ($error == 0) {
1307
					$this->db->commit();
1308
					return 1;
1309
				} else {
1310
					$this->db->rollback();
1311
					return -1;
1312
				}
1313
			} else {
1314
				$this->db->rollback();
1315
				$this->error = $this->db->lasterror();
1316
				dol_syslog(get_class($this)."::cancel ".$this->error);
1317
				return -1;
1318
			}
1319
		} else {
1320
			dol_syslog(get_class($this)."::cancel Not Authorized");
1321
			return -1;
1322
		}
1323
	}
1324
1325
	/**
1326
	 * 	Submit a supplier order to supplier
1327
	 *
1328
	 * 	@param		User	$user		User making change
1329
	 * 	@param		integer	$date		Date
1330
	 * 	@param		int		$methode	Method
1331
	 * 	@param		string	$comment	Comment
1332
	 * 	@return		int			        <0 if KO, >0 if OK
1333
	 */
1334
	public function commande($user, $date, $methode, $comment = '')
1335
	{
1336
		global $langs;
1337
		dol_syslog(get_class($this)."::commande");
1338
		$error = 0;
1339
		if ($user->rights->fournisseur->commande->commander) {
1340
			$this->db->begin();
1341
1342
			$newnoteprivate = $this->note_private;
1343
			if ($comment) {
1344
				$newnoteprivate = dol_concatdesc($newnoteprivate, $langs->trans("Comment").': '.$comment);
1345
			}
1346
1347
			$sql = "UPDATE ".MAIN_DB_PREFIX."commande_fournisseur";
1348
			$sql .= " SET fk_statut=".self::STATUS_ORDERSENT.", fk_input_method=".$methode.", date_commande='".$this->db->idate($date)."', ";
1349
			$sql .= " note_private='".$this->db->escape($newnoteprivate)."'";
1350
			$sql .= " WHERE rowid=".((int) $this->id);
1351
1352
			dol_syslog(get_class($this)."::commande", LOG_DEBUG);
1353
			if ($this->db->query($sql)) {
1354
				$this->statut = self::STATUS_ORDERSENT;
1355
				$this->methode_commande_id = $methode;
1356
				$this->date_commande = $date;
1357
				$this->context = array('comments' => $comment);
1358
1359
				// Call trigger
1360
				$result = $this->call_trigger('ORDER_SUPPLIER_SUBMIT', $user);
1361
				if ($result < 0) {
1362
					$error++;
1363
				}
1364
				// End call triggers
1365
			} else {
1366
				$error++;
1367
				$this->error = $this->db->lasterror();
1368
				$this->errors[] = $this->db->lasterror();
1369
			}
1370
1371
			if (!$error) {
1372
				$this->db->commit();
1373
			} else {
1374
				$this->db->rollback();
1375
			}
1376
		} else {
1377
			$error++;
1378
			$this->error = $langs->trans('NotAuthorized');
1379
			$this->errors[] = $langs->trans('NotAuthorized');
1380
			dol_syslog(get_class($this)."::commande User not Authorized", LOG_WARNING);
1381
		}
1382
1383
		return ($error ? -1 : 1);
1384
	}
1385
1386
	/**
1387
	 *  Create order with draft status
1388
	 *
1389
	 *  @param      User	$user       User making creation
1390
	 *	@param		int		$notrigger	Disable all triggers
1391
	 *  @return     int         		<0 if KO, Id of supplier order if OK
1392
	 */
1393
	public function create($user, $notrigger = 0)
1394
	{
1395
		global $langs, $conf, $hookmanager;
1396
1397
		$this->db->begin();
1398
1399
		$error = 0;
1400
		$now = dol_now();
1401
1402
		// set tmp vars
1403
		$date = ($this->date_commande ? $this->date_commande : $this->date); // in case of date is set
1404
		if (empty($date)) {
1405
			$date = $now;
1406
		}
1407
		$delivery_date = empty($this->delivery_date) ? $this->date_livraison : $this->delivery_date;
1408
1409
		// Clean parameters
1410
		if (empty($this->source)) {
1411
			$this->source = 0;
1412
		}
1413
1414
		// Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1415
		if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1416
			list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $date);
1417
		} else {
1418
			$this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1419
		}
1420
		if (empty($this->fk_multicurrency)) {
1421
			$this->multicurrency_code = $conf->currency;
1422
			$this->fk_multicurrency = 0;
1423
			$this->multicurrency_tx = 1;
1424
		}
1425
1426
		// We set order into draft status
1427
		$this->brouillon = 1;
1428
1429
		$sql = "INSERT INTO ".MAIN_DB_PREFIX."commande_fournisseur (";
1430
		$sql .= "ref";
1431
		$sql .= ", ref_supplier";
1432
		$sql .= ", note_private";
1433
		$sql .= ", note_public";
1434
		$sql .= ", entity";
1435
		$sql .= ", fk_soc";
1436
		$sql .= ", fk_projet";
1437
		$sql .= ", date_creation";
1438
		$sql .= ", date_livraison";
1439
		$sql .= ", fk_user_author";
1440
		$sql .= ", fk_statut";
1441
		$sql .= ", source";
1442
		$sql .= ", model_pdf";
1443
		$sql .= ", fk_mode_reglement";
1444
		$sql .= ", fk_cond_reglement";
1445
		$sql .= ", fk_account";
1446
		$sql .= ", fk_incoterms, location_incoterms";
1447
		$sql .= ", fk_multicurrency";
1448
		$sql .= ", multicurrency_code";
1449
		$sql .= ", multicurrency_tx";
1450
		$sql .= ") ";
1451
		$sql .= " VALUES (";
1452
		$sql .= "'(PROV)'";
1453
		$sql .= ", '".$this->db->escape($this->ref_supplier)."'";
1454
		$sql .= ", '".$this->db->escape($this->note_private)."'";
1455
		$sql .= ", '".$this->db->escape($this->note_public)."'";
1456
		$sql .= ", ".setEntity($this);
1457
		$sql .= ", ".((int) $this->socid);
1458
		$sql .= ", ".($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
1459
		$sql .= ", '".$this->db->idate($date)."'";
1460
		$sql .= ", ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : "null");
1461
		$sql .= ", ".((int) $user->id);
1462
		$sql .= ", ".self::STATUS_DRAFT;
1463
		$sql .= ", ".((int) $this->source);
1464
		$sql .= ", '".$this->db->escape($conf->global->COMMANDE_SUPPLIER_ADDON_PDF)."'";
1465
		$sql .= ", ".($this->mode_reglement_id > 0 ? $this->mode_reglement_id : 'null');
1466
		$sql .= ", ".($this->cond_reglement_id > 0 ? $this->cond_reglement_id : 'null');
1467
		$sql .= ", ".($this->fk_account > 0 ? $this->fk_account : 'NULL');
1468
		$sql .= ", ".(int) $this->fk_incoterms;
1469
		$sql .= ", '".$this->db->escape($this->location_incoterms)."'";
1470
		$sql .= ", ".(int) $this->fk_multicurrency;
1471
		$sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
1472
		$sql .= ", ".(double) $this->multicurrency_tx;
1473
		$sql .= ")";
1474
1475
		dol_syslog(get_class($this)."::create", LOG_DEBUG);
1476
		if ($this->db->query($sql)) {
1477
			$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."commande_fournisseur");
1478
1479
			if ($this->id) {
1480
				$num = count($this->lines);
1481
1482
				// insert products details into database
1483
				for ($i = 0; $i < $num; $i++) {
1484
										$line = $this->lines[$i];
1485
					if (!is_object($line)) {
1486
						$line = (object) $line;
1487
					}
1488
1489
1490
					//$this->special_code = $line->special_code; // TODO : remove this in 9.0 and add special_code param to addline()
1491
1492
					// This include test on qty if option SUPPLIER_ORDER_WITH_NOPRICEDEFINED is not set
1493
					$result = $this->addline(
1494
						$line->desc,
1495
						$line->subprice,
1496
						$line->qty,
1497
						$line->tva_tx,
1498
						$line->localtax1_tx,
1499
						$line->localtax2_tx,
1500
						$line->fk_product,
1501
						0,
1502
						$line->ref_fourn, // $line->ref_fourn comes from field ref into table of lines. Value may ba a ref that does not exists anymore, so we first try with value of product
1503
						$line->remise_percent,
1504
						'HT',
1505
						0,
1506
						$line->product_type,
1507
						$line->info_bits,
1508
						false,
1509
						$line->date_start,
1510
						$line->date_end,
1511
						$line->array_options,
1512
						$line->fk_unit,
1513
						$line->special_code
1514
						);
1515
					if ($result < 0) {
1516
						dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING); // do not use dol_print_error here as it may be a functionnal error
1517
						$this->db->rollback();
1518
						return -1;
1519
					}
1520
				}
1521
1522
				$sql = "UPDATE ".MAIN_DB_PREFIX."commande_fournisseur";
1523
				$sql .= " SET ref='(PROV".$this->id.")'";
1524
				$sql .= " WHERE rowid=".((int) $this->id);
1525
				dol_syslog(get_class($this)."::create", LOG_DEBUG);
1526
				if ($this->db->query($sql)) {
1527
					// Add link with price request and supplier order
1528
					if ($this->id) {
1529
						$this->ref = "(PROV".$this->id.")";
1530
1531
						if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) {	// To use new linkedObjectsIds instead of old linked_objects
1532
							$this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1533
						}
1534
1535
						// Add object linked
1536
						if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
1537
							foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1538
								if (is_array($tmp_origin_id)) {       // New behaviour, if linked_object can have several links per type, so is something like array('contract'=>array(id1, id2, ...))
1539
									foreach ($tmp_origin_id as $origin_id) {
1540
										$ret = $this->add_object_linked($origin, $origin_id);
1541
										if (!$ret) {
1542
											dol_print_error($this->db);
1543
											$error++;
1544
										}
1545
									}
1546
								} else // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
1547
								{
1548
									$origin_id = $tmp_origin_id;
1549
									$ret = $this->add_object_linked($origin, $origin_id);
1550
									if (!$ret) {
1551
										dol_print_error($this->db);
1552
										$error++;
1553
									}
1554
								}
1555
							}
1556
						}
1557
					}
1558
1559
					if (!$error) {
1560
						$result = $this->insertExtraFields();
1561
						if ($result < 0) {
1562
							$error++;
1563
						}
1564
					}
1565
1566
					if (!$error && !$notrigger) {
1567
						// Call trigger
1568
						$result = $this->call_trigger('ORDER_SUPPLIER_CREATE', $user);
1569
						if ($result < 0) {
1570
							$this->db->rollback();
1571
							return -1;
1572
						}
1573
						// End call triggers
1574
					}
1575
1576
					$this->db->commit();
1577
					return $this->id;
1578
				} else {
1579
					$this->error = $this->db->lasterror();
1580
					$this->db->rollback();
1581
					return -2;
1582
				}
1583
			}
1584
		} else {
1585
			$this->error = $this->db->lasterror();
1586
			$this->db->rollback();
1587
			return -1;
1588
		}
1589
	}
1590
1591
	/**
1592
	 *	Update Supplier Order
1593
	 *
1594
	 *	@param      User	$user        	User that modify
1595
	 *	@param      int		$notrigger	    0=launch triggers after, 1=disable triggers
1596
	 *	@return     int      			   	<0 if KO, >0 if OK
1597
	 */
1598
	public function update(User $user, $notrigger = 0)
1599
	{
1600
		global $conf;
1601
1602
		$error = 0;
1603
1604
		// Clean parameters
1605
		if (isset($this->ref)) {
1606
			$this->ref = trim($this->ref);
1607
		}
1608
		if (isset($this->ref_supplier)) {
1609
			$this->ref_supplier = trim($this->ref_supplier);
1610
		}
1611
		if (isset($this->note_private)) {
1612
			$this->note_private = trim($this->note_private);
1613
		}
1614
		if (isset($this->note_public)) {
1615
			$this->note_public = trim($this->note_public);
1616
		}
1617
		if (isset($this->model_pdf)) {
1618
			$this->model_pdf = trim($this->model_pdf);
1619
		}
1620
		if (isset($this->import_key)) {
1621
			$this->import_key = trim($this->import_key);
1622
		}
1623
1624
		// Update request
1625
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
1626
1627
		$sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1628
		$sql .= " ref_supplier=".(isset($this->ref_supplier) ? "'".$this->db->escape($this->ref_supplier)."'" : "null").",";
1629
		$sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1630
		$sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
1631
		$sql .= " date_commande=".(strval($this->date_commande) != '' ? "'".$this->db->idate($this->date_commande)."'" : 'null').",";
1632
		$sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
1633
		$sql .= " total_tva=".(isset($this->total_tva) ? $this->total_tva : "null").",";
1634
		$sql .= " localtax1=".(isset($this->total_localtax1) ? $this->total_localtax1 : "null").",";
1635
		$sql .= " localtax2=".(isset($this->total_localtax2) ? $this->total_localtax2 : "null").",";
1636
		$sql .= " total_ht=".(isset($this->total_ht) ? $this->total_ht : "null").",";
1637
		$sql .= " total_ttc=".(isset($this->total_ttc) ? $this->total_ttc : "null").",";
1638
		$sql .= " fk_statut=".(isset($this->statut) ? $this->statut : "null").",";
1639
		$sql .= " fk_user_author=".(isset($this->user_author_id) ? $this->user_author_id : "null").",";
1640
		$sql .= " fk_user_valid=".(isset($this->user_valid) && $this->user_valid > 0 ? $this->user_valid : "null").",";
1641
		$sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").",";
1642
		$sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? $this->cond_reglement_id : "null").",";
1643
		$sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? $this->mode_reglement_id : "null").",";
1644
		$sql .= " date_livraison=".(strval($this->delivery_date) != '' ? "'".$this->db->idate($this->delivery_date)."'" : 'null').",";
1645
		//$sql .= " fk_shipping_method=".(isset($this->shipping_method_id) ? $this->shipping_method_id : "null").",";
1646
		$sql .= " fk_account=".($this->fk_account > 0 ? $this->fk_account : "null").",";
1647
		//$sql .= " fk_input_reason=".($this->demand_reason_id > 0 ? $this->demand_reason_id : "null").",";
1648
		$sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1649
		$sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1650
		$sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1651
		$sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null");
1652
1653
		$sql .= " WHERE rowid=".((int) $this->id);
1654
1655
		$this->db->begin();
1656
1657
		dol_syslog(get_class($this)."::update", LOG_DEBUG);
1658
		$resql = $this->db->query($sql);
1659
		if (!$resql) {
1660
			$error++;
1661
			$this->errors[] = "Error ".$this->db->lasterror();
1662
		}
1663
1664
		if (!$error) {
1665
			$result = $this->insertExtraFields();
1666
			if ($result < 0) {
1667
				$error++;
1668
			}
1669
		}
1670
1671
		if (!$error && !$notrigger) {
1672
			// Call trigger
1673
			$result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
1674
			if ($result < 0) {
1675
				$error++;
1676
			}
1677
			// End call triggers
1678
		}
1679
1680
		// Commit or rollback
1681
		if ($error) {
1682
			foreach ($this->errors as $errmsg) {
1683
				dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1684
				$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1685
			}
1686
			$this->db->rollback();
1687
			return -1 * $error;
1688
		} else {
1689
			$this->db->commit();
1690
			return 1;
1691
		}
1692
	}
1693
1694
	/**
1695
	 *	Load an object from its id and create a new one in database
1696
	 *
1697
	 *  @param	    User	$user		User making the clone
1698
	 *	@param		int		$socid		Id of thirdparty
1699
	 *  @param 		int		$notrigger  Disable all triggers
1700
	 *	@return		int					New id of clone
1701
	 */
1702
	public function createFromClone(User $user, $socid = 0, $notrigger = 0)
1703
	{
1704
		global $conf, $user, $hookmanager;
1705
1706
		$error = 0;
1707
1708
		$this->db->begin();
1709
1710
		// get extrafields so they will be clone
1711
		foreach ($this->lines as $line) {
1712
			$line->fetch_optionals();
1713
		}
1714
1715
		// Load source object
1716
		$objFrom = clone $this;
1717
1718
		// Change socid if needed
1719
		if (!empty($socid) && $socid != $this->socid) {
1720
			$objsoc = new Societe($this->db);
1721
1722
			if ($objsoc->fetch($socid) > 0) {
1723
				$this->socid = $objsoc->id;
1724
				$this->cond_reglement_id	= (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1725
				$this->mode_reglement_id	= (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1726
				$this->fk_project = 0;
1727
				$this->fk_delivery_address = 0;
1728
			}
1729
1730
			// TODO Change product price if multi-prices
1731
		}
1732
1733
		$this->id = 0;
1734
		$this->statut = self::STATUS_DRAFT;
1735
1736
		// Clear fields
1737
		$this->user_author_id     = $user->id;
1738
		$this->user_valid         = 0;
1739
		$this->date_creation      = '';
1740
		$this->date_validation    = '';
1741
		$this->ref_supplier       = '';
1742
		$this->user_approve_id    = '';
1743
		$this->user_approve_id2   = '';
1744
		$this->date_approve       = '';
1745
		$this->date_approve2      = '';
1746
1747
		// Create clone
1748
		$this->context['createfromclone'] = 'createfromclone';
1749
		$result = $this->create($user, $notrigger);
1750
		if ($result < 0) {
1751
			$error++;
1752
		}
1753
1754
		if (!$error) {
1755
			// Hook of thirdparty module
1756
			if (is_object($hookmanager)) {
1757
				$parameters = array('objFrom'=>$objFrom);
1758
				$action = '';
1759
				$reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1760
				if ($reshook < 0) {
1761
					$this->errors += $hookmanager->errors;
1762
					$this->error = $hookmanager->error;
1763
					$error++;
1764
				}
1765
			}
1766
		}
1767
1768
		unset($this->context['createfromclone']);
1769
1770
		// End
1771
		if (!$error) {
1772
			$this->db->commit();
1773
			return $this->id;
1774
		} else {
1775
			$this->db->rollback();
1776
			return -1;
1777
		}
1778
	}
1779
1780
	/**
1781
	 *	Add order line
1782
	 *
1783
	 *	@param      string	$desc            		Description
1784
	 *	@param      float	$pu_ht              	Unit price (used if $price_base_type is 'HT')
1785
	 *	@param      float	$qty             		Quantity
1786
	 *	@param      float	$txtva           		Taux tva
1787
	 *	@param      float	$txlocaltax1        	Localtax1 tax
1788
	 *  @param      float	$txlocaltax2        	Localtax2 tax
1789
	 *	@param      int		$fk_product      		Id product
1790
	 *  @param      int		$fk_prod_fourn_price	Id supplier price
1791
	 *  @param      string	$ref_supplier			Supplier reference price
1792
	 *	@param      float	$remise_percent  		Remise
1793
	 *	@param      string	$price_base_type		HT or TTC
1794
	 *	@param		float	$pu_ttc					Unit price TTC (used if $price_base_type is 'TTC')
1795
	 *	@param		int		$type					Type of line (0=product, 1=service)
1796
	 *	@param		int		$info_bits				More information
1797
	 *  @param		bool	$notrigger				Disable triggers
1798
	 *  @param		int		$date_start				Date start of service
1799
	 *  @param		int		$date_end				Date end of service
1800
	 *  @param		array	$array_options			extrafields array
1801
	 *  @param 		string	$fk_unit 				Code of the unit to use. Null to use the default one
1802
	 *  @param 		string	$pu_ht_devise			Amount in currency
1803
	 *  @param		string	$origin					'order', ...
1804
	 *  @param		int		$origin_id				Id of origin object
1805
	 *  @param		int		$rang					Rank
1806
	 * 	@param		int		$special_code			Special code
1807
	 *	@return     int             				<=0 if KO, >0 if OK
1808
	 */
1809
	public function addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1 = 0.0, $txlocaltax2 = 0.0, $fk_product = 0, $fk_prod_fourn_price = 0, $ref_supplier = '', $remise_percent = 0.0, $price_base_type = 'HT', $pu_ttc = 0.0, $type = 0, $info_bits = 0, $notrigger = false, $date_start = null, $date_end = null, $array_options = 0, $fk_unit = null, $pu_ht_devise = 0, $origin = '', $origin_id = 0, $rang = -1, $special_code = 0)
1810
	{
1811
		global $langs, $mysoc, $conf;
1812
1813
		dol_syslog(get_class($this)."::addline $desc, $pu_ht, $qty, $txtva, $txlocaltax1, $txlocaltax2, $fk_product, $fk_prod_fourn_price, $ref_supplier, $remise_percent, $price_base_type, $pu_ttc, $type, $info_bits, $notrigger, $date_start, $date_end, $fk_unit, $pu_ht_devise, $origin, $origin_id");
1814
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1815
1816
		if ($this->statut == self::STATUS_DRAFT) {
1817
			include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1818
1819
			// Clean parameters
1820
			if (empty($qty)) {
1821
				$qty = 0;
1822
			}
1823
			if (!$info_bits) {
1824
				$info_bits = 0;
1825
			}
1826
			if (empty($txtva)) {
1827
				$txtva = 0;
1828
			}
1829
			if (empty($rang)) {
1830
				$rang = 0;
1831
			}
1832
			if (empty($txlocaltax1)) {
1833
				$txlocaltax1 = 0;
1834
			}
1835
			if (empty($txlocaltax2)) {
1836
				$txlocaltax2 = 0;
1837
			}
1838
			if (empty($remise_percent)) {
1839
				$remise_percent = 0;
1840
			}
1841
1842
			$remise_percent = price2num($remise_percent);
1843
			$qty = price2num($qty);
1844
			$pu_ht = price2num($pu_ht);
1845
			$pu_ht_devise = price2num($pu_ht_devise);
1846
			$pu_ttc = price2num($pu_ttc);
1847
			if (!preg_match('/\((.*)\)/', $txtva)) {
1848
				$txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
1849
			}
1850
			$txlocaltax1 = price2num($txlocaltax1);
1851
			$txlocaltax2 = price2num($txlocaltax2);
1852
			if ($price_base_type == 'HT') {
1853
				$pu = $pu_ht;
1854
			} else {
1855
				$pu = $pu_ttc;
1856
			}
1857
			$desc = trim($desc);
1858
1859
			// Check parameters
1860
			if ($qty < 0 && !$fk_product) {
1861
				$this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Product"));
1862
				return -1;
1863
			}
1864
			if ($type < 0) {
1865
				return -1;
1866
			}
1867
			if ($date_start && $date_end && $date_start > $date_end) {
1868
				$langs->load("errors");
1869
				$this->error = $langs->trans('ErrorStartDateGreaterEnd');
1870
				return -1;
1871
			}
1872
1873
1874
			$this->db->begin();
1875
1876
			$product_type = $type;
1877
			$label = '';	// deprecated
1878
1879
			if ($fk_product > 0) {
1880
				if (!empty($conf->global->SUPPLIER_ORDER_WITH_PREDEFINED_PRICES_ONLY)) {	// Not the common case
1881
					// Check quantity is enough
1882
					dol_syslog(get_class($this)."::addline we check supplier prices fk_product=".$fk_product." fk_prod_fourn_price=".$fk_prod_fourn_price." qty=".$qty." ref_supplier=".$ref_supplier);
1883
					$prod = new ProductFournisseur($this->db);
1884
					if ($prod->fetch($fk_product) > 0) {
1885
						$product_type = $prod->type;
1886
						$label = $prod->label;
1887
1888
						// We use 'none' instead of $ref_supplier, because fourn_ref may not exists anymore. So we will take the first supplier price ok.
1889
						// If we want a dedicated supplier price, we must provide $fk_prod_fourn_price.
1890
						$result = $prod->get_buyprice($fk_prod_fourn_price, $qty, $fk_product, 'none', (isset($this->fk_soc) ? $this->fk_soc : $this->socid)); // Search on couple $fk_prod_fourn_price/$qty first, then on triplet $qty/$fk_product/$ref_supplier/$this->fk_soc
1891
1892
						// If supplier order created from sales order, we take best supplier price
1893
						// If $pu (defined previously from pu_ht or pu_ttc) is not defined at all, we also take the best supplier price
1894
						if ($result > 0 && ($origin == 'commande' || $pu === '')) {
1895
							$pu = $prod->fourn_pu; // Unit price supplier price set by get_buyprice
1896
							$ref_supplier = $prod->ref_supplier; // Ref supplier price set by get_buyprice
1897
							// is remise percent not keyed but present for the product we add it
1898
							if ($remise_percent == 0 && $prod->remise_percent != 0) {
1899
								$remise_percent = $prod->remise_percent;
1900
							}
1901
						}
1902
						if ($result == 0) {                   // If result == 0, we failed to found the supplier reference price
1903
							$langs->load("errors");
1904
							$this->error = "Ref ".$prod->ref." ".$langs->trans("ErrorQtyTooLowForThisSupplier");
1905
							$this->db->rollback();
1906
							dol_syslog(get_class($this)."::addline we did not found supplier price, so we can't guess unit price");
1907
							//$pu    = $prod->fourn_pu;     // We do not overwrite unit price
1908
							//$ref   = $prod->ref_fourn;    // We do not overwrite ref supplier price
1909
							return -1;
1910
						}
1911
						if ($result == -1) {
1912
							$langs->load("errors");
1913
							$this->error = "Ref ".$prod->ref." ".$langs->trans("ErrorQtyTooLowForThisSupplier");
1914
							$this->db->rollback();
1915
							dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_DEBUG);
1916
							return -1;
1917
						}
1918
						if ($result < -1) {
1919
							$this->error = $prod->error;
1920
							$this->db->rollback();
1921
							dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_ERR);
1922
							return -1;
1923
						}
1924
					} else {
1925
						$this->error = $prod->error;
1926
						$this->db->rollback();
1927
						return -1;
1928
					}
1929
				}
1930
1931
				// Predefine quantity according to packaging
1932
				if (!empty($conf->global->PRODUCT_USE_SUPPLIER_PACKAGING)) {
1933
					$prod = new Product($this->db);
1934
					$prod->get_buyprice($fk_prod_fourn_price, $qty, $fk_product, 'none', ($this->fk_soc ? $this->fk_soc : $this->socid));
1935
1936
					if ($qty < $prod->packaging) {
1937
						$qty = $prod->packaging;
1938
					} else {
1939
						if (!empty($prod->packaging) && ($qty % $prod->packaging) > 0) {
1940
							$coeff = intval($qty / $prod->packaging) + 1;
1941
							$qty = $prod->packaging * $coeff;
1942
							setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'mesgs');
1943
						}
1944
					}
1945
				}
1946
			}
1947
1948
			if (isModEnabled("multicurrency") && $pu_ht_devise > 0) {
1949
				$pu = 0;
1950
			}
1951
1952
			$localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
1953
1954
			// Clean vat code
1955
			$reg = array();
1956
			$vat_src_code = '';
1957
			if (preg_match('/\((.*)\)/', $txtva, $reg)) {
1958
				$vat_src_code = $reg[1];
1959
				$txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
1960
			}
1961
1962
			// Calcul du total TTC et de la TVA pour la ligne a partir de
1963
			// qty, pu, remise_percent et txtva
1964
			// TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
1965
			// la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
1966
1967
			$tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $product_type, $this->thirdparty, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise);
1968
1969
			$total_ht  = $tabprice[0];
1970
			$total_tva = $tabprice[1];
1971
			$total_ttc = $tabprice[2];
1972
			$total_localtax1 = $tabprice[9];
1973
			$total_localtax2 = $tabprice[10];
1974
			$pu = $pu_ht = $tabprice[3];
1975
1976
			// MultiCurrency
1977
			$multicurrency_total_ht = $tabprice[16];
1978
			$multicurrency_total_tva = $tabprice[17];
1979
			$multicurrency_total_ttc = $tabprice[18];
1980
			$pu_ht_devise = $tabprice[19];
1981
1982
			$localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
1983
			$localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
1984
1985
			if ($rang < 0) {
1986
				$rangmax = $this->line_max();
1987
				$rang = $rangmax + 1;
1988
			}
1989
1990
			// Insert line
1991
			$this->line = new CommandeFournisseurLigne($this->db);
1992
1993
			$this->line->context = $this->context;
1994
1995
			$this->line->fk_commande = $this->id;
1996
			$this->line->label = $label;
1997
			$this->line->ref_fourn = $ref_supplier;
1998
			$this->line->ref_supplier = $ref_supplier;
1999
			$this->line->desc = $desc;
2000
			$this->line->qty = $qty;
2001
			$this->line->tva_tx = $txtva;
2002
			$this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
2003
			$this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
2004
			$this->line->localtax1_type = $localtax1_type;
2005
			$this->line->localtax2_type = $localtax2_type;
2006
			$this->line->fk_product = $fk_product;
2007
			$this->line->product_type = $product_type;
2008
			$this->line->remise_percent = $remise_percent;
2009
			$this->line->subprice = $pu_ht;
2010
			$this->line->rang = $rang;
2011
			$this->line->info_bits = $info_bits;
2012
2013
			$this->line->vat_src_code = $vat_src_code;
2014
			$this->line->total_ht = $total_ht;
2015
			$this->line->total_tva = $total_tva;
2016
			$this->line->total_localtax1 = $total_localtax1;
2017
			$this->line->total_localtax2 = $total_localtax2;
2018
			$this->line->total_ttc = $total_ttc;
2019
			$this->line->product_type = $type;
2020
			$this->line->special_code   = (!empty($this->special_code) ? $this->special_code : 0);
2021
			$this->line->origin = $origin;
2022
			$this->line->origin_id = $origin_id;
2023
			$this->line->fk_unit = $fk_unit;
2024
2025
			$this->line->date_start = $date_start;
2026
			$this->line->date_end = $date_end;
2027
2028
			// Multicurrency
2029
			$this->line->fk_multicurrency = $this->fk_multicurrency;
2030
			$this->line->multicurrency_code = $this->multicurrency_code;
2031
			$this->line->multicurrency_subprice		= $pu_ht_devise;
2032
			$this->line->multicurrency_total_ht 	= $multicurrency_total_ht;
2033
			$this->line->multicurrency_total_tva 	= $multicurrency_total_tva;
2034
			$this->line->multicurrency_total_ttc 	= $multicurrency_total_ttc;
2035
2036
			$this->line->subprice = $pu_ht;
2037
			$this->line->price = $this->line->subprice;
2038
2039
			$this->line->remise_percent = $remise_percent;
2040
2041
			if (is_array($array_options) && count($array_options) > 0) {
2042
				$this->line->array_options = $array_options;
2043
			}
2044
2045
			$result = $this->line->insert($notrigger);
2046
			if ($result > 0) {
2047
				// Reorder if child line
2048
				if (!empty($fk_parent_line)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $fk_parent_line seems to never exist and therefore empty should always be true.
Loading history...
2049
					$this->line_order(true, 'DESC');
2050
				} elseif ($rang > 0 && $rang <= count($this->lines)) { // Update all rank of all other lines
2051
					$linecount = count($this->lines);
2052
					for ($ii = $rang; $ii <= $linecount; $ii++) {
2053
						$this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
2054
					}
2055
				}
2056
2057
				// Mise a jour informations denormalisees au niveau de la commande meme
2058
				$result = $this->update_price(1, 'auto', 0, $this->thirdparty); // This method is designed to add line from user input so total calculation must be done using 'auto' mode.
2059
				if ($result > 0) {
2060
					$this->db->commit();
2061
					return $this->line->id;
2062
				} else {
2063
					$this->db->rollback();
2064
					return -1;
2065
				}
2066
			} else {
2067
				$this->error = $this->line->error;
2068
				$this->errors = $this->line->errors;
2069
				dol_syslog(get_class($this)."::addline error=".$this->error, LOG_ERR);
2070
				$this->db->rollback();
2071
				return -1;
2072
			}
2073
		}
2074
	}
2075
2076
2077
	/**
2078
	 * Save a receiving into the tracking table of receiving (commande_fournisseur_dispatch) and add product into stock warehouse.
2079
	 *
2080
	 * @param 	User		$user					User object making change
2081
	 * @param 	int			$product				Id of product to dispatch
2082
	 * @param 	double		$qty					Qty to dispatch
2083
	 * @param 	int			$entrepot				Id of warehouse to add product
2084
	 * @param 	double		$price					Unit Price for PMP value calculation (Unit price without Tax and taking into account discount)
2085
	 * @param	string		$comment				Comment for stock movement
2086
	 * @param	integer		$eatby					eat-by date
2087
	 * @param	integer		$sellby					sell-by date
2088
	 * @param	string		$batch					Lot number
2089
	 * @param	int			$fk_commandefourndet	Id of supplier order line
2090
	 * @param	int			$notrigger          	1 = notrigger
2091
	 * @return 	int						<0 if KO, >0 if OK
2092
	 */
2093
	public function dispatchProduct($user, $product, $qty, $entrepot, $price = 0, $comment = '', $eatby = '', $sellby = '', $batch = '', $fk_commandefourndet = 0, $notrigger = 0)
2094
	{
2095
		global $conf, $langs;
2096
2097
		$error = 0;
2098
		require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2099
2100
		// Check parameters (if test are wrong here, there is bug into caller)
2101
		if ($entrepot <= 0) {
2102
			$this->error = 'ErrorBadValueForParameterWarehouse';
2103
			return -1;
2104
		}
2105
		if ($qty == 0) {
2106
			$this->error = 'ErrorBadValueForParameterQty';
2107
			return -1;
2108
		}
2109
2110
		$dispatchstatus = 1;
2111
		if (!empty($conf->global->SUPPLIER_ORDER_USE_DISPATCH_STATUS)) {
2112
			$dispatchstatus = 0; // Setting dispatch status (a validation step after receiving products) will be done manually to 1 or 2 if this option is on
2113
		}
2114
2115
		$now = dol_now();
2116
2117
		$inventorycode = dol_print_date(dol_now(), 'dayhourlog');
2118
2119
		if (($this->statut == self::STATUS_ORDERSENT || $this->statut == self::STATUS_RECEIVED_PARTIALLY || $this->statut == self::STATUS_RECEIVED_COMPLETELY)) {
2120
			$this->db->begin();
2121
2122
			$sql = "INSERT INTO ".MAIN_DB_PREFIX."commande_fournisseur_dispatch";
2123
			$sql .= " (fk_commande, fk_product, qty, fk_entrepot, fk_user, datec, fk_commandefourndet, status, comment, eatby, sellby, batch) VALUES";
2124
			$sql .= " ('".$this->id."','".$product."','".$qty."',".($entrepot > 0 ? "'".$entrepot."'" : "null").",'".$user->id."','".$this->db->idate($now)."','".$fk_commandefourndet."', ".$dispatchstatus.", '".$this->db->escape($comment)."', ";
2125
			$sql .= ($eatby ? "'".$this->db->idate($eatby)."'" : "null").", ".($sellby ? "'".$this->db->idate($sellby)."'" : "null").", ".($batch ? "'".$this->db->escape($batch)."'" : "null");
2126
			$sql .= ")";
2127
2128
			dol_syslog(get_class($this)."::dispatchProduct", LOG_DEBUG);
2129
			$resql = $this->db->query($sql);
2130
			if ($resql) {
2131
				if (!$notrigger) {
2132
					global $conf, $langs, $user;
2133
					// Call trigger
2134
					$result = $this->call_trigger('LINEORDER_SUPPLIER_DISPATCH', $user);
2135
					if ($result < 0) {
2136
						$error++;
2137
					}
2138
					// End call triggers
2139
				}
2140
			} else {
2141
				$this->error = $this->db->lasterror();
2142
				$error++;
2143
			}
2144
2145
			// If module stock is enabled and the stock increase is done on purchase order dispatching
2146
			if (!$error && $entrepot > 0 && isModEnabled('stock') && !empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER)) {
2147
				$mouv = new MouvementStock($this->db);
2148
				if ($product > 0) {
2149
					// $price should take into account discount (except if option STOCK_EXCLUDE_DISCOUNT_FOR_PMP is on)
2150
					$mouv->origin = &$this;
2151
					$mouv->setOrigin($this->element, $this->id);
2152
2153
					// Method change if qty < 0
2154
					if (!empty($conf->global->SUPPLIER_ORDER_ALLOW_NEGATIVE_QTY_FOR_SUPPLIER_ORDER_RETURN) && $qty < 0) {
2155
						$result = $mouv->livraison($user, $product, $entrepot, $qty*(-1), $price, $comment, $now, $eatby, $sellby, $batch, 0, $inventorycode);
2156
					} else {
2157
						$result = $mouv->reception($user, $product, $entrepot, $qty, $price, $comment, $eatby, $sellby, $batch, '', 0, $inventorycode);
2158
					}
2159
2160
					if ($result < 0) {
2161
						$this->error = $mouv->error;
2162
						$this->errors = $mouv->errors;
2163
						dol_syslog(get_class($this)."::dispatchProduct ".$this->error." ".join(',', $this->errors), LOG_ERR);
2164
						$error++;
2165
					}
2166
				}
2167
			}
2168
2169
			if ($error == 0) {
2170
				$this->db->commit();
2171
				return 1;
2172
			} else {
2173
				$this->db->rollback();
2174
				return -1;
2175
			}
2176
		} else {
2177
			$this->error = 'BadStatusForObject';
2178
			return -2;
2179
		}
2180
	}
2181
2182
	/**
2183
	 * 	Delete line
2184
	 *
2185
	 *	@param	int		$idline		Id of line to delete
2186
	 *	@param	int		$notrigger	1=Disable call to triggers
2187
	 *	@return	int					<0 if KO, >0 if OK
2188
	 */
2189
	public function deleteline($idline, $notrigger = 0)
2190
	{
2191
		if ($this->statut == 0) {
2192
			$line = new CommandeFournisseurLigne($this->db);
2193
2194
			if ($line->fetch($idline) <= 0) {
2195
				return 0;
2196
			}
2197
2198
			// check if not yet received
2199
			$dispatchedLines = $this->getDispachedLines();
2200
			foreach ($dispatchedLines as $dispatchLine) {
2201
				if ($dispatchLine['orderlineid'] == $idline) {
2202
					$this->error = "LineAlreadyDispatched";
2203
					$this->errors[] = $this->error;
2204
					return -3;
2205
				}
2206
			}
2207
2208
			if ($line->delete($notrigger) > 0) {
2209
				$this->update_price(1);
2210
				return 1;
2211
			} else {
2212
				$this->error = $line->error;
2213
				$this->errors = $line->errors;
2214
				return -1;
2215
			}
2216
		} else {
2217
			return -2;
2218
		}
2219
	}
2220
2221
	/**
2222
	 *  Delete an order
2223
	 *
2224
	 *	@param	User	$user		Object user
2225
	 *	@param	int		$notrigger	1=Does not execute triggers, 0= execute triggers
2226
	 *	@return	int					<0 if KO, >0 if OK
2227
	 */
2228
	public function delete(User $user, $notrigger = 0)
2229
	{
2230
		global $langs, $conf;
2231
		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2232
2233
		$error = 0;
2234
2235
		$this->db->begin();
2236
2237
		if (empty($notrigger)) {
2238
			// Call trigger
2239
			$result = $this->call_trigger('ORDER_SUPPLIER_DELETE', $user);
2240
			if ($result < 0) {
2241
				$this->errors[] = 'ErrorWhenRunningTrigger';
2242
				dol_syslog(get_class($this)."::delete ".$this->error, LOG_ERR);
2243
				$this->db->rollback();
2244
				return -1;
2245
			}
2246
			// End call triggers
2247
		}
2248
2249
		// Test we can delete
2250
		$this->fetchObjectLinked(null, 'order_supplier');
2251
		if (!empty($this->linkedObjects) && array_key_exists('reception', $this->linkedObjects)) {
2252
			foreach ($this->linkedObjects['reception'] as $element) {
2253
				if ($element->statut >= 0) {
2254
					$this->errors[] = $langs->trans('ReceptionExist');
2255
					$error++;
2256
					break;
2257
				}
2258
			}
2259
		}
2260
2261
		$main = MAIN_DB_PREFIX.'commande_fournisseurdet';
2262
		$ef = $main."_extrafields";
2263
		$sql = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM $main WHERE fk_commande = ".((int) $this->id).")";
2264
		dol_syslog(get_class($this)."::delete extrafields lines", LOG_DEBUG);
2265
		if (!$this->db->query($sql)) {
2266
			$this->error = $this->db->lasterror();
2267
			$this->errors[] = $this->db->lasterror();
2268
			$error++;
2269
		}
2270
2271
		$sql = "DELETE FROM ".MAIN_DB_PREFIX."commande_fournisseurdet WHERE fk_commande =".((int) $this->id);
2272
		dol_syslog(get_class($this)."::delete", LOG_DEBUG);
2273
		if (!$this->db->query($sql)) {
2274
			$this->error = $this->db->lasterror();
2275
			$this->errors[] = $this->db->lasterror();
2276
			$error++;
2277
		}
2278
2279
		$sql = "DELETE FROM ".MAIN_DB_PREFIX."commande_fournisseur WHERE rowid =".((int) $this->id);
2280
		dol_syslog(get_class($this)."::delete", LOG_DEBUG);
2281
		if ($resql = $this->db->query($sql)) {
2282
			if ($this->db->affected_rows($resql) < 1) {
2283
				$this->error = $this->db->lasterror();
2284
				$this->errors[] = $this->db->lasterror();
2285
				$error++;
2286
			}
2287
		} else {
2288
			$this->error = $this->db->lasterror();
2289
			$this->errors[] = $this->db->lasterror();
2290
			$error++;
2291
		}
2292
2293
		// Remove extrafields
2294
		if (!$error) {
2295
			$result = $this->deleteExtraFields();
2296
			if ($result < 0) {
2297
				$this->error = 'FailToDeleteExtraFields';
2298
				$this->errors[] = 'FailToDeleteExtraFields';
2299
				$error++;
2300
				dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
2301
			}
2302
		}
2303
2304
		// Delete linked object
2305
		$res = $this->deleteObjectLinked();
2306
		if ($res < 0) {
2307
			$this->error = 'FailToDeleteObjectLinked';
2308
			$this->errors[] = 'FailToDeleteObjectLinked';
2309
			$error++;
2310
		}
2311
2312
		if (!$error) {
2313
			// Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
2314
			$this->deleteEcmFiles();
2315
2316
			// We remove directory
2317
			$ref = dol_sanitizeFileName($this->ref);
2318
			if ($conf->fournisseur->commande->dir_output) {
2319
				$dir = $conf->fournisseur->commande->dir_output."/".$ref;
2320
				$file = $dir."/".$ref.".pdf";
2321
				if (file_exists($file)) {
2322
					if (!dol_delete_file($file, 0, 0, 0, $this)) { // For triggers
2323
						$this->error = 'ErrorFailToDeleteFile';
2324
						$this->errors[] = 'ErrorFailToDeleteFile';
2325
						$error++;
2326
					}
2327
				}
2328
				if (file_exists($dir)) {
2329
					$res = @dol_delete_dir_recursive($dir);
2330
					if (!$res) {
2331
						$this->error = 'ErrorFailToDeleteDir';
2332
						$this->errors[] = 'ErrorFailToDeleteDir';
2333
						$error++;
2334
					}
2335
				}
2336
			}
2337
		}
2338
2339
		if (!$error) {
2340
			dol_syslog(get_class($this)."::delete $this->id by $user->id", LOG_DEBUG);
2341
			$this->db->commit();
2342
			return 1;
2343
		} else {
2344
			dol_syslog(get_class($this)."::delete ".$this->error, LOG_ERR);
2345
			$this->db->rollback();
2346
			return -$error;
2347
		}
2348
	}
2349
2350
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2351
	/**
2352
	 *	Get list of order methods
2353
	 *
2354
	 *	@return int 0 if OK, <0 if KO
2355
	 */
2356
	public function get_methodes_commande()
2357
	{
2358
		// phpcs:enable
2359
		$sql = "SELECT rowid, libelle";
2360
		$sql .= " FROM ".MAIN_DB_PREFIX."c_input_method";
2361
		$sql .= " WHERE active = 1";
2362
2363
		$resql = $this->db->query($sql);
2364
		if ($resql) {
2365
			$i = 0;
2366
			$num = $this->db->num_rows($resql);
2367
			$this->methodes_commande = array();
2368
			while ($i < $num) {
2369
				$row = $this->db->fetch_row($resql);
2370
2371
				$this->methodes_commande[$row[0]] = $row[1];
2372
2373
				$i++;
2374
			}
2375
			return 0;
2376
		} else {
2377
			return -1;
2378
		}
2379
	}
2380
2381
	/**
2382
	 * Return array of dispatched lines waiting to be approved for this order
2383
	 *
2384
	 * @since 8.0 Return dispatched quantity (qty).
2385
	 *
2386
	 * @param	int		$status		Filter on stats (-1 = no filter, 0 = lines draft to be approved, 1 = approved lines)
2387
	 * @return	array				Array of lines
2388
	 */
2389
	public function getDispachedLines($status = -1)
2390
	{
2391
		$ret = array();
2392
2393
		// List of already dispatched lines
2394
		$sql = "SELECT p.ref, p.label,";
2395
		$sql .= " e.rowid as warehouse_id, e.ref as entrepot,";
2396
		$sql .= " cfd.rowid as dispatchedlineid, cfd.fk_product, cfd.qty, cfd.eatby, cfd.sellby, cfd.batch, cfd.comment, cfd.status, cfd.fk_commandefourndet";
2397
		$sql .= " FROM ".MAIN_DB_PREFIX."product as p,";
2398
		$sql .= " ".MAIN_DB_PREFIX."commande_fournisseur_dispatch as cfd";
2399
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."entrepot as e ON cfd.fk_entrepot = e.rowid";
2400
		$sql .= " WHERE cfd.fk_commande = ".((int) $this->id);
2401
		$sql .= " AND cfd.fk_product = p.rowid";
2402
		if ($status >= 0) {
2403
			$sql .= " AND cfd.status = ".((int) $status);
2404
		}
2405
		$sql .= " ORDER BY cfd.rowid ASC";
2406
2407
		$resql = $this->db->query($sql);
2408
		if ($resql) {
2409
			$num = $this->db->num_rows($resql);
2410
			$i = 0;
2411
2412
			while ($i < $num) {
2413
				$objp = $this->db->fetch_object($resql);
2414
				if ($objp) {
2415
					$ret[] = array(
2416
						'id' => $objp->dispatchedlineid,
2417
						'productid' => $objp->fk_product,
2418
						'warehouseid' => $objp->warehouse_id,
2419
						'qty' => $objp->qty,
2420
						'orderlineid' => $objp->fk_commandefourndet
2421
					);
2422
				}
2423
2424
				$i++;
2425
			}
2426
		} else {
2427
			dol_print_error($this->db, 'Failed to execute request to get dispatched lines');
2428
		}
2429
2430
		return $ret;
2431
	}
2432
2433
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2434
	/**
2435
	 * 	Set a delivery in database for this supplier order
2436
	 *
2437
	 *	@param	User	$user		User that input data
2438
	 *	@param	integer	$date		Date of reception
2439
	 *	@param	string	$type		Type of receipt ('tot' = total/done, 'par' = partial, 'nev' = never, 'can' = cancel)
2440
	 *	@param	string	$comment	Comment
2441
	 *	@return	int					<0 if KO, >0 if OK
2442
	 */
2443
	public function Livraison($user, $date, $type, $comment)
2444
	{
2445
		// phpcs:enable
2446
		global $conf, $langs;
2447
2448
		$result = 0;
2449
		$error = 0;
2450
2451
		dol_syslog(get_class($this)."::Livraison");
2452
2453
		$usercanreceive = 0;
2454
		if (!isModEnabled('reception')) {
2455
			$usercanreceive = $user->rights->fournisseur->commande->receptionner;
2456
		} else {
2457
			$usercanreceive = $user->rights->reception->creer;
2458
		}
2459
2460
		if ($usercanreceive) {
2461
			// Define the new status
2462
			if ($type == 'par') {
2463
				$statut = self::STATUS_RECEIVED_PARTIALLY;
2464
			} elseif ($type == 'tot') {
2465
				$statut = self::STATUS_RECEIVED_COMPLETELY;
2466
			} elseif ($type == 'nev') {
2467
				$statut = self::STATUS_CANCELED_AFTER_ORDER;
2468
			} elseif ($type == 'can') {
2469
				$statut = self::STATUS_CANCELED_AFTER_ORDER;
2470
			} else {
2471
				$error++;
2472
				dol_syslog(get_class($this)."::Livraison Error -2", LOG_ERR);
2473
				return -2;
2474
			}
2475
2476
			// Some checks to accept the record
2477
			if (!empty($conf->global->SUPPLIER_ORDER_USE_DISPATCH_STATUS)) {
2478
				// If option SUPPLIER_ORDER_USE_DISPATCH_STATUS is on, we check all reception are approved to allow status "total/done"
2479
				if (!$error && ($type == 'tot')) {
2480
					$dispatchedlinearray = $this->getDispachedLines(0);
2481
					if (count($dispatchedlinearray) > 0) {
2482
						$result = -1;
2483
						$error++;
2484
						$this->errors[] = 'ErrorCantSetReceptionToTotalDoneWithReceptionToApprove';
2485
						dol_syslog('ErrorCantSetReceptionToTotalDoneWithReceptionToApprove', LOG_DEBUG);
2486
					}
2487
				}
2488
				if (!$error && !empty($conf->global->SUPPLIER_ORDER_USE_DISPATCH_STATUS_NEED_APPROVE) && ($type == 'tot')) {	// Accept to move to reception done, only if status of all line are ok (refuse denied)
2489
					$dispatcheddenied = $this->getDispachedLines(2);
2490
					if (count($dispatchedlinearray) > 0) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $dispatchedlinearray does not seem to be defined for all execution paths leading up to this point.
Loading history...
2491
						$result = -1;
2492
						$error++;
2493
						$this->errors[] = 'ErrorCantSetReceptionToTotalDoneWithReceptionDenied';
2494
						dol_syslog('ErrorCantSetReceptionToTotalDoneWithReceptionDenied', LOG_DEBUG);
2495
					}
2496
				}
2497
			}
2498
2499
			// TODO LDR01 Add a control test to accept only if ALL predefined products are received (same qty).
2500
2501
			if (empty($error)) {
2502
				$this->db->begin();
2503
2504
				$sql = "UPDATE ".MAIN_DB_PREFIX."commande_fournisseur";
2505
				$sql .= " SET fk_statut = ".((int) $statut);
2506
				$sql .= " WHERE rowid = ".((int) $this->id);
2507
				$sql .= " AND fk_statut IN (".self::STATUS_ORDERSENT.",".self::STATUS_RECEIVED_PARTIALLY.")"; // Process running or Partially received
2508
2509
				dol_syslog(get_class($this)."::Livraison", LOG_DEBUG);
2510
				$resql = $this->db->query($sql);
2511
				if ($resql) {
2512
					$result = 1;
2513
					$old_statut = $this->statut;
2514
					$this->statut = $statut;
2515
					$this->actionmsg2 = $comment;
2516
2517
					// Call trigger
2518
					$result_trigger = $this->call_trigger('ORDER_SUPPLIER_RECEIVE', $user);
2519
					if ($result_trigger < 0) {
2520
						$error++;
2521
					}
2522
					// End call triggers
2523
2524
					if (empty($error)) {
2525
						$this->db->commit();
2526
					} else {
2527
						$this->statut = $old_statut;
2528
						$this->db->rollback();
2529
						$this->error = $this->db->lasterror();
2530
						$result = -1;
2531
					}
2532
				} else {
2533
					$this->db->rollback();
2534
					$this->error = $this->db->lasterror();
2535
					$result = -1;
2536
				}
2537
			}
2538
		} else {
2539
			$this->error = $langs->trans('NotAuthorized');
2540
			$this->errors[] = $langs->trans('NotAuthorized');
2541
			dol_syslog(get_class($this)."::Livraison Not Authorized");
2542
			$result = -3;
2543
		}
2544
		return $result;
2545
	}
2546
2547
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2548
	/**
2549
	 *	Set delivery date
2550
	 *
2551
	 *	@param      User 	$user        		Object user that modify
2552
	 *	@param      int		$delivery_date		Delivery date
2553
	 *  @param  	int		$notrigger			1=Does not execute triggers, 0= execute triggers
2554
	 *	@return     int         				<0 if ko, >0 if ok
2555
	 *	@deprecated Use  setDeliveryDate
2556
	 */
2557
	public function set_date_livraison($user, $delivery_date, $notrigger = 0)
2558
	{
2559
		// phpcs:enable
2560
		return $this->setDeliveryDate($user, $delivery_date, $notrigger);
2561
	}
2562
2563
	/**
2564
	 *	Set the planned delivery date
2565
	 *
2566
	 *	@param      User			$user        		Objet user making change
2567
	 *	@param      integer  		$delivery_date     Planned delivery date
2568
	 *  @param     	int				$notrigger			1=Does not execute triggers, 0= execute triggers
2569
	 *	@return     int         						<0 if KO, >0 if OK
2570
	 */
2571
	public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2572
	{
2573
		if ($user->rights->fournisseur->commande->creer || $user->rights->supplier_order->creer) {
2574
			$error = 0;
2575
2576
			$this->db->begin();
2577
2578
			$sql = "UPDATE ".MAIN_DB_PREFIX."commande_fournisseur";
2579
			$sql .= " SET date_livraison = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
2580
			$sql .= " WHERE rowid = ".((int) $this->id);
2581
2582
			dol_syslog(__METHOD__, LOG_DEBUG);
2583
			$resql = $this->db->query($sql);
2584
			if (!$resql) {
2585
				$this->errors[] = $this->db->error();
2586
				$error++;
2587
			}
2588
2589
			if (!$error) {
2590
				$this->oldcopy = clone $this;
2591
				$this->date_livraison = $delivery_date;
2592
				$this->delivery_date = $delivery_date;
2593
			}
2594
2595
			if (!$notrigger && empty($error)) {
2596
				// Call trigger
2597
				$result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
2598
				if ($result < 0) {
2599
					$error++;
2600
				}
2601
				// End call triggers
2602
			}
2603
2604
			if (!$error) {
2605
				$this->db->commit();
2606
				return 1;
2607
			} else {
2608
				foreach ($this->errors as $errmsg) {
2609
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2610
					$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2611
				}
2612
				$this->db->rollback();
2613
				return -1 * $error;
2614
			}
2615
		} else {
2616
			return -2;
2617
		}
2618
	}
2619
2620
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2621
	/**
2622
	 *	Set the id projet
2623
	 *
2624
	 *	@param      User			$user        		Objet utilisateur qui modifie
2625
	 *	@param      int				$id_projet    	 	Delivery date
2626
	 *  @param     	int				$notrigger			1=Does not execute triggers, 0= execute triggers
2627
	 *	@return     int         						<0 si ko, >0 si ok
2628
	 */
2629
	public function set_id_projet($user, $id_projet, $notrigger = 0)
2630
	{
2631
		// phpcs:enable
2632
		if ($user->rights->fournisseur->commande->creer || $user->rights->supplier_order->creer) {
2633
			$error = 0;
2634
2635
			$this->db->begin();
2636
2637
			$sql = "UPDATE ".MAIN_DB_PREFIX."commande_fournisseur";
2638
			$sql .= " SET fk_projet = ".($id_projet > 0 ? (int) $id_projet : 'null');
2639
			$sql .= " WHERE rowid = ".((int) $this->id);
2640
2641
			dol_syslog(__METHOD__, LOG_DEBUG);
2642
			$resql = $this->db->query($sql);
2643
			if (!$resql) {
2644
				$this->errors[] = $this->db->error();
2645
				$error++;
2646
			}
2647
2648
			if (!$error) {
2649
				$this->oldcopy = clone $this;
2650
				$this->fk_projet = $id_projet;
2651
				$this->fk_project = $id_projet;
2652
			}
2653
2654
			if (!$notrigger && empty($error)) {
2655
				// Call trigger
2656
				$result = $this->call_trigger('ORDER_SUPPLIER_MODIFY', $user);
2657
				if ($result < 0) {
2658
					$error++;
2659
				}
2660
				// End call triggers
2661
			}
2662
2663
			if (!$error) {
2664
				$this->db->commit();
2665
				return 1;
2666
			} else {
2667
				foreach ($this->errors as $errmsg) {
2668
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2669
					$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2670
				}
2671
				$this->db->rollback();
2672
				return -1 * $error;
2673
			}
2674
		} else {
2675
			return -2;
2676
		}
2677
	}
2678
2679
	/**
2680
	 *  Update a supplier order from a sales order
2681
	 *
2682
	 *  @param  User	$user           User that create
2683
	 *  @param  int		$idc			Id of purchase order to update
2684
	 *  @param	int		$comclientid	Id of sale order to use as template
2685
	 *	@return	int						<0 if KO, >0 if OK
2686
	 */
2687
	public function updateFromCommandeClient($user, $idc, $comclientid)
2688
	{
2689
		$comclient = new Commande($this->db);
2690
		$comclient->fetch($comclientid);
2691
2692
		$this->id = $idc;
2693
2694
		$this->lines = array();
2695
2696
		$num = count($comclient->lines);
2697
		for ($i = 0; $i < $num; $i++) {
2698
			$prod = new Product($this->db);
2699
			$label = '';
2700
			$ref = '';
2701
			if ($prod->fetch($comclient->lines[$i]->fk_product) > 0) {
2702
				$label  = $prod->label;
2703
				$ref    = $prod->ref;
2704
			}
2705
2706
			$sql = "INSERT INTO ".MAIN_DB_PREFIX."commande_fournisseurdet";
2707
			$sql .= " (fk_commande, label, description, fk_product, price, qty, tva_tx, localtax1_tx, localtax2_tx, remise_percent, subprice, remise, ref)";
2708
			$sql .= " VALUES (".((int) $idc).", '".$this->db->escape($label)."', '".$this->db->escape($comclient->lines[$i]->desc)."'";
2709
			$sql .= ",".$comclient->lines[$i]->fk_product.", ".price2num($comclient->lines[$i]->price, 'MU');
2710
			$sql .= ", ".price2num($comclient->lines[$i]->qty, 'MS').", ".price2num($comclient->lines[$i]->tva_tx, 5).", ".price2num($comclient->lines[$i]->localtax1_tx, 5).", ".price2num($comclient->lines[$i]->localtax2_tx, 5).", ".price2num($comclient->lines[$i]->remise_percent, 3);
2711
			$sql .= ", '".price2num($comclient->lines[$i]->subprice, 'MT')."','0', '".$this->db->escape($ref)."');";
2712
			if ($this->db->query($sql)) {
2713
				$this->update_price(1);
2714
			}
2715
		}
2716
2717
		return 1;
2718
	}
2719
2720
	/**
2721
	 *  Tag order with a particular status
2722
	 *
2723
	 *  @param      User	$user       Object user that change status
2724
	 *  @param      int		$status		New status
2725
	 *  @return     int         		<0 if KO, >0 if OK
2726
	 */
2727
	public function setStatus($user, $status)
2728
	{
2729
		global $conf, $langs;
2730
		$error = 0;
2731
2732
		$this->db->begin();
2733
2734
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'commande_fournisseur';
2735
		$sql .= " SET fk_statut = ".$status;
2736
		$sql .= " WHERE rowid = ".((int) $this->id);
2737
2738
		dol_syslog(get_class($this)."::setStatus", LOG_DEBUG);
2739
		$resql = $this->db->query($sql);
2740
		if ($resql) {
2741
			// Trigger names for each status
2742
			$triggerName = array();
2743
			$triggerName[0] = 'DRAFT';
2744
			$triggerName[1] = 'VALIDATED';
2745
			$triggerName[2] = 'APPROVED';
2746
			$triggerName[3] = 'ORDERED'; // Ordered
2747
			$triggerName[4] = 'RECEIVED_PARTIALLY';
2748
			$triggerName[5] = 'RECEIVED_COMPLETELY';
2749
			$triggerName[6] = 'CANCELED';
2750
			$triggerName[7] = 'CANCELED';
2751
			$triggerName[9] = 'REFUSED';
2752
2753
			// Call trigger
2754
			$result = $this->call_trigger("ORDER_SUPPLIER_STATUS_".$triggerName[$status], $user);
2755
			if ($result < 0) {
2756
				$error++;
2757
			}
2758
			// End call triggers
2759
		} else {
2760
			$error++;
2761
			$this->error = $this->db->lasterror();
2762
			dol_syslog(get_class($this)."::setStatus ".$this->error);
2763
		}
2764
2765
		if (!$error) {
2766
			$this->statut = $status;
2767
			$this->db->commit();
2768
			return 1;
2769
		} else {
2770
			$this->db->rollback();
2771
			return -1;
2772
		}
2773
	}
2774
2775
	/**
2776
	 *	Update line
2777
	 *
2778
	 *	@param     	int			$rowid           	Id de la ligne de facture
2779
	 *	@param     	string		$desc            	Description de la ligne
2780
	 *	@param     	double		$pu              	Prix unitaire
2781
	 *	@param     	double		$qty             	Quantity
2782
	 *	@param     	double		$remise_percent  	Percent discount on line
2783
	 *	@param     	double		$txtva          	VAT rate
2784
	 *  @param     	double		$txlocaltax1	    Localtax1 tax
2785
	 *  @param     	double		$txlocaltax2   		Localtax2 tax
2786
	 *  @param     	double		$price_base_type 	Type of price base
2787
	 *	@param		int			$info_bits			Miscellaneous informations
2788
	 *	@param		int			$type				Type of line (0=product, 1=service)
2789
	 *  @param		int			$notrigger			Disable triggers
2790
	 *  @param      integer     $date_start     	Date start of service
2791
	 *  @param      integer     $date_end       	Date end of service
2792
	 *  @param		array		$array_options		Extrafields array
2793
	 * 	@param 		string		$fk_unit 			Code of the unit to use. Null to use the default one
2794
	 * 	@param		double		$pu_ht_devise		Unit price in currency
2795
	 *  @param		string		$ref_supplier		Supplier ref
2796
	 *	@return    	int         	    			< 0 if error, > 0 if ok
2797
	 */
2798
	public function updateline($rowid, $desc, $pu, $qty, $remise_percent, $txtva, $txlocaltax1 = 0, $txlocaltax2 = 0, $price_base_type = 'HT', $info_bits = 0, $type = 0, $notrigger = 0, $date_start = '', $date_end = '', $array_options = 0, $fk_unit = null, $pu_ht_devise = 0, $ref_supplier = '')
2799
	{
2800
		global $mysoc, $conf, $langs;
2801
		dol_syslog(get_class($this)."::updateline $rowid, $desc, $pu, $qty, $remise_percent, $txtva, $price_base_type, $info_bits, $type, $fk_unit");
2802
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2803
2804
		$error = 0;
2805
2806
		if ($this->brouillon) {
2807
			// Clean parameters
2808
			if (empty($qty)) {
2809
				$qty = 0;
2810
			}
2811
			if (empty($info_bits)) {
2812
				$info_bits = 0;
2813
			}
2814
			if (empty($txtva)) {
2815
				$txtva = 0;
2816
			}
2817
			if (empty($txlocaltax1)) {
2818
				$txlocaltax1 = 0;
2819
			}
2820
			if (empty($txlocaltax2)) {
2821
				$txlocaltax2 = 0;
2822
			}
2823
			if (empty($remise)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $remise does not exist. Did you maybe mean $remise_percent?
Loading history...
2824
				$remise = 0;
2825
			}
2826
			if (empty($remise_percent)) {
2827
				$remise_percent = 0;
2828
			}
2829
2830
			$remise_percent = price2num($remise_percent);
2831
			$qty = price2num($qty);
2832
			if (!$qty) {
2833
				$qty = 1;
2834
			}
2835
			$pu = price2num($pu);
2836
			$pu_ht_devise = price2num($pu_ht_devise);
2837
			if (!preg_match('/\((.*)\)/', $txtva)) {
2838
				$txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
2839
			}
2840
			$txlocaltax1 = price2num($txlocaltax1);
2841
			$txlocaltax2 = price2num($txlocaltax2);
2842
2843
			// Check parameters
2844
			if ($type < 0) {
2845
				return -1;
2846
			}
2847
			if ($date_start && $date_end && $date_start > $date_end) {
2848
				$langs->load("errors");
2849
				$this->error = $langs->trans('ErrorStartDateGreaterEnd');
2850
				return -1;
2851
			}
2852
2853
			$this->db->begin();
2854
2855
			// Calcul du total TTC et de la TVA pour la ligne a partir de
2856
			// qty, pu, remise_percent et txtva
2857
			// TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2858
			// la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2859
2860
			$localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
2861
2862
			// Clean vat code
2863
			$reg = array();
2864
			$vat_src_code = '';
2865
			if (preg_match('/\((.*)\)/', $txtva, $reg)) {
2866
				$vat_src_code = $reg[1];
2867
				$txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
2868
			}
2869
2870
			$tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $this->thirdparty, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise);
2871
			$total_ht  = $tabprice[0];
2872
			$total_tva = $tabprice[1];
2873
			$total_ttc = $tabprice[2];
2874
			$total_localtax1 = $tabprice[9];
2875
			$total_localtax2 = $tabprice[10];
2876
			$pu_ht  = $tabprice[3];
2877
			$pu_tva = $tabprice[4];
2878
			$pu_ttc = $tabprice[5];
2879
2880
			// MultiCurrency
2881
			$multicurrency_total_ht = $tabprice[16];
2882
			$multicurrency_total_tva = $tabprice[17];
2883
			$multicurrency_total_ttc = $tabprice[18];
2884
			$pu_ht_devise = $tabprice[19];
2885
2886
			$localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
2887
			$localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
2888
2889
			//Fetch current line from the database and then clone the object and set it in $oldline property
2890
			$this->line = new CommandeFournisseurLigne($this->db);
2891
			$this->line->fetch($rowid);
2892
2893
			$oldline = clone $this->line;
2894
			$this->line->oldline = $oldline;
2895
2896
			$this->line->context = $this->context;
2897
2898
			$this->line->fk_commande = $this->id;
2899
			//$this->line->label=$label;
2900
			$this->line->desc = $desc;
2901
2902
			// redefine quantity according to packaging
2903
			if (!empty($conf->global->PRODUCT_USE_SUPPLIER_PACKAGING)) {
2904
				if ($qty < $this->line->packaging) {
2905
					$qty = $this->line->packaging;
2906
				} else {
2907
					if (!empty($this->line->packaging) && ($qty % $this->line->packaging) > 0) {
2908
						$coeff = intval($qty / $this->line->packaging) + 1;
2909
						$qty = $this->line->packaging * $coeff;
2910
						setEventMessage($langs->trans('QtyRecalculatedWithPackaging'), 'mesgs');
2911
					}
2912
				}
2913
			}
2914
2915
			$this->line->qty = $qty;
2916
			$this->line->ref_supplier = $ref_supplier;
2917
2918
			$this->line->vat_src_code = $vat_src_code;
2919
			$this->line->tva_tx         = $txtva;
2920
			$this->line->localtax1_tx   = $txlocaltax1;
2921
			$this->line->localtax2_tx   = $txlocaltax2;
2922
			$this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
2923
			$this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
2924
			$this->line->remise_percent = $remise_percent;
2925
			$this->line->subprice       = $pu_ht;
2926
			$this->line->rang           = $this->rang;
2927
			$this->line->info_bits      = $info_bits;
2928
			$this->line->total_ht       = $total_ht;
2929
			$this->line->total_tva      = $total_tva;
2930
			$this->line->total_localtax1 = $total_localtax1;
2931
			$this->line->total_localtax2 = $total_localtax2;
2932
			$this->line->total_ttc      = $total_ttc;
2933
			$this->line->product_type   = $type;
2934
			$this->line->special_code   = (!empty($this->special_code) ? $this->special_code : 0);
2935
			$this->line->origin         = $this->origin;
2936
			$this->line->fk_unit        = $fk_unit;
2937
2938
			$this->line->date_start     = $date_start;
2939
			$this->line->date_end       = $date_end;
2940
2941
			// Multicurrency
2942
			$this->line->fk_multicurrency = $this->fk_multicurrency;
2943
			$this->line->multicurrency_code = $this->multicurrency_code;
2944
			$this->line->multicurrency_subprice		= $pu_ht_devise;
2945
			$this->line->multicurrency_total_ht 	= $multicurrency_total_ht;
2946
			$this->line->multicurrency_total_tva 	= $multicurrency_total_tva;
2947
			$this->line->multicurrency_total_ttc 	= $multicurrency_total_ttc;
2948
2949
			$this->line->subprice = $pu_ht;
2950
			$this->line->price = $this->line->subprice;
2951
2952
			$this->line->remise_percent = $remise_percent;
2953
2954
			if (is_array($array_options) && count($array_options) > 0) {
2955
				// We replace values in this->line->array_options only for entries defined into $array_options
2956
				foreach ($array_options as $key => $value) {
2957
					$this->line->array_options[$key] = $array_options[$key];
2958
				}
2959
			}
2960
2961
			$result = $this->line->update($notrigger);
2962
2963
2964
			// Mise a jour info denormalisees au niveau facture
2965
			if ($result >= 0) {
2966
				$this->update_price('1', 'auto');
2967
				$this->db->commit();
2968
				return $result;
2969
			} else {
2970
				$this->error = $this->db->lasterror();
2971
				$this->db->rollback();
2972
				return -1;
2973
			}
2974
		} else {
2975
			$this->error = "Order status makes operation forbidden";
2976
			dol_syslog(get_class($this)."::updateline ".$this->error, LOG_ERR);
2977
			return -2;
2978
		}
2979
	}
2980
2981
2982
	/**
2983
	 *  Initialise an instance with random values.
2984
	 *  Used to build previews or test instances.
2985
	 *	id must be 0 if object instance is a specimen.
2986
	 *
2987
	 *  @return	void
2988
	 */
2989
	public function initAsSpecimen()
2990
	{
2991
		global $user, $langs, $conf;
2992
2993
		include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
2994
2995
		dol_syslog(get_class($this)."::initAsSpecimen");
2996
2997
		$now = dol_now();
2998
2999
		// Find first product
3000
		$prodid = 0;
3001
		$product = new ProductFournisseur($this->db);
3002
		$sql = "SELECT rowid";
3003
		$sql .= " FROM ".MAIN_DB_PREFIX."product";
3004
		$sql .= " WHERE entity IN (".getEntity('product').")";
3005
		$sql .= $this->db->order("rowid", "ASC");
3006
		$sql .= $this->db->plimit(1);
3007
		$resql = $this->db->query($sql);
3008
		if ($resql) {
3009
			$obj = $this->db->fetch_object($resql);
3010
			$prodid = $obj->rowid;
3011
		}
3012
3013
		// Initialise parametres
3014
		$this->id = 0;
3015
		$this->ref = 'SPECIMEN';
3016
		$this->specimen = 1;
3017
		$this->socid = 1;
3018
		$this->date = $now;
3019
		$this->date_commande = $now;
3020
		$this->date_lim_reglement = $this->date + 3600 * 24 * 30;
3021
		$this->cond_reglement_code = 'RECEP';
3022
		$this->mode_reglement_code = 'CHQ';
3023
3024
		$this->note_public = 'This is a comment (public)';
3025
		$this->note_private = 'This is a comment (private)';
3026
3027
		$this->multicurrency_tx = 1;
3028
		$this->multicurrency_code = $conf->currency;
3029
3030
		$this->statut = 0;
3031
3032
		// Lines
3033
		$nbp = 5;
3034
		$xnbp = 0;
3035
		while ($xnbp < $nbp) {
3036
			$line = new CommandeFournisseurLigne($this->db);
3037
			$line->desc = $langs->trans("Description")." ".$xnbp;
3038
			$line->qty = 1;
3039
			$line->subprice = 100;
3040
			$line->price = 100;
3041
			$line->tva_tx = 19.6;
3042
			$line->localtax1_tx = 0;
3043
			$line->localtax2_tx = 0;
3044
			if ($xnbp == 2) {
3045
				$line->total_ht = 50;
3046
				$line->total_ttc = 59.8;
3047
				$line->total_tva = 9.8;
3048
				$line->remise_percent = 50;
3049
			} else {
3050
				$line->total_ht = 100;
3051
				$line->total_ttc = 119.6;
3052
				$line->total_tva = 19.6;
3053
				$line->remise_percent = 00;
3054
			}
3055
			$line->fk_product = $prodid;
3056
3057
			$this->lines[$xnbp] = $line;
3058
3059
			$this->total_ht       += $line->total_ht;
3060
			$this->total_tva      += $line->total_tva;
3061
			$this->total_ttc      += $line->total_ttc;
3062
3063
			$xnbp++;
3064
		}
3065
	}
3066
3067
	/**
3068
	 *	Charge les informations d'ordre info dans l'objet facture
3069
	 *
3070
	 *	@param  int		$id       	Id de la facture a charger
3071
	 *	@return	void
3072
	 */
3073
	public function info($id)
3074
	{
3075
		$sql = 'SELECT c.rowid, date_creation as datec, tms as datem, date_valid as date_validation, date_approve as datea, date_approve2 as datea2,';
3076
		$sql .= ' fk_user_author, fk_user_modif, fk_user_valid, fk_user_approve, fk_user_approve2';
3077
		$sql .= ' FROM '.MAIN_DB_PREFIX.'commande_fournisseur as c';
3078
		$sql .= ' WHERE c.rowid = '.((int) $id);
3079
3080
		$result = $this->db->query($sql);
3081
		if ($result) {
3082
			if ($this->db->num_rows($result)) {
3083
				$obj = $this->db->fetch_object($result);
3084
				$this->id = $obj->rowid;
3085
				if ($obj->fk_user_author) {
3086
					$this->user_creation_id = $obj->fk_user_author;
3087
				}
3088
				if ($obj->fk_user_valid) {
3089
					$this->user_validation_id = $obj->fk_user_valid;
3090
				}
3091
				if ($obj->fk_user_modif) {
3092
					$this->user_modification_id = $obj->fk_user_modif;
3093
				}
3094
				if ($obj->fk_user_approve) {
3095
					$this->user_approve_id = $obj->fk_user_approve;
3096
				}
3097
				if ($obj->fk_user_approve2) {
3098
					$this->user_approve_id2 = $obj->fk_user_approve2;
3099
				}
3100
3101
				$this->date_creation     = $this->db->jdate($obj->datec);
3102
				$this->date_modification = $this->db->jdate($obj->datem);
3103
				$this->date_approve      = $this->db->jdate($obj->datea);
3104
				$this->date_approve2     = $this->db->jdate($obj->datea2);
3105
				$this->date_validation   = $this->db->jdate($obj->date_validation);
3106
			}
3107
			$this->db->free($result);
3108
		} else {
3109
			dol_print_error($this->db);
3110
		}
3111
	}
3112
3113
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3114
	/**
3115
	 *	Charge indicateurs this->nb de tableau de bord
3116
	 *
3117
	 *	@return     int         <0 si ko, >0 si ok
3118
	 */
3119
	public function load_state_board()
3120
	{
3121
		// phpcs:enable
3122
		global $conf, $user;
3123
3124
		$this->nb = array();
3125
		$clause = "WHERE";
3126
3127
		$sql = "SELECT count(co.rowid) as nb";
3128
		$sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseur as co";
3129
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON co.fk_soc = s.rowid";
3130
		if (empty($user->rights->societe->client->voir) && !$user->socid) {
3131
			$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3132
			$sql .= " WHERE sc.fk_user = ".((int) $user->id);
3133
			$clause = "AND";
3134
		}
3135
		$sql .= " ".$clause." co.entity IN (".getEntity('supplier_order').")";
3136
3137
		$resql = $this->db->query($sql);
3138
		if ($resql) {
3139
			while ($obj = $this->db->fetch_object($resql)) {
3140
				$this->nb["supplier_orders"] = $obj->nb;
3141
			}
3142
			$this->db->free($resql);
3143
			return 1;
3144
		} else {
3145
			dol_print_error($this->db);
3146
			$this->error = $this->db->error();
3147
			return -1;
3148
		}
3149
	}
3150
3151
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3152
	/**
3153
	 *	Load indicators for dashboard (this->nbtodo and this->nbtodolate)
3154
	 *
3155
	 *	@param          User	$user   Objet user
3156
	 *  @param          int		$mode   "opened", "awaiting" for orders awaiting reception
3157
	 *	@return WorkboardResponse|int 	<0 if KO, WorkboardResponse if OK
3158
	 */
3159
	public function load_board($user, $mode = 'opened')
3160
	{
3161
		// phpcs:enable
3162
		global $conf, $langs;
3163
3164
		$sql = "SELECT c.rowid, c.date_creation as datec, c.date_commande, c.fk_statut, c.date_livraison as delivery_date, c.total_ht";
3165
		$sql .= " FROM ".MAIN_DB_PREFIX."commande_fournisseur as c";
3166
		if (empty($user->rights->societe->client->voir) && !$user->socid) {
3167
			$sql .= " JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON c.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
3168
		}
3169
		$sql .= " WHERE c.entity = ".$conf->entity;
3170
		if ($mode === 'awaiting') {
3171
			$sql .= " AND c.fk_statut IN (".self::STATUS_ORDERSENT.", ".self::STATUS_RECEIVED_PARTIALLY.")";
3172
		} else {
3173
			$sql .= " AND c.fk_statut IN (".self::STATUS_VALIDATED.", ".self::STATUS_ACCEPTED.")";
3174
		}
3175
		if ($user->socid) {
3176
			$sql .= " AND c.fk_soc = ".((int) $user->socid);
3177
		}
3178
3179
		$resql = $this->db->query($sql);
3180
		if ($resql) {
3181
			$commandestatic = new CommandeFournisseur($this->db);
3182
3183
			$response = new WorkboardResponse();
3184
			$response->warning_delay = $conf->commande->fournisseur->warning_delay / 60 / 60 / 24;
3185
			$response->label = $langs->trans("SuppliersOrdersToProcess");
3186
			$response->labelShort = $langs->trans("Opened");
3187
			$response->url = DOL_URL_ROOT.'/fourn/commande/list.php?search_status=1,2&mainmenu=commercial&leftmenu=orders_suppliers';
3188
			$response->img = img_object('', "order");
3189
3190
			if ($mode === 'awaiting') {
3191
				$response->label = $langs->trans("SuppliersOrdersAwaitingReception");
3192
				$response->labelShort = $langs->trans("AwaitingReception");
3193
				$response->url = DOL_URL_ROOT.'/fourn/commande/list.php?search_status=3,4&mainmenu=commercial&leftmenu=orders_suppliers';
3194
			}
3195
3196
			while ($obj = $this->db->fetch_object($resql)) {
3197
				$commandestatic->delivery_date = $this->db->jdate($obj->delivery_date);
3198
				$commandestatic->date_commande = $this->db->jdate($obj->date_commande);
3199
				$commandestatic->statut = $obj->fk_statut;
3200
3201
				$response->nbtodo++;
3202
				$response->total += $obj->total_ht;
3203
3204
				if ($commandestatic->hasDelay()) {
3205
					$response->nbtodolate++;
3206
				}
3207
			}
3208
3209
			return $response;
3210
		} else {
3211
			$this->error = $this->db->error();
3212
			return -1;
3213
		}
3214
	}
3215
3216
	/**
3217
	 * Returns the translated input method of object (defined if $this->methode_commande_id > 0).
3218
	 * This function make a sql request to get translation. No cache yet, try to not use it inside a loop.
3219
	 *
3220
	 * @return string
3221
	 */
3222
	public function getInputMethod()
3223
	{
3224
		global $db, $langs;
3225
3226
		if ($this->methode_commande_id > 0) {
3227
			$sql = "SELECT rowid, code, libelle as label";
3228
			$sql .= " FROM ".MAIN_DB_PREFIX.'c_input_method';
3229
			$sql .= " WHERE active=1 AND rowid = ".((int) $this->methode_commande_id);
3230
3231
			$resql = $this->db->query($sql);
3232
			if ($resql) {
3233
				if ($this->db->num_rows($resql)) {
3234
					$obj = $this->db->fetch_object($resql);
3235
3236
					$string = $langs->trans($obj->code);
3237
					if ($string == $obj->code) {
3238
						$string = $obj->label != '-' ? $obj->label : '';
3239
					}
3240
					return $string;
3241
				}
3242
			} else {
3243
				dol_print_error($this->db);
3244
			}
3245
		}
3246
3247
		return '';
3248
	}
3249
3250
	/**
3251
	 *  Create a document onto disk according to template model.
3252
	 *
3253
	 *  @param	    string		$modele			Force template to use ('' to not force)
3254
	 *  @param		Translate	$outputlangs	Object lang to use for traduction
3255
	 *  @param      int			$hidedetails    Hide details of lines
3256
	 *  @param      int			$hidedesc       Hide description
3257
	 *  @param      int			$hideref        Hide ref
3258
	 *  @param      null|array  $moreparams     Array to provide more information
3259
	 *  @return     int          				< 0 if KO, 0 = no doc generated, > 0 if OK
3260
	 */
3261
	public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3262
	{
3263
		global $conf, $langs;
3264
3265
		if (!dol_strlen($modele)) {
3266
			$modele = '';	// No doc template/generation by default
3267
3268
			if (!empty($this->model_pdf)) {
3269
				$modele = $this->model_pdf;
3270
			} elseif (!empty($conf->global->COMMANDE_SUPPLIER_ADDON_PDF)) {
3271
				$modele = $conf->global->COMMANDE_SUPPLIER_ADDON_PDF;
3272
			}
3273
		}
3274
3275
		if (empty($modele)) {
3276
			return 0;
3277
		} else {
3278
			$langs->load("suppliers");
3279
			$outputlangs->load("products");
3280
3281
			$modelpath = "core/modules/supplier_order/doc/";
3282
			return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3283
		}
3284
	}
3285
3286
	/**
3287
	 * Return the max number delivery delay in day
3288
	 *
3289
	 * @param	Translate	$langs		Language object
3290
	 * @return 	string                  Translated string
3291
	 */
3292
	public function getMaxDeliveryTimeDay($langs)
3293
	{
3294
		if (empty($this->lines)) {
3295
			return '';
3296
		}
3297
3298
		$obj = new ProductFournisseur($this->db);
3299
3300
		$nb = 0;
3301
		foreach ($this->lines as $line) {
3302
			if ($line->fk_product > 0) {
3303
				$idp = $obj->find_min_price_product_fournisseur($line->fk_product, $line->qty);
3304
				if ($idp) {
3305
					$obj->fetch($idp);
3306
					if ($obj->delivery_time_days > $nb) {
3307
						$nb = $obj->delivery_time_days;
3308
					}
3309
				}
3310
			}
3311
		}
3312
3313
		if ($nb === 0) {
3314
			return '';
3315
		} else {
3316
			return $nb.' '.$langs->trans('Days');
3317
		}
3318
	}
3319
3320
	/**
3321
	 * Returns the rights used for this class
3322
	 * @return stdClass
3323
	 */
3324
	public function getRights()
3325
	{
3326
		global $user;
3327
3328
		return $user->rights->fournisseur->commande;
3329
	}
3330
3331
3332
	/**
3333
	 * Function used to replace a thirdparty id with another one.
3334
	 *
3335
	 * @param 	DoliDB 	$dbs 		Database handler, because function is static we name it $dbs not $db to avoid breaking coding test
3336
	 * @param 	int 	$origin_id 	Old thirdparty id
3337
	 * @param 	int 	$dest_id 	New thirdparty id
3338
	 * @return 	bool
3339
	 */
3340
	public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
3341
	{
3342
		$tables = array(
3343
			'commande_fournisseur'
3344
		);
3345
3346
		return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
3347
	}
3348
3349
	/**
3350
	 * Function used to replace a product id with another one.
3351
	 *
3352
	 * @param DoliDB $db Database handler
3353
	 * @param int $origin_id Old product id
3354
	 * @param int $dest_id New product id
3355
	 * @return bool
3356
	 */
3357
	public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
3358
	{
3359
		$tables = array(
3360
			'commande_fournisseurdet'
3361
		);
3362
3363
		return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
3364
	}
3365
3366
	/**
3367
	 * Is the supplier order delayed?
3368
	 * We suppose a purchase ordered as late if a the purchase order has been sent and the delivery date is set and before the delay.
3369
	 * If order has not been sent, we use the order date.
3370
	 *
3371
	 * @return 	bool					True if object is delayed
3372
	 */
3373
	public function hasDelay()
3374
	{
3375
		global $conf;
3376
3377
		if (empty($this->delivery_date) && !empty($this->date_livraison)) {
3378
			$this->delivery_date = $this->date_livraison; // For backward compatibility
3379
		}
3380
3381
		if ($this->statut == self::STATUS_ORDERSENT || $this->statut == self::STATUS_RECEIVED_PARTIALLY) {
3382
			$now = dol_now();
3383
			if (!empty($this->delivery_date)) {
3384
				$date_to_test = $this->delivery_date;
3385
				return $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3386
			} else {
3387
				//$date_to_test = $this->date_commande;
3388
				//return $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3389
				return false;
3390
			}
3391
		} else {
3392
			$now = dol_now();
3393
			$date_to_test = $this->date_commande;
3394
3395
			return ($this->statut > 0 && $this->statut < 5) && $date_to_test && $date_to_test < ($now - $conf->commande->fournisseur->warning_delay);
3396
		}
3397
	}
3398
3399
	/**
3400
	 * Show the customer delayed info.
3401
	 * We suppose a purchase ordered as late if a the purchase order has been sent and the delivery date is set and before the delay.
3402
	 * If order has not been sent, we use the order date.
3403
	 *
3404
	 * @return string       Show delayed information
3405
	 */
3406
	public function showDelay()
3407
	{
3408
		global $conf, $langs;
3409
3410
		if (empty($this->delivery_date) && !empty($this->date_livraison)) {
3411
			$this->delivery_date = $this->date_livraison; // For backward compatibility
3412
		}
3413
3414
		$text = '';
3415
3416
		if ($this->statut == self::STATUS_ORDERSENT || $this->statut == self::STATUS_RECEIVED_PARTIALLY) {
3417
			if (!empty($this->delivery_date)) {
3418
				$text = $langs->trans("DeliveryDate").' '.dol_print_date($this->delivery_date, 'day');
3419
			} else {
3420
				$text = $langs->trans("OrderDate").' '.dol_print_date($this->date_commande, 'day');
3421
			}
3422
		} else {
3423
			$text = $langs->trans("OrderDate").' '.dol_print_date($this->date_commande, 'day');
3424
		}
3425
		if ($text) {
3426
			$text .= ' '.($conf->commande->fournisseur->warning_delay > 0 ? '+' : '-').' '.round(abs($conf->commande->fournisseur->warning_delay) / 3600 / 24, 1).' '.$langs->trans("days").' < '.$langs->trans("Today");
3427
		}
3428
3429
		return $text;
3430
	}
3431
3432
3433
	/**
3434
	 * Calc status regarding to dispatched stock
3435
	 *
3436
	 * @param 		User 	$user                   User action
3437
	 * @param       int     $closeopenorder         Close if received
3438
	 * @param		string	$comment				Comment
3439
	 * @return		int		                        <0 if KO, 0 if not applicable, >0 if OK
3440
	 */
3441
	public function calcAndSetStatusDispatch(User $user, $closeopenorder = 1, $comment = '')
3442
	{
3443
		global $conf, $langs;
3444
3445
		if ((isModEnabled("fournisseur") && empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD)) || isModEnabled("supplier_order")) {
3446
			require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.dispatch.class.php';
3447
3448
			$qtydelivered = array();
3449
			$qtywished = array();
3450
3451
			$supplierorderdispatch = new CommandeFournisseurDispatch($this->db);
3452
			$filter = array('t.fk_commande'=>$this->id);
3453
			if (!empty($conf->global->SUPPLIER_ORDER_USE_DISPATCH_STATUS)) {
3454
				$filter['t.status'] = 1; // Restrict to lines with status validated
3455
			}
3456
3457
			$ret = $supplierorderdispatch->fetchAll('', '', 0, 0, $filter);
3458
			if ($ret < 0) {
3459
				$this->error = $supplierorderdispatch->error; $this->errors = $supplierorderdispatch->errors;
3460
				return $ret;
3461
			} else {
3462
				if (is_array($supplierorderdispatch->lines) && count($supplierorderdispatch->lines) > 0) {
3463
					require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
3464
					$date_liv = dol_now();
3465
3466
					// Build array with quantity deliverd by product
3467
					foreach ($supplierorderdispatch->lines as $line) {
3468
						$qtydelivered[$line->fk_product] += $line->qty;
3469
					}
3470
					foreach ($this->lines as $line) {
3471
						// Exclude lines not qualified for shipment, similar code is found into interface_20_modWrokflow for customers
3472
						if (empty($conf->global->STOCK_SUPPORTS_SERVICES) && $line->product_type > 0) {
3473
							continue;
3474
						}
3475
						$qtywished[$line->fk_product] += $line->qty;
3476
					}
3477
3478
					//Compare array
3479
					$diff_array = array_diff_assoc($qtydelivered, $qtywished); // Warning: $diff_array is done only on common keys.
3480
					$keysinwishednotindelivered = array_diff(array_keys($qtywished), array_keys($qtydelivered)); // To check we also have same number of keys
3481
					$keysindeliverednotinwished = array_diff(array_keys($qtydelivered), array_keys($qtywished)); // To check we also have same number of keys
3482
					//var_dump(array_keys($qtydelivered));
3483
					//var_dump(array_keys($qtywished));
3484
					//var_dump($diff_array);
3485
					//var_dump($keysinwishednotindelivered);
3486
					//var_dump($keysindeliverednotinwished);
3487
					//exit;
3488
3489
					if (count($diff_array) == 0 && count($keysinwishednotindelivered) == 0 && count($keysindeliverednotinwished) == 0) { //No diff => mean everythings is received
3490
						if ($closeopenorder) {
3491
							//$ret=$this->setStatus($user,5);
3492
							$ret = $this->Livraison($user, $date_liv, 'tot', $comment); // GETPOST("type") is 'tot', 'par', 'nev', 'can'
3493
							if ($ret < 0) {
3494
								return -1;
3495
							}
3496
							return 5;
3497
						} else {
3498
							//Diff => received partially
3499
							//$ret=$this->setStatus($user,4);
3500
							$ret = $this->Livraison($user, $date_liv, 'par', $comment); // GETPOST("type") is 'tot', 'par', 'nev', 'can'
3501
							if ($ret < 0) {
3502
								return -1;
3503
							}
3504
							return 4;
3505
						}
3506
					} elseif (!empty($conf->global->SUPPLIER_ORDER_MORE_THAN_WISHED)) {
3507
						//set livraison to 'tot' if more products received than wished. (and if $closeopenorder is set to 1 of course...)
3508
3509
						$close = 0;
3510
3511
						if (count($diff_array) > 0) {
3512
							//there are some difference between  the two arrays
3513
3514
							//scan the array of results
3515
							foreach ($diff_array as $key => $value) {
3516
								//if the quantity delivered is greater or equal to wish quantity
3517
								if ($qtydelivered[$key] >= $qtywished[$key]) {
3518
									$close++;
3519
								}
3520
							}
3521
						}
3522
3523
3524
						if ($close == count($diff_array)) {
3525
							//all the products are received equal or more than the wished quantity
3526
							if ($closeopenorder) {
3527
								$ret = $this->Livraison($user, $date_liv, 'tot', $comment); // GETPOST("type") is 'tot', 'par', 'nev', 'can'
3528
								if ($ret < 0) {
3529
									return -1;
3530
								}
3531
								return 5;
3532
							} else {
3533
								//Diff => received partially
3534
								$ret = $this->Livraison($user, $date_liv, 'par', $comment); // GETPOST("type") is 'tot', 'par', 'nev', 'can'
3535
								if ($ret < 0) {
3536
									return -1;
3537
								}
3538
								return 4;
3539
							}
3540
						} else {
3541
							//all the products are not received
3542
							$ret = $this->Livraison($user, $date_liv, 'par', $comment); // GETPOST("type") is 'tot', 'par', 'nev', 'can'
3543
							if ($ret < 0) {
3544
								return -1;
3545
							}
3546
							return 4;
3547
						}
3548
					} else {
3549
						//Diff => received partially
3550
						$ret = $this->Livraison($user, $date_liv, 'par', $comment); // GETPOST("type") is 'tot', 'par', 'nev', 'can'
3551
						if ($ret < 0) {
3552
							return -1;
3553
						}
3554
						return 4;
3555
					}
3556
				}
3557
				return 1;
3558
			}
3559
		}
3560
		return 0;
3561
	}
3562
3563
	/**
3564
	 *	Load array this->receptions of lines of shipments with nb of products sent for each order line
3565
	 *  Note: For a dedicated shipment, the fetch_lines can be used to load the qty_asked and qty_shipped. This function is use to return qty_shipped cumulated for the order
3566
	 *
3567
	 *	@param      int		$filtre_statut      Filter on shipment status
3568
	 * 	@return     int                			<0 if KO, Nb of lines found if OK
3569
	 */
3570
	public function loadReceptions($filtre_statut = -1)
3571
	{
3572
		$this->receptions = array();
3573
3574
		dol_syslog(get_class($this)."::loadReceptions", LOG_DEBUG);
3575
3576
		$sql = 'SELECT cd.rowid, cd.fk_product,';
3577
		$sql .= ' sum(cfd.qty) as qty';
3578
		$sql .= ' FROM '.MAIN_DB_PREFIX.'commande_fournisseur_dispatch as cfd,';
3579
		if ($filtre_statut >= 0) {
3580
			$sql .= ' '.MAIN_DB_PREFIX.'reception as e,';
3581
		}
3582
		$sql .= ' '.MAIN_DB_PREFIX.'commande_fournisseurdet as cd';
3583
		$sql .= ' WHERE';
3584
		if ($filtre_statut >= 0) {
3585
			$sql .= ' cfd.fk_reception = e.rowid AND';
3586
		}
3587
		$sql .= ' cfd.fk_commandefourndet = cd.rowid';
3588
		$sql .= ' AND cd.fk_commande ='.((int) $this->id);
3589
		if ($this->fk_product > 0) {
3590
			$sql .= ' AND cd.fk_product = '.((int) $this->fk_product);
3591
		}
3592
		if ($filtre_statut >= 0) {
3593
			$sql .= ' AND e.fk_statut >= '.((int) $filtre_statut);
3594
		}
3595
		$sql .= ' GROUP BY cd.rowid, cd.fk_product';
3596
3597
		$resql = $this->db->query($sql);
3598
		if ($resql) {
3599
			$num = $this->db->num_rows($resql);
3600
			$i = 0;
3601
			while ($i < $num) {
3602
				$obj = $this->db->fetch_object($resql);
3603
				empty($this->receptions[$obj->rowid]) ? $this->receptions[$obj->rowid] = $obj->qty : $this->receptions[$obj->rowid] += $obj->qty;
3604
				$i++;
3605
			}
3606
			$this->db->free($resql);
3607
3608
			return $num;
3609
		} else {
3610
			$this->error = $this->db->lasterror();
3611
			return -1;
3612
		}
3613
	}
3614
3615
	/**
3616
	 *	Return clicable link of object (with eventually picto)
3617
	 *
3618
	 *	@param      string	    $option                 Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
3619
	 *  @param		array		$arraydata				Array of data
3620
	 *  @return		string								HTML Code for Kanban thumb.
3621
	 */
3622
	public function getKanbanView($option = '', $arraydata = null)
3623
	{
3624
		global $langs;
3625
		$return = '<div class="box-flex-item box-flex-grow-zero">';
3626
		$return .= '<div class="info-box info-box-sm">';
3627
		$return .= '<span class="info-box-icon bg-infobox-action">';
3628
		$return .= img_picto('', $this->picto);
3629
		//$return .= '<i class="fa fa-dol-action"></i>'; // Can be image
3630
		$return .= '</span>';
3631
		$return .= '<div class="info-box-content">';
3632
		$return .= '<span class="info-box-ref">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
3633
		if (property_exists($this, 'socid') || property_exists($this, 'total_tva')) {
3634
			$return .='<br><span class="info-box-label amount">'.$this->socid.'</span>';
3635
		}
3636
		if (property_exists($this, 'billed')) {
3637
			$return .= '<br><span class="opacitymedium">'.$langs->trans("Billed").' : </span><span class="info-box-label">'.yn($this->billed).'</span>';
3638
		}
3639
		if (method_exists($this, 'getLibStatut')) {
3640
			$return .= '<br><div class="info-box-status margintoponly">'.$this->getLibStatut(5).'</div>';
3641
		}
3642
		$return .= '</div>';
3643
		$return .= '</div>';
3644
		$return .= '</div>';
3645
		return $return;
3646
	}
3647
}
3648
3649
3650
3651
/**
3652
 *  Class to manage line orders
3653
 */
3654
class CommandeFournisseurLigne extends CommonOrderLine
3655
{
3656
	/**
3657
	 * @var string ID to identify managed object
3658
	 */
3659
	public $element = 'commande_fournisseurdet';
3660
3661
	/**
3662
	 * @var string Name of table without prefix where object is stored
3663
	 */
3664
	public $table_element = 'commande_fournisseurdet';
3665
3666
	public $oldline;
3667
3668
	/**
3669
	 * Id of parent order
3670
	 * @var int
3671
	 */
3672
	public $fk_commande;
3673
3674
	// From llx_commande_fournisseurdet
3675
	/**
3676
	 * @var int ID
3677
	 */
3678
	public $fk_parent_line;
3679
3680
	/**
3681
	 * @var int ID
3682
	 */
3683
	public $fk_facture;
3684
3685
	public $rang = 0;
3686
	public $special_code = 0;
3687
3688
	/**
3689
	 * Unit price without taxes
3690
	 * @var float
3691
	 */
3692
	public $pu_ht;
3693
3694
	public $date_start;
3695
	public $date_end;
3696
3697
	// From llx_product_fournisseur_price
3698
3699
	/**
3700
	 * Supplier reference of price when we added the line. May have been changed after line was added.
3701
	 * @var string
3702
	 */
3703
	public $ref_supplier;
3704
3705
	/**
3706
	 * @var string ref supplier
3707
	 * @deprecated
3708
	 * @see $ref_supplier
3709
	 */
3710
	public $ref_fourn;
3711
3712
	public $remise;
3713
3714
3715
	/**
3716
	 *	Constructor
3717
	 *
3718
	 *  @param		DoliDB		$db      Database handler
3719
	 */
3720
	public function __construct($db)
3721
	{
3722
		$this->db = $db;
3723
	}
3724
3725
	/**
3726
	 *  Load line order
3727
	 *
3728
	 *  @param  int		$rowid      Id line order
3729
	 *	@return	int					<0 if KO, >0 if OK
3730
	 */
3731
	public function fetch($rowid)
3732
	{
3733
		global $conf;
3734
3735
		$sql = 'SELECT cd.rowid, cd.fk_commande, cd.fk_product, cd.product_type, cd.description, cd.qty, cd.tva_tx, cd.special_code,';
3736
		$sql .= ' cd.localtax1_tx, cd.localtax2_tx, cd.localtax1_type, cd.localtax2_type, cd.ref as ref_supplier,';
3737
		$sql .= ' cd.remise, cd.remise_percent, cd.subprice,';
3738
		$sql .= ' cd.info_bits, cd.total_ht, cd.total_tva, cd.total_ttc,';
3739
		$sql .= ' cd.total_localtax1, cd.total_localtax2,';
3740
		$sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc,';
3741
		$sql .= ' cd.date_start, cd.date_end, cd.fk_unit,';
3742
		$sql .= ' cd.multicurrency_subprice, cd.multicurrency_total_ht, cd.multicurrency_total_tva, cd.multicurrency_total_ttc,';
3743
		$sql .= ' c.fk_soc as socid';
3744
		$sql .= ' FROM '.MAIN_DB_PREFIX.'commande_fournisseur as c, '.MAIN_DB_PREFIX.'commande_fournisseurdet as cd';
3745
		$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON cd.fk_product = p.rowid';
3746
		$sql .= ' WHERE cd.fk_commande = c.rowid AND cd.rowid = '.((int) $rowid);
3747
3748
		$result = $this->db->query($sql);
3749
		if ($result) {
3750
			$objp = $this->db->fetch_object($result);
3751
3752
			if (!empty($objp)) {
3753
				$this->rowid = $objp->rowid;
3754
				$this->id               = $objp->rowid;
3755
				$this->fk_commande      = $objp->fk_commande;
3756
				$this->desc             = $objp->description;
3757
				$this->qty              = $objp->qty;
3758
				$this->ref_fourn        = $objp->ref_supplier;
3759
				$this->ref_supplier     = $objp->ref_supplier;
3760
				$this->subprice         = $objp->subprice;
3761
				$this->tva_tx           = $objp->tva_tx;
3762
				$this->localtax1_tx		= $objp->localtax1_tx;
3763
				$this->localtax2_tx		= $objp->localtax2_tx;
3764
				$this->localtax1_type	= $objp->localtax1_type;
3765
				$this->localtax2_type	= $objp->localtax2_type;
3766
				$this->remise           = $objp->remise;
3767
				$this->remise_percent   = $objp->remise_percent;
3768
				$this->fk_product       = $objp->fk_product;
3769
				$this->info_bits        = $objp->info_bits;
3770
				$this->total_ht         = $objp->total_ht;
3771
				$this->total_tva        = $objp->total_tva;
3772
				$this->total_localtax1	= $objp->total_localtax1;
3773
				$this->total_localtax2	= $objp->total_localtax2;
3774
				$this->total_ttc        = $objp->total_ttc;
3775
				$this->product_type     = $objp->product_type;
3776
				$this->special_code     = $objp->special_code;
3777
3778
				$this->ref = $objp->product_ref;
3779
3780
				$this->product_ref      = $objp->product_ref;
3781
				$this->product_label    = $objp->product_label;
3782
				$this->product_desc     = $objp->product_desc;
3783
3784
				if (!empty($conf->global->PRODUCT_USE_SUPPLIER_PACKAGING)) {
3785
					// TODO We should not fetch this properties into the fetch_lines. This is NOT properties of a line.
3786
					// Move this into another method and call it when required.
3787
3788
					// Take better packaging for $objp->qty (first supplier ref quantity <= $objp->qty)
3789
					$sqlsearchpackage = 'SELECT rowid, packaging FROM '.MAIN_DB_PREFIX."product_fournisseur_price";
3790
					$sqlsearchpackage .= ' WHERE entity IN ('.getEntity('product_fournisseur_price').")";
3791
					$sqlsearchpackage .= " AND fk_product = ".((int) $objp->fk_product);
3792
					$sqlsearchpackage .= " AND ref_fourn = '".$this->db->escape($objp->ref_supplier)."'";
3793
					$sqlsearchpackage .= " AND quantity <= ".((float) $objp->qty);	// required to be qualified
3794
					$sqlsearchpackage .= " AND (packaging IS NULL OR packaging = 0 OR packaging <= ".((float) $objp->qty).")";	// required to be qualified
3795
					$sqlsearchpackage .= " AND fk_soc = ".((int) $objp->socid);
3796
					$sqlsearchpackage .= " ORDER BY packaging ASC";		// Take the smaller package first
3797
					$sqlsearchpackage .= " LIMIT 1";
3798
3799
					$resqlsearchpackage = $this->db->query($sqlsearchpackage);
3800
					if ($resqlsearchpackage) {
3801
						$objsearchpackage = $this->db->fetch_object($resqlsearchpackage);
3802
						if ($objsearchpackage) {
3803
							$this->fk_fournprice = $objsearchpackage->rowid;
3804
							$this->packaging     = $objsearchpackage->packaging;
3805
						}
3806
					} else {
3807
						$this->error = $this->db->lasterror();
3808
						return -1;
3809
					}
3810
				}
3811
3812
				$this->date_start       		= $this->db->jdate($objp->date_start);
3813
				$this->date_end         		= $this->db->jdate($objp->date_end);
3814
				$this->fk_unit = $objp->fk_unit;
3815
3816
				$this->multicurrency_subprice	= $objp->multicurrency_subprice;
3817
				$this->multicurrency_total_ht	= $objp->multicurrency_total_ht;
3818
				$this->multicurrency_total_tva	= $objp->multicurrency_total_tva;
3819
				$this->multicurrency_total_ttc	= $objp->multicurrency_total_ttc;
3820
3821
				$this->fetch_optionals();
3822
3823
				$this->db->free($result);
3824
				return 1;
3825
			} else {
3826
				$this->error = 'Supplier order line  with id='.$rowid.' not found';
3827
				dol_syslog(get_class($this)."::fetch Error ".$this->error, LOG_ERR);
3828
				return 0;
3829
			}
3830
		} else {
3831
			dol_print_error($this->db);
3832
			return -1;
3833
		}
3834
	}
3835
3836
	/**
3837
	 *	Insert line into database
3838
	 *
3839
	 *	@param      int		$notrigger		1 = disable triggers
3840
	 *	@return		int						<0 if KO, >0 if OK
3841
	 */
3842
	public function insert($notrigger = 0)
3843
	{
3844
		global $conf, $user;
3845
3846
		$error = 0;
3847
3848
		dol_syslog(get_class($this)."::insert rang=".$this->rang);
3849
3850
		// Clean parameters
3851
		if (empty($this->tva_tx)) {
3852
			$this->tva_tx = 0;
3853
		}
3854
		if (empty($this->localtax1_tx)) {
3855
			$this->localtax1_tx = 0;
3856
		}
3857
		if (empty($this->localtax2_tx)) {
3858
			$this->localtax2_tx = 0;
3859
		}
3860
		if (empty($this->localtax1_type)) {
3861
			$this->localtax1_type = '0';
3862
		}
3863
		if (empty($this->localtax2_type)) {
3864
			$this->localtax2_type = '0';
3865
		}
3866
		if (empty($this->total_localtax1)) {
3867
			$this->total_localtax1 = 0;
3868
		}
3869
		if (empty($this->total_localtax2)) {
3870
			$this->total_localtax2 = 0;
3871
		}
3872
		if (empty($this->rang)) {
3873
			$this->rang = 0;
3874
		}
3875
		if (empty($this->remise_percent)) {
3876
			$this->remise_percent = 0;
3877
		}
3878
		if (empty($this->info_bits)) {
3879
			$this->info_bits = 0;
3880
		}
3881
		if (empty($this->special_code)) {
3882
			$this->special_code = 0;
3883
		}
3884
		if (empty($this->fk_parent_line)) {
3885
			$this->fk_parent_line = 0;
3886
		}
3887
		if (empty($this->pa_ht)) {
3888
			$this->pa_ht = 0;
3889
		}
3890
3891
		// Multicurrency
3892
		if (!empty($this->multicurrency_code)) {
3893
			list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code);
3894
		}
3895
		if (empty($this->fk_multicurrency)) {
3896
			$this->multicurrency_code = $conf->currency;
3897
			$this->fk_multicurrency = 0;
3898
			$this->multicurrency_tx = 1;
3899
		}
3900
3901
		// Check parameters
3902
		if ($this->product_type < 0) {
3903
			return -1;
3904
		}
3905
3906
		$this->db->begin();
3907
3908
		// Insertion dans base de la ligne
3909
		$sql = 'INSERT INTO '.MAIN_DB_PREFIX.$this->table_element;
3910
		$sql .= " (fk_commande, label, description, date_start, date_end,";
3911
		$sql .= " fk_product, product_type, special_code, rang,";
3912
		$sql .= " qty, vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type, remise_percent, subprice, ref,";
3913
		$sql .= " total_ht, total_tva, total_localtax1, total_localtax2, total_ttc, fk_unit,";
3914
		$sql .= " fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc";
3915
		$sql .= ")";
3916
		$sql .= " VALUES (".$this->fk_commande.", '".$this->db->escape($this->label)."','".$this->db->escape($this->desc)."',";
3917
		$sql .= " ".($this->date_start ? "'".$this->db->idate($this->date_start)."'" : "null").",";
3918
		$sql .= " ".($this->date_end ? "'".$this->db->idate($this->date_end)."'" : "null").",";
3919
		if ($this->fk_product) {
3920
			$sql .= $this->fk_product.",";
3921
		} else {
3922
			$sql .= "null,";
3923
		}
3924
		$sql .= "'".$this->db->escape($this->product_type)."',";
3925
		$sql .= "'".$this->db->escape($this->special_code)."',";
3926
		$sql .= "'".$this->db->escape($this->rang)."',";
3927
		$sql .= "'".$this->db->escape($this->qty)."', ";
3928
		$sql .= " ".(empty($this->vat_src_code) ? "''" : "'".$this->db->escape($this->vat_src_code)."'").",";
3929
		$sql .= " ".price2num($this->tva_tx).", ";
3930
		$sql .= " ".price2num($this->localtax1_tx).",";
3931
		$sql .= " ".price2num($this->localtax2_tx).",";
3932
		$sql .= " '".$this->db->escape($this->localtax1_type)."',";
3933
		$sql .= " '".$this->db->escape($this->localtax2_type)."',";
3934
		$sql .= " ".((float) $this->remise_percent).", ".price2num($this->subprice, 'MU').", '".$this->db->escape($this->ref_supplier)."',";
3935
		$sql .= " ".price2num($this->total_ht).",";
3936
		$sql .= " ".price2num($this->total_tva).",";
3937
		$sql .= " ".price2num($this->total_localtax1).",";
3938
		$sql .= " ".price2num($this->total_localtax2).",";
3939
		$sql .= " ".price2num($this->total_ttc).",";
3940
		$sql .= ($this->fk_unit ? "'".$this->db->escape($this->fk_unit)."'" : "null");
3941
		$sql .= ", ".($this->fk_multicurrency ? ((int) $this->fk_multicurrency) : "null");
3942
		$sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
3943
		$sql .= ", ".($this->multicurrency_subprice ? price2num($this->multicurrency_subprice) : '0');
3944
		$sql .= ", ".($this->multicurrency_total_ht ? price2num($this->multicurrency_total_ht) : '0');
3945
		$sql .= ", ".($this->multicurrency_total_tva ? price2num($this->multicurrency_total_tva) : '0');
3946
		$sql .= ", ".($this->multicurrency_total_ttc ? price2num($this->multicurrency_total_ttc) : '0');
3947
		$sql .= ")";
3948
3949
		dol_syslog(get_class($this)."::insert", LOG_DEBUG);
3950
		$resql = $this->db->query($sql);
3951
		if ($resql) {
3952
			$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
3953
			$this->rowid = $this->id;
3954
3955
			if (!$error) {
3956
				$result = $this->insertExtraFields();
3957
				if ($result < 0) {
3958
					$error++;
3959
				}
3960
			}
3961
3962
			if (!$error && !$notrigger) {
3963
				// Call trigger
3964
				$result = $this->call_trigger('LINEORDER_SUPPLIER_CREATE', $user);
3965
				if ($result < 0) {
3966
					$error++;
3967
				}
3968
				// End call triggers
3969
			}
3970
3971
			if (!$error) {
3972
				$this->db->commit();
3973
				return 1;
3974
			}
3975
3976
			foreach ($this->errors as $errmsg) {
3977
				dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
3978
				$this->errors[] = ($this->errors ? ', '.$errmsg : $errmsg);
3979
			}
3980
			$this->db->rollback();
3981
			return -1 * $error;
3982
		} else {
3983
			$this->errors[] = $this->db->error();
3984
			$this->db->rollback();
3985
			return -2;
3986
		}
3987
	}
3988
	/**
3989
	 *	Update the line object into db
3990
	 *
3991
	 *	@param      int		$notrigger		1 = disable triggers
3992
	 *	@return		int		<0 si ko, >0 si ok
3993
	 */
3994
	public function update($notrigger = 0)
3995
	{
3996
		global $conf, $user;
3997
3998
		$error = 0;
3999
4000
		$this->db->begin();
4001
4002
		// Mise a jour ligne en base
4003
		$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
4004
		$sql .= "  description='".$this->db->escape($this->desc)."'";
4005
		$sql .= ", ref='".$this->db->escape($this->ref_supplier)."'";
4006
		$sql .= ", subprice='".price2num($this->subprice)."'";
4007
		//$sql.= ",remise='".price2num($remise)."'";
4008
		$sql .= ", remise_percent='".price2num($this->remise_percent)."'";
4009
4010
		$sql .= ", vat_src_code = '".(empty($this->vat_src_code) ? '' : $this->vat_src_code)."'";
4011
		$sql .= ", tva_tx='".price2num($this->tva_tx)."'";
4012
		$sql .= ", localtax1_tx='".price2num($this->localtax1_tx)."'";
4013
		$sql .= ", localtax2_tx='".price2num($this->localtax2_tx)."'";
4014
		$sql .= ", localtax1_type='".$this->db->escape($this->localtax1_type)."'";
4015
		$sql .= ", localtax2_type='".$this->db->escape($this->localtax2_type)."'";
4016
		$sql .= ", qty='".price2num($this->qty)."'";
4017
		$sql .= ", date_start=".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null");
4018
		$sql .= ", date_end=".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
4019
		$sql .= ", info_bits='".$this->db->escape($this->info_bits)."'";
4020
		$sql .= ", total_ht='".price2num($this->total_ht)."'";
4021
		$sql .= ", total_tva='".price2num($this->total_tva)."'";
4022
		$sql .= ", total_localtax1='".price2num($this->total_localtax1)."'";
4023
		$sql .= ", total_localtax2='".price2num($this->total_localtax2)."'";
4024
		$sql .= ", total_ttc='".price2num($this->total_ttc)."'";
4025
		$sql .= ", product_type=".$this->product_type;
4026
		$sql .= ", special_code=".(!empty($this->special_code) ? $this->special_code : 0);
4027
		$sql .= ($this->fk_unit ? ", fk_unit='".$this->db->escape($this->fk_unit)."'" : ", fk_unit=null");
4028
4029
		// Multicurrency
4030
		$sql .= ", multicurrency_subprice=".price2num($this->multicurrency_subprice);
4031
		$sql .= ", multicurrency_total_ht=".price2num($this->multicurrency_total_ht);
4032
		$sql .= ", multicurrency_total_tva=".price2num($this->multicurrency_total_tva);
4033
		$sql .= ", multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc);
4034
4035
		$sql .= " WHERE rowid = ".((int) $this->id);
4036
4037
		dol_syslog(get_class($this)."::updateline", LOG_DEBUG);
4038
		$result = $this->db->query($sql);
4039
		if ($result > 0) {
4040
			if (!$error) {
4041
				$result = $this->insertExtraFields();
4042
				if ($result < 0) {
4043
					$error++;
4044
				}
4045
			}
4046
4047
			if (!$error && !$notrigger) {
4048
				global $user;
4049
				// Call trigger
4050
				$result = $this->call_trigger('LINEORDER_SUPPLIER_MODIFY', $user);
4051
				if ($result < 0) {
4052
					$this->db->rollback();
4053
					return -1;
4054
				}
4055
				// End call triggers
4056
			}
4057
4058
			if (!$error) {
4059
				$this->db->commit();
4060
				return 1;
4061
			} else {
4062
				$this->db->rollback();
4063
				return -1;
4064
			}
4065
		} else {
4066
			$this->error = $this->db->lasterror();
4067
			$this->db->rollback();
4068
			return -1;
4069
		}
4070
	}
4071
4072
	/**
4073
	 * 	Delete line in database
4074
	 *
4075
	 *	@param      int     $notrigger  1=Disable call to triggers
4076
	 *	@return     int                 <0 if KO, >0 if OK
4077
	 */
4078
	public function delete($notrigger = 0)
4079
	{
4080
		global $user;
4081
4082
		$error = 0;
4083
4084
		$this->db->begin();
4085
4086
		// extrafields
4087
		$result = $this->deleteExtraFields();
4088
		if ($result < 0) {
4089
			$this->db->rollback();
4090
			return -1;
4091
		}
4092
4093
		$sql = 'DELETE FROM '.MAIN_DB_PREFIX."commande_fournisseurdet WHERE rowid=".((int) $this->id);
4094
4095
		dol_syslog(__METHOD__, LOG_DEBUG);
4096
		$resql = $this->db->query($sql);
4097
		if ($resql) {
4098
			if (!$notrigger) {
4099
				// Call trigger
4100
				$result = $this->call_trigger('LINEORDER_SUPPLIER_DELETE', $user);
4101
				if ($result < 0) {
4102
					$error++;
4103
				}
4104
				// End call triggers
4105
			}
4106
4107
			if (!$error) {
4108
				$this->db->commit();
4109
				return 1;
4110
			}
4111
4112
			foreach ($this->errors as $errmsg) {
4113
				dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
4114
				$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
4115
			}
4116
			$this->db->rollback();
4117
			return -1 * $error;
4118
		} else {
4119
			$this->error = $this->db->lasterror();
4120
			return -1;
4121
		}
4122
	}
4123
}
4124