Passed
Branch develop (4336b1)
by
unknown
85:39
created

Facture::updateline()   F

Complexity

Conditions 52

Size

Total Lines 202
Code Lines 134

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 52
eloc 134
nop 26
dl 0
loc 202
rs 3.3333
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/* Copyright (C) 2002-2007  Rodolphe Quiedeville    <[email protected]>
3
 * Copyright (C) 2004-2013  Laurent Destailleur     <[email protected]>
4
 * Copyright (C) 2004       Sebastien Di Cintio     <[email protected]>
5
 * Copyright (C) 2004       Benoit Mortier          <[email protected]>
6
 * Copyright (C) 2005       Marc Barilley / Ocebo   <[email protected]>
7
 * Copyright (C) 2005-2014  Regis Houssin           <[email protected]>
8
 * Copyright (C) 2006       Andre Cianfarani        <[email protected]>
9
 * Copyright (C) 2007       Franky Van Liedekerke   <[email protected]>
10
 * Copyright (C) 2010-2020  Juanjo Menent           <[email protected]>
11
 * Copyright (C) 2012-2014  Christophe Battarel     <[email protected]>
12
 * Copyright (C) 2012-2015  Marcos García           <[email protected]>
13
 * Copyright (C) 2012       Cédric Salvador         <[email protected]>
14
 * Copyright (C) 2012-2014  Raphaël Doursenaud      <[email protected]>
15
 * Copyright (C) 2013       Cedric Gross            <[email protected]>
16
 * Copyright (C) 2013       Florian Henry           <[email protected]>
17
 * Copyright (C) 2016-2022  Ferran Marcet           <[email protected]>
18
 * Copyright (C) 2018-2022  Alexandre Spangaro		<[email protected]>
19
 * Copyright (C) 2018       Nicolas ZABOURI         <[email protected]>
20
 * Copyright (C) 2022       Sylvain Legrand         <[email protected]>
21
 * Copyright (C) 2022      	Gauthier VERDOL       	<[email protected]>
22
 *
23
 * This program is free software; you can redistribute it and/or modify
24
 * it under the terms of the GNU General Public License as published by
25
 * the Free Software Foundation; either version 3 of the License, or
26
 * (at your option) any later version.
27
 *
28
 * This program is distributed in the hope that it will be useful,
29
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
30
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
31
 * GNU General Public License for more details.
32
 *
33
 * You should have received a copy of the GNU General Public License
34
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
35
 */
36
37
/**
38
 *	\file       htdocs/compta/facture/class/facture.class.php
39
 *	\ingroup    facture
40
 *	\brief      File of class to manage invoices
41
 */
42
43
require_once DOL_DOCUMENT_ROOT.'/core/class/commoninvoice.class.php';
44
require_once DOL_DOCUMENT_ROOT.'/core/class/commonobjectline.class.php';
45
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
46
require_once DOL_DOCUMENT_ROOT.'/societe/class/client.class.php';
47
require_once DOL_DOCUMENT_ROOT.'/margin/lib/margins.lib.php';
48
require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php';
49
50
if (isModEnabled('accounting')) {
51
	require_once DOL_DOCUMENT_ROOT.'/core/class/html.formaccounting.class.php';
52
}
53
if (isModEnabled('accounting')) {
54
	require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php';
55
}
56
57
/**
58
 *	Class to manage invoices
59
 */
60
class Facture extends CommonInvoice
61
{
62
	/**
63
	 * @var string ID to identify managed object
64
	 */
65
	public $element = 'facture';
66
67
	/**
68
	 * @var string Name of table without prefix where object is stored
69
	 */
70
	public $table_element = 'facture';
71
72
	/**
73
	 * @var string    Name of subtable line
74
	 */
75
	public $table_element_line = 'facturedet';
76
77
	/**
78
	 * @var string Fieldname with ID of parent key if this field has a parent
79
	 */
80
	public $fk_element = 'fk_facture';
81
82
	/**
83
	 * @var string String with name of icon for myobject.
84
	 */
85
	public $picto = 'bill';
86
87
	/**
88
	 * 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
89
	 * @var int
90
	 */
91
	public $ismultientitymanaged = 1;
92
93
	/**
94
	 * 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
95
	 * @var integer
96
	 */
97
	public $restrictiononfksoc = 1;
98
99
	/**
100
	 * {@inheritdoc}
101
	 */
102
	protected $table_ref_field = 'ref';
103
104
	/**
105
	 * @var int 1 if status is draft
106
	 * @deprecated
107
	 */
108
	public $brouillon;
109
110
	/**
111
	 * @var int thirdparty ID
112
	 */
113
	public $socid;
114
115
	public $author;
116
117
	/**
118
	 * @var int ID
119
	 */
120
	public $fk_user_author;
121
122
	/**
123
	 * @var int ID
124
	 */
125
	public $fk_user_valid;
126
127
	/**
128
	 * @var int ID
129
	 */
130
	public $fk_user_modif;
131
132
133
	public $date; // Date invoice
134
	public $datem;
135
136
	/**
137
	 * @var int	Date expected for delivery
138
	 * @deprecated
139
	 * @see $delivery_date
140
	 */
141
	public $date_livraison;
142
143
	/**
144
	 * @var int	Date expected for delivery
145
	 */
146
	public $delivery_date; // Date expected of shipment (date starting shipment, not the reception that occurs some days after)
147
148
	/**
149
	 * @var string customer ref
150
	 * @deprecated
151
	 * @see $ref_customer
152
	 */
153
	public $ref_client;
154
155
	/**
156
	 * @var string customer ref
157
	 */
158
	public $ref_customer;
159
160
	//Check constants for types
161
	public $type = self::TYPE_STANDARD;
162
163
	// Warning: Do not set default value into property defintion. it must stay null.
164
	// For example to avoid to have substition done when object is generic and not yet defined.
165
	public $remise_absolue;
166
	public $remise_percent;
167
	public $total_ht;
168
	public $total_tva;
169
	public $total_localtax1;
170
	public $total_localtax2;
171
	public $total_ttc;
172
	public $revenuestamp;
173
174
	public $resteapayer;
175
176
	/**
177
	 * ! Closing after partial payment: discount_vat, badcustomer or badsupplier, bankcharge, other
178
	 * ! Closing when no payment: replaced, abandoned
179
	 * @var string Close code
180
	 */
181
	public $close_code;
182
183
	/**
184
	 * ! Comment if paid without full payment
185
	 * @var string Close note
186
	 */
187
	public $close_note;
188
189
	/**
190
	 * 1 if invoice paid COMPLETELY, 0 otherwise (do not use it anymore, use statut and close_code)
191
	 */
192
	public $paye;
193
194
	//! key of module source when invoice generated from a dedicated module ('cashdesk', 'takepos', ...)
195
	public $module_source;
196
	//! key of pos source ('0', '1', ...)
197
	public $pos_source;
198
	//! id of template invoice when generated from a template invoice
199
	public $fk_fac_rec_source;
200
	//! id of source invoice if replacement invoice or credit note
201
	public $fk_facture_source;
202
	public $linked_objects = array();
203
204
	public $date_lim_reglement;
205
	public $cond_reglement_code; // Code in llx_c_paiement
206
	public $mode_reglement_code; // Code in llx_c_paiement
207
208
	/**
209
	 * @var int ID Field to store bank id to use when payment mode is withdraw
210
	 */
211
	public $fk_bank;
212
213
	/**
214
	 * @var FactureLigne[]
215
	 */
216
	public $lines = array();
217
218
	public $line;
219
	public $extraparams = array();
220
221
	public $fac_rec;
222
223
	public $date_pointoftax;
224
225
	// Multicurrency
226
	/**
227
	 * @var int ID
228
	 */
229
	public $fk_multicurrency;
230
231
	public $multicurrency_code;
232
	public $multicurrency_tx;
233
	public $multicurrency_total_ht;
234
	public $multicurrency_total_tva;
235
	public $multicurrency_total_ttc;
236
237
	/**
238
	 * @var int Situation cycle reference number
239
	 */
240
	public $situation_cycle_ref;
241
242
	/**
243
	 * @var int Situation counter inside the cycle
244
	 */
245
	public $situation_counter;
246
247
	/**
248
	 * @var int Final situation flag
249
	 */
250
	public $situation_final;
251
252
	/**
253
	 * @var array Table of previous situations
254
	 */
255
	public $tab_previous_situation_invoice = array();
256
257
	/**
258
	 * @var array Table of next situations
259
	 */
260
	public $tab_next_situation_invoice = array();
261
262
	public $oldcopy;
263
264
	/**
265
	 * @var double percentage of retainage
266
	 */
267
	public $retained_warranty;
268
269
	/**
270
	 * @var int timestamp of date limit of retainage
271
	 */
272
	public $retained_warranty_date_limit;
273
274
	/**
275
	 * @var int Code in llx_c_paiement
276
	 */
277
	public $retained_warranty_fk_cond_reglement;
278
279
280
	/**
281
	 *  'type' if the field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter]]', 'varchar(x)', 'double(24,8)', 'real', 'price', 'text', 'html', 'date', 'datetime', 'timestamp', 'duration', 'mail', 'phone', 'url', 'password')
282
	 *         Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)"
283
	 *  'label' the translation key.
284
	 *  'enabled' is a condition when the field must be managed.
285
	 *  'position' is the sort order of field.
286
	 *  'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
287
	 *  '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)
288
	 *  'noteditable' says if field is not editable (1 or 0)
289
	 *  '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.
290
	 *  'index' if we want an index in database.
291
	 *  'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommanded to name the field fk_...).
292
	 *  'searchall' is 1 if we want to search in this field when making a search from the quick search button.
293
	 *  'isameasure' must be set to 1 if you want to have a total on list for this field. Field type must be summable like integer or double(24,8).
294
	 *  'css' is the CSS style to use on field. For example: 'maxwidth200'
295
	 *  'help' is a string visible as a tooltip on field
296
	 *  'showoncombobox' if value of the field must be visible into the label of the combobox that list record
297
	 *  '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.
298
	 *  'arrayofkeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel")
299
	 *  'comment' is not used. You can store here any text of your choice. It is not used by application.
300
	 *
301
	 *  Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
302
	 */
303
304
	// BEGIN MODULEBUILDER PROPERTIES
305
	/**
306
	 * @var array  Array with all fields and their property. Do not use it as a static var. It may be modified by constructor.
307
	 */
308
	public $fields = array(
309
		'rowid' =>array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>1),
310
		'ref' =>array('type'=>'varchar(30)', 'label'=>'Ref', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'showoncombobox'=>1, 'position'=>5),
311
		'entity' =>array('type'=>'integer', 'label'=>'Entity', 'default'=>1, 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>20, 'index'=>1),
312
		'ref_client' =>array('type'=>'varchar(255)', 'label'=>'RefCustomer', 'enabled'=>1, 'visible'=>-1, 'position'=>10),
313
		'ref_ext' =>array('type'=>'varchar(255)', 'label'=>'Ref ext', 'enabled'=>1, 'visible'=>0, 'position'=>12),
314
		'type' =>array('type'=>'smallint(6)', 'label'=>'Type', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>15),
315
		//'increment' =>array('type'=>'varchar(10)', 'label'=>'Increment', 'enabled'=>1, 'visible'=>-1, 'position'=>45),
316
		'fk_soc' =>array('type'=>'integer:Societe:societe/class/societe.class.php', 'label'=>'ThirdParty', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>50),
317
		'datef' =>array('type'=>'date', 'label'=>'DateInvoice', 'enabled'=>1, 'visible'=>1, 'position'=>20),
318
		'date_valid' =>array('type'=>'date', 'label'=>'DateValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>22),
319
		'date_lim_reglement' =>array('type'=>'date', 'label'=>'DateDue', 'enabled'=>1, 'visible'=>1, 'position'=>25),
320
		'date_closing' =>array('type'=>'datetime', 'label'=>'Date closing', 'enabled'=>1, 'visible'=>-1, 'position'=>30),
321
		'paye' =>array('type'=>'smallint(6)', 'label'=>'InvoicePaidCompletely', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>80),
322
		//'amount' =>array('type'=>'double(24,8)', 'label'=>'Amount', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>85),
323
		'remise_percent' =>array('type'=>'double', 'label'=>'RelativeDiscount', 'enabled'=>1, 'visible'=>-1, 'position'=>90),
324
		'remise_absolue' =>array('type'=>'double', 'label'=>'CustomerRelativeDiscount', 'enabled'=>1, 'visible'=>-1, 'position'=>91),
325
		//'remise' =>array('type'=>'double', 'label'=>'Remise', 'enabled'=>1, 'visible'=>-1, 'position'=>100),
326
		'close_code' =>array('type'=>'varchar(16)', 'label'=>'EarlyClosingReason', 'enabled'=>1, 'visible'=>-1, 'position'=>92),
327
		'close_note' =>array('type'=>'varchar(128)', 'label'=>'EarlyClosingComment', 'enabled'=>1, 'visible'=>-1, 'position'=>93),
328
		'total_ht' =>array('type'=>'double(24,8)', 'label'=>'AmountHT', 'enabled'=>1, 'visible'=>1, 'position'=>95, 'isameasure'=>1),
329
		'total_tva' =>array('type'=>'double(24,8)', 'label'=>'AmountVAT', 'enabled'=>1, 'visible'=>-1, 'position'=>100, 'isameasure'=>1),
330
		'localtax1' =>array('type'=>'double(24,8)', 'label'=>'LT1', 'enabled'=>1, 'visible'=>-1, 'position'=>110, 'isameasure'=>1),
331
		'localtax2' =>array('type'=>'double(24,8)', 'label'=>'LT2', 'enabled'=>1, 'visible'=>-1, 'position'=>120, 'isameasure'=>1),
332
		'revenuestamp' =>array('type'=>'double(24,8)', 'label'=>'RevenueStamp', 'enabled'=>1, 'visible'=>-1, 'position'=>115, 'isameasure'=>1),
333
		'total_ttc' =>array('type'=>'double(24,8)', 'label'=>'AmountTTC', 'enabled'=>1, 'visible'=>1, 'position'=>130, 'isameasure'=>1),
334
		'fk_user_author' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserAuthor', 'enabled'=>1, 'visible'=>-1, 'position'=>165),
335
		'fk_user_modif' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserModif', 'enabled'=>1, 'visible'=>-2, 'notnull'=>-1, 'position'=>166),
336
		'fk_user_valid' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>167),
337
		'fk_user_closing' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserClosing', 'enabled'=>1, 'visible'=>-1, 'position'=>168),
338
		'fk_facture_source' =>array('type'=>'integer', 'label'=>'SourceInvoice', 'enabled'=>1, 'visible'=>-1, 'position'=>170),
339
		'fk_projet' =>array('type'=>'integer:Project:projet/class/project.class.php:1:fk_statut=1', 'label'=>'Project', 'enabled'=>1, 'visible'=>-1, 'position'=>175),
340
		'fk_account' =>array('type'=>'integer', 'label'=>'Fk account', 'enabled'=>1, 'visible'=>-1, 'position'=>180),
341
		'fk_currency' =>array('type'=>'varchar(3)', 'label'=>'CurrencyCode', 'enabled'=>1, 'visible'=>-1, 'position'=>185),
342
		'fk_cond_reglement' =>array('type'=>'integer', 'label'=>'PaymentTerm', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>190),
343
		'fk_mode_reglement' =>array('type'=>'integer', 'label'=>'PaymentMode', 'enabled'=>1, 'visible'=>-1, 'position'=>195),
344
		'note_private' =>array('type'=>'text', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>205),
345
		'note_public' =>array('type'=>'text', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>210),
346
		'model_pdf' =>array('type'=>'varchar(255)', 'label'=>'Model pdf', 'enabled'=>1, 'visible'=>0, 'position'=>215),
347
		'extraparams' =>array('type'=>'varchar(255)', 'label'=>'Extraparams', 'enabled'=>1, 'visible'=>-1, 'position'=>225),
348
		'situation_cycle_ref' =>array('type'=>'smallint(6)', 'label'=>'Situation cycle ref', 'enabled'=>'$conf->global->INVOICE_USE_SITUATION', 'visible'=>-1, 'position'=>230),
349
		'situation_counter' =>array('type'=>'smallint(6)', 'label'=>'Situation counter', 'enabled'=>'$conf->global->INVOICE_USE_SITUATION', 'visible'=>-1, 'position'=>235),
350
		'situation_final' =>array('type'=>'smallint(6)', 'label'=>'Situation final', 'enabled'=>'empty($conf->global->INVOICE_USE_SITUATION) ? 0 : 1', 'visible'=>-1, 'position'=>240),
351
		'retained_warranty' =>array('type'=>'double', 'label'=>'Retained warranty', 'enabled'=>'$conf->global->INVOICE_USE_RETAINED_WARRANTY', 'visible'=>-1, 'position'=>245),
352
		'retained_warranty_date_limit' =>array('type'=>'date', 'label'=>'Retained warranty date limit', 'enabled'=>'$conf->global->INVOICE_USE_RETAINED_WARRANTY', 'visible'=>-1, 'position'=>250),
353
		'retained_warranty_fk_cond_reglement' =>array('type'=>'integer', 'label'=>'Retained warranty fk cond reglement', 'enabled'=>'$conf->global->INVOICE_USE_RETAINED_WARRANTY', 'visible'=>-1, 'position'=>255),
354
		'fk_incoterms' =>array('type'=>'integer', 'label'=>'IncotermCode', 'enabled'=>'$conf->incoterm->enabled', 'visible'=>-1, 'position'=>260),
355
		'location_incoterms' =>array('type'=>'varchar(255)', 'label'=>'IncotermLabel', 'enabled'=>'$conf->incoterm->enabled', 'visible'=>-1, 'position'=>265),
356
		'date_pointoftax' =>array('type'=>'date', 'label'=>'DatePointOfTax', 'enabled'=>'$conf->global->INVOICE_POINTOFTAX_DATE', 'visible'=>-1, 'position'=>270),
357
		'fk_multicurrency' =>array('type'=>'integer', 'label'=>'MulticurrencyID', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>275),
358
		'multicurrency_code' =>array('type'=>'varchar(255)', 'label'=>'Currency', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>280),
359
		'multicurrency_tx' =>array('type'=>'double(24,8)', 'label'=>'CurrencyRate', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>285, 'isameasure'=>1),
360
		'multicurrency_total_ht' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountHT', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>290, 'isameasure'=>1),
361
		'multicurrency_total_tva' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountVAT', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>291, 'isameasure'=>1),
362
		'multicurrency_total_ttc' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountTTC', 'enabled'=>'isModEnabled("multicurrency")', 'visible'=>-1, 'position'=>292, 'isameasure'=>1),
363
		'fk_fac_rec_source' =>array('type'=>'integer', 'label'=>'RecurringInvoiceSource', 'enabled'=>1, 'visible'=>-1, 'position'=>305),
364
		'last_main_doc' =>array('type'=>'varchar(255)', 'label'=>'LastMainDoc', 'enabled'=>1, 'visible'=>-1, 'position'=>310),
365
		'module_source' =>array('type'=>'varchar(32)', 'label'=>'POSModule', 'enabled'=>1, 'visible'=>-1, 'position'=>315),
366
		'pos_source' =>array('type'=>'varchar(32)', 'label'=>'POSTerminal', 'enabled'=>1, 'visible'=>-1, 'position'=>320),
367
		'datec' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-1, 'position'=>500),
368
		'tms' =>array('type'=>'timestamp', 'label'=>'DateModificationShort', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>500),
369
		'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'position'=>900),
370
		'fk_statut' =>array('type'=>'smallint(6)', 'label'=>'Status', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'position'=>1000, 'arrayofkeyval'=>array(0=>'Draft', 1=>'Validated', 2=>'Paid', 3=>'Abandonned')),
371
	);
372
	// END MODULEBUILDER PROPERTIES
373
374
	/**
375
	 * Standard invoice
376
	 */
377
	const TYPE_STANDARD = 0;
378
379
	/**
380
	 * Replacement invoice
381
	 */
382
	const TYPE_REPLACEMENT = 1;
383
384
	/**
385
	 * Credit note invoice
386
	 */
387
	const TYPE_CREDIT_NOTE = 2;
388
389
	/**
390
	 * Deposit invoice
391
	 */
392
	const TYPE_DEPOSIT = 3;
393
394
	/**
395
	 * Proforma invoice (should not be used. a proforma is an order)
396
	 */
397
	const TYPE_PROFORMA = 4;
398
399
	/**
400
	 * Situation invoice
401
	 */
402
	const TYPE_SITUATION = 5;
403
404
	/**
405
	 * Draft status
406
	 */
407
	const STATUS_DRAFT = 0;
408
409
	/**
410
	 * Validated (need to be paid)
411
	 */
412
	const STATUS_VALIDATED = 1;
413
414
	/**
415
	 * Classified paid.
416
	 * If paid partially, $this->close_code can be:
417
	 * - CLOSECODE_DISCOUNTVAT
418
	 * - CLOSECODE_BADDEBT
419
	 * If paid completely, this->close_code will be null
420
	 */
421
	const STATUS_CLOSED = 2;
422
423
	/**
424
	 * Classified abandoned and no payment done.
425
	 * $this->close_code can be:
426
	 * - CLOSECODE_BADDEBT
427
	 * - CLOSECODE_ABANDONED
428
	 * - CLOSECODE_REPLACED
429
	 */
430
	const STATUS_ABANDONED = 3;
431
432
	const CLOSECODE_DISCOUNTVAT = 'discount_vat'; // Abandonned remain - escompte
433
	const CLOSECODE_BADDEBT = 'badcustomer'; // Abandonned remain - bad customer
434
	const CLOSECODE_BANKCHARGE = 'bankcharge'; // Abandonned remain - bank charge
435
	const CLOSECODE_OTHER = 'other'; // Abandonned remain - other
436
437
	const CLOSECODE_ABANDONED = 'abandon'; // Abandonned - other
438
	const CLOSECODE_REPLACED = 'replaced'; // Closed after doing a replacement invoice
439
440
441
	/**
442
	 * 	Constructor
443
	 *
444
	 * 	@param	DoliDB		$db			Database handler
445
	 */
446
	public function __construct(DoliDB $db)
447
	{
448
		$this->db = $db;
449
	}
450
451
	/**
452
	 *	Create invoice in database.
453
	 *  Note: this->ref can be set or empty. If empty, we will use "(PROV999)"
454
	 *  Note: this->fac_rec must be set to create invoice from a recurring invoice
455
	 *
456
	 *	@param	User	$user      		Object user that create
457
	 *	@param  int		$notrigger		1=Does not execute triggers, 0 otherwise
458
	 * 	@param	int		$forceduedate	If set, do not recalculate due date from payment condition but force it with value
459
	 *	@return	int						<0 if KO, >0 if OK
460
	 */
461
	public function create(User $user, $notrigger = 0, $forceduedate = 0)
462
	{
463
		global $langs, $conf, $mysoc, $hookmanager;
464
		$error = 0;
465
466
		// Clean parameters
467
		if (empty($this->type)) {
468
			$this->type = self::TYPE_STANDARD;
469
		}
470
		$this->ref_client = trim($this->ref_client);
471
		$this->note = (isset($this->note) ? trim($this->note) : trim($this->note_private)); // deprecated
472
		$this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note_private));
473
		$this->note_public = trim($this->note_public);
474
		if (!$this->cond_reglement_id) {
475
			$this->cond_reglement_id = 0;
476
		}
477
		if (!$this->mode_reglement_id) {
478
			$this->mode_reglement_id = 0;
479
		}
480
		$this->brouillon = 1;
481
		$this->status = self::STATUS_DRAFT;
482
		$this->statut = self::STATUS_DRAFT;
483
484
		if (!empty($this->multicurrency_code)) {
485
			// Multicurrency (test on $this->multicurrency_tx because we should take the default rate of multicurrency_code only if not using original rate)
486
			if (empty($this->multicurrency_tx)) {
487
				// If original rate is not set, we take a default value from date
488
				list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date);
489
			} else {
490
				// original rate multicurrency_tx and multicurrency_code are set, we use them
491
				$this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
492
			}
493
		} else {
494
			$this->fk_multicurrency = 0;
495
		}
496
		if (empty($this->fk_multicurrency)) {
497
			$this->multicurrency_code = $conf->currency;
498
			$this->fk_multicurrency = 0;
499
			$this->multicurrency_tx = 1;
500
		}
501
502
		dol_syslog(get_class($this)."::create user=".$user->id." date=".$this->date);
503
504
		// Check parameters
505
		if (empty($this->date)) {
506
			$this->error = "Try to create an invoice with an empty parameter (date)";
507
			dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
508
			return -3;
509
		}
510
		$soc = new Societe($this->db);
511
		$result = $soc->fetch($this->socid);
512
		if ($result < 0) {
513
			$this->error = "Failed to fetch company: ".$soc->error;
514
			dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
515
			return -2;
516
		}
517
518
		$now = dol_now();
519
520
		$this->db->begin();
521
522
		$originaldatewhen = null;
523
		$nextdatewhen = null;
524
		$previousdaynextdatewhen = null;
525
526
		// Create invoice from a template recurring invoice
527
		if ($this->fac_rec > 0) {
528
			$this->fk_fac_rec_source = $this->fac_rec;
529
530
			require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture-rec.class.php';
531
			$_facrec = new FactureRec($this->db);
532
			$result = $_facrec->fetch($this->fac_rec);
533
			$result = $_facrec->fetchObjectLinked(null, '', null, '', 'OR', 1, 'sourcetype', 0); // This load $_facrec->linkedObjectsIds
534
535
			// Define some dates
536
			$originaldatewhen = $_facrec->date_when;
537
			$nextdatewhen = null; $previousdaynextdatewhen = null;
538
			if ($originaldatewhen) {
539
				$nextdatewhen = dol_time_plus_duree($originaldatewhen, $_facrec->frequency, $_facrec->unit_frequency);
540
				$previousdaynextdatewhen = dol_time_plus_duree($nextdatewhen, -1, 'd');
541
			}
542
543
			if (!empty($_facrec->frequency)) {  // Invoice are created on same thirdparty than template when there is a recurrence, but not necessarly when there is no recurrence.
544
				$this->socid = $_facrec->socid;
545
			}
546
			$this->entity            = $_facrec->entity; // Invoice created in same entity than template
547
548
			// Fields coming from GUI (priority on template). TODO Value of template should be used as default value on GUI so we can use here always value from GUI
549
			$this->fk_project        = GETPOST('projectid', 'int') > 0 ? ((int) GETPOST('projectid', 'int')) : $_facrec->fk_project;
550
			$this->note_public       = GETPOSTISSET('note_public') ? GETPOST('note_public', 'restricthtml') : $_facrec->note_public;
551
			$this->note_private      = GETPOSTISSET('note_private') ? GETPOST('note_private', 'restricthtml') : $_facrec->note_private;
552
			$this->model_pdf = GETPOSTISSET('model') ? GETPOST('model', 'alpha') : $_facrec->model_pdf;
553
			$this->cond_reglement_id = GETPOST('cond_reglement_id', 'int') > 0 ? ((int) GETPOST('cond_reglement_id', 'int')) : $_facrec->cond_reglement_id;
554
			$this->mode_reglement_id = GETPOST('mode_reglement_id', 'int') > 0 ? ((int) GETPOST('mode_reglement_id', 'int')) : $_facrec->mode_reglement_id;
555
			$this->fk_account        = GETPOST('fk_account') > 0 ? ((int) GETPOST('fk_account')) : $_facrec->fk_account;
556
557
			// Set here to have this defined for substitution into notes, should be recalculated after adding lines to get same result
558
			$this->total_ht          = $_facrec->total_ht;
559
			$this->total_ttc         = $_facrec->total_ttc;
560
561
			// Fields always coming from template
562
			$this->remise_absolue    = $_facrec->remise_absolue;
563
			$this->remise_percent    = $_facrec->remise_percent;
564
			$this->fk_incoterms = $_facrec->fk_incoterms;
565
			$this->location_incoterms = $_facrec->location_incoterms;
566
567
			// Clean parameters
568
			if (!$this->type) {
569
				$this->type = self::TYPE_STANDARD;
570
			}
571
			$this->ref_client = trim($this->ref_client);
572
			$this->note_public = trim($this->note_public);
573
			$this->note_private = trim($this->note_private);
574
			$this->note_private = dol_concatdesc($this->note_private, $langs->trans("GeneratedFromRecurringInvoice", $_facrec->ref));
575
576
			$this->array_options = $_facrec->array_options;
577
578
			if (!$this->mode_reglement_id) {
579
				$this->mode_reglement_id = 0;
580
			}
581
			$this->brouillon = 1;
582
			$this->status = self::STATUS_DRAFT;
583
			$this->statut = self::STATUS_DRAFT;
584
585
			$this->linked_objects = $_facrec->linkedObjectsIds;
586
			// We do not add link to template invoice or next invoice will be linked to all generated invoices
587
			//$this->linked_objects['facturerec'][0] = $this->fac_rec;
588
589
			$forceduedate = $this->calculate_date_lim_reglement();
590
591
			// For recurring invoices, update date and number of last generation of recurring template invoice, before inserting new invoice
592
			if ($_facrec->frequency > 0) {
593
				dol_syslog("This is a recurring invoice so we set date_last_gen and next date_when");
594
				if (empty($_facrec->date_when)) {
595
					$_facrec->date_when = $now;
596
				}
597
				$next_date = $_facrec->getNextDate(); // Calculate next date
598
				$result = $_facrec->setValueFrom('date_last_gen', $now, '', null, 'date', '', $user, '');
599
				//$_facrec->setValueFrom('nb_gen_done', $_facrec->nb_gen_done + 1);		// Not required, +1 already included into setNextDate when second param is 1.
600
				$result = $_facrec->setNextDate($next_date, 1);
601
			}
602
603
			// Define lang of customer
604
			$outputlangs = $langs;
605
			$newlang = '';
606
607
			if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang) && isset($this->thirdparty->default_lang)) {
608
				$newlang = $this->thirdparty->default_lang; // for proposal, order, invoice, ...
609
			}
610
			if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang) && isset($this->default_lang)) {
611
				$newlang = $this->default_lang; // for thirdparty
612
			}
613
			if (!empty($newlang)) {
614
				$outputlangs = new Translate("", $conf);
615
				$outputlangs->setDefaultLang($newlang);
616
			}
617
618
			// Array of possible substitutions (See also file mailing-send.php that should manage same substitutions)
619
			$substitutionarray = getCommonSubstitutionArray($outputlangs, 0, null, $this);
620
			$substitutionarray['__INVOICE_PREVIOUS_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%m');
621
			$substitutionarray['__INVOICE_MONTH__'] = dol_print_date($this->date, '%m');
622
			$substitutionarray['__INVOICE_NEXT_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%m');
623
			$substitutionarray['__INVOICE_PREVIOUS_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%B');
624
			$substitutionarray['__INVOICE_MONTH_TEXT__'] = dol_print_date($this->date, '%B');
625
			$substitutionarray['__INVOICE_NEXT_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%B');
626
			$substitutionarray['__INVOICE_PREVIOUS_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'y'), '%Y');
627
			$substitutionarray['__INVOICE_YEAR__'] = dol_print_date($this->date, '%Y');
628
			$substitutionarray['__INVOICE_NEXT_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'y'), '%Y');
629
			// Only for template invoice
630
			$substitutionarray['__INVOICE_DATE_NEXT_INVOICE_BEFORE_GEN__'] = (isset($originaldatewhen) ? dol_print_date($originaldatewhen, 'dayhour') : '');
631
			$substitutionarray['__INVOICE_DATE_NEXT_INVOICE_AFTER_GEN__'] = (isset($nextdatewhen) ? dol_print_date($nextdatewhen, 'dayhour') : '');
632
			$substitutionarray['__INVOICE_PREVIOUS_DATE_NEXT_INVOICE_AFTER_GEN__'] = (isset($previousdaynextdatewhen) ? dol_print_date($previousdaynextdatewhen, 'dayhour') : '');
633
			$substitutionarray['__INVOICE_COUNTER_CURRENT__'] = $_facrec->nb_gen_done;
634
			$substitutionarray['__INVOICE_COUNTER_MAX__'] = $_facrec->nb_gen_max;
635
636
			//var_dump($substitutionarray);exit;
637
638
			complete_substitutions_array($substitutionarray, $outputlangs);
639
640
			$this->note_public = make_substitutions($this->note_public, $substitutionarray);
641
			$this->note_private = make_substitutions($this->note_private, $substitutionarray);
642
		}
643
644
		// Define due date if not already defined
645
		if (empty($forceduedate)) {
646
			$duedate = $this->calculate_date_lim_reglement();
647
			/*if ($duedate < 0) {	Regression, a date can be negative if before 1970.
648
				dol_syslog(__METHOD__ . ' Error in calculate_date_lim_reglement. We got ' . $duedate, LOG_ERR);
649
				return -1;
650
			}*/
651
			$this->date_lim_reglement = $duedate;
652
		} else {
653
			$this->date_lim_reglement = $forceduedate;
654
		}
655
656
		// Insert into database
657
		$socid = $this->socid;
658
659
		$sql = "INSERT INTO ".MAIN_DB_PREFIX."facture (";
660
		$sql .= " ref";
661
		$sql .= ", entity";
662
		$sql .= ", ref_ext";
663
		$sql .= ", type";
664
		$sql .= ", fk_soc";
665
		$sql .= ", datec";
666
		$sql .= ", remise_absolue";
667
		$sql .= ", remise_percent";
668
		$sql .= ", datef";
669
		$sql .= ", date_pointoftax";
670
		$sql .= ", note_private";
671
		$sql .= ", note_public";
672
		$sql .= ", ref_client";
673
		$sql .= ", fk_account";
674
		$sql .= ", module_source, pos_source, fk_fac_rec_source, fk_facture_source, fk_user_author, fk_projet";
675
		$sql .= ", fk_cond_reglement, fk_mode_reglement, date_lim_reglement, model_pdf";
676
		$sql .= ", situation_cycle_ref, situation_counter, situation_final";
677
		$sql .= ", fk_incoterms, location_incoterms";
678
		$sql .= ", fk_multicurrency";
679
		$sql .= ", multicurrency_code";
680
		$sql .= ", multicurrency_tx";
681
		$sql .= ", retained_warranty";
682
		$sql .= ", retained_warranty_date_limit";
683
		$sql .= ", retained_warranty_fk_cond_reglement";
684
		$sql .= ")";
685
		$sql .= " VALUES (";
686
		$sql .= "'(PROV)'";
687
		$sql .= ", ".setEntity($this);
688
		$sql .= ", ".($this->ref_ext ? "'".$this->db->escape($this->ref_ext)."'" : "null");
689
		$sql .= ", '".$this->db->escape($this->type)."'";
690
		$sql .= ", ".((int) $socid);
691
		$sql .= ", '".$this->db->idate($now)."'";
692
		$sql .= ", ".($this->remise_absolue > 0 ? $this->remise_absolue : 'NULL');
693
		$sql .= ", ".($this->remise_percent > 0 ? $this->remise_percent : 'NULL');
694
		$sql .= ", '".$this->db->idate($this->date)."'";
695
		$sql .= ", ".(empty($this->date_pointoftax) ? "null" : "'".$this->db->idate($this->date_pointoftax)."'");
696
		$sql .= ", ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "null");
697
		$sql .= ", ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : "null");
698
		$sql .= ", ".($this->ref_client ? "'".$this->db->escape($this->ref_client)."'" : "null");
699
		$sql .= ", ".($this->fk_account > 0 ? $this->fk_account : 'NULL');
700
		$sql .= ", ".($this->module_source ? "'".$this->db->escape($this->module_source)."'" : "null");
701
		$sql .= ", ".($this->pos_source != '' ? "'".$this->db->escape($this->pos_source)."'" : "null");
702
		$sql .= ", ".($this->fk_fac_rec_source ? "'".$this->db->escape($this->fk_fac_rec_source)."'" : "null");
703
		$sql .= ", ".($this->fk_facture_source ? "'".$this->db->escape($this->fk_facture_source)."'" : "null");
704
		$sql .= ", ".($user->id > 0 ? (int) $user->id : "null");
705
		$sql .= ", ".($this->fk_project ? $this->fk_project : "null");
706
		$sql .= ", ".((int) $this->cond_reglement_id);
707
		$sql .= ", ".((int) $this->mode_reglement_id);
708
		$sql .= ", '".$this->db->idate($this->date_lim_reglement)."'";
709
		$sql .= ", ".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null");
710
		$sql .= ", ".($this->situation_cycle_ref ? "'".$this->db->escape($this->situation_cycle_ref)."'" : "null");
711
		$sql .= ", ".($this->situation_counter ? "'".$this->db->escape($this->situation_counter)."'" : "null");
712
		$sql .= ", ".($this->situation_final ? $this->situation_final : 0);
713
		$sql .= ", ".(int) $this->fk_incoterms;
714
		$sql .= ", '".$this->db->escape($this->location_incoterms)."'";
715
		$sql .= ", ".(int) $this->fk_multicurrency;
716
		$sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
717
		$sql .= ", ".(double) $this->multicurrency_tx;
718
		$sql .= ", ".(empty($this->retained_warranty) ? "0" : $this->db->escape($this->retained_warranty));
719
		$sql .= ", ".(!empty($this->retained_warranty_date_limit) ? "'".$this->db->idate($this->retained_warranty_date_limit)."'" : 'NULL');
720
		$sql .= ", ".(int) $this->retained_warranty_fk_cond_reglement;
721
		$sql .= ")";
722
723
		$resql = $this->db->query($sql);
724
		if ($resql) {
725
			$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'facture');
726
727
			// Update ref with new one
728
			$this->ref = '(PROV'.$this->id.')';
729
			$sql = 'UPDATE '.MAIN_DB_PREFIX."facture SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".((int) $this->id);
730
731
			$resql = $this->db->query($sql);
732
			if (!$resql) {
733
				$error++;
734
			}
735
736
			if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) {	// To use new linkedObjectsIds instead of old linked_objects
737
				$this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
738
			}
739
740
			// Add object linked
741
			if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
742
				foreach ($this->linked_objects as $origin => $tmp_origin_id) {
743
					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, ...))
744
						foreach ($tmp_origin_id as $origin_id) {
745
							$ret = $this->add_object_linked($origin, $origin_id);
746
							if (!$ret) {
747
								$this->error = $this->db->lasterror();
748
								$error++;
749
							}
750
						}
751
					} else // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
752
					{
753
						$origin_id = $tmp_origin_id;
754
						$ret = $this->add_object_linked($origin, $origin_id);
755
						if (!$ret) {
756
							$this->error = $this->db->lasterror();
757
							$error++;
758
						}
759
					}
760
				}
761
			}
762
763
			// Propagate contacts
764
			if (!$error && $this->id && !empty($conf->global->MAIN_PROPAGATE_CONTACTS_FROM_ORIGIN) && !empty($this->origin) && !empty($this->origin_id)) {   // Get contact from origin object
765
				$originforcontact = $this->origin;
766
				$originidforcontact = $this->origin_id;
767
				if ($originforcontact == 'shipping') {     // shipment and order share the same contacts. If creating from shipment we take data of order
768
					require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
769
					$exp = new Expedition($this->db);
770
					$exp->fetch($this->origin_id);
771
					$exp->fetchObjectLinked(null, '', null, '', 'OR', 1, 'sourcetype', 0);
772
					if (count($exp->linkedObjectsIds['commande']) > 0) {
773
						foreach ($exp->linkedObjectsIds['commande'] as $key => $value) {
774
							$originforcontact = 'commande';
775
							if (is_object($value)) {
776
								$originidforcontact = $value->id;
777
							} else {
778
								$originidforcontact = $value;
779
							}
780
							break; // We take first one
781
						}
782
					}
783
				}
784
785
				$sqlcontact = "SELECT ctc.code, ctc.source, ec.fk_socpeople FROM ".MAIN_DB_PREFIX."element_contact as ec, ".MAIN_DB_PREFIX."c_type_contact as ctc";
786
				$sqlcontact .= " WHERE element_id = ".((int) $originidforcontact)." AND ec.fk_c_type_contact = ctc.rowid AND ctc.element = '".$this->db->escape($originforcontact)."'";
787
788
				$resqlcontact = $this->db->query($sqlcontact);
789
				if ($resqlcontact) {
790
					while ($objcontact = $this->db->fetch_object($resqlcontact)) {
791
						//print $objcontact->code.'-'.$objcontact->source.'-'.$objcontact->fk_socpeople."\n";
792
						$this->add_contact($objcontact->fk_socpeople, $objcontact->code, $objcontact->source); // May failed because of duplicate key or because code of contact type does not exists for new object
793
					}
794
				} else {
795
					dol_print_error($resqlcontact);
796
				}
797
			}
798
799
			/*
800
			 *  Insert lines of invoices, if not from template invoice, into database
801
			 */
802
			if (!$error && empty($this->fac_rec) && count($this->lines) && is_object($this->lines[0])) {	// If this->lines is array of InvoiceLines (preferred mode)
803
				$fk_parent_line = 0;
804
805
				dol_syslog("There is ".count($this->lines)." lines that are invoice lines objects");
806
				foreach ($this->lines as $i => $val) {
807
					$newinvoiceline = $this->lines[$i];
808
809
					$newinvoiceline->context = $this->context;
810
811
					$newinvoiceline->fk_facture = $this->id;
812
813
					$newinvoiceline->origin = $this->lines[$i]->element;
814
					$newinvoiceline->origin_id = $this->lines[$i]->id;
815
816
					// Auto set date of service ?
817
					if ($this->lines[$i]->date_start_fill == 1 && $originaldatewhen) {		// $originaldatewhen is defined when generating from recurring invoice only
818
						$newinvoiceline->date_start = $originaldatewhen;
819
					}
820
					if ($this->lines[$i]->date_end_fill == 1 && $previousdaynextdatewhen) {	// $previousdaynextdatewhen is defined when generating from recurring invoice only
821
						$newinvoiceline->date_end = $previousdaynextdatewhen;
822
					}
823
824
					if ($result >= 0) {
825
						// Reset fk_parent_line for no child products and special product
826
						if (($newinvoiceline->product_type != 9 && empty($newinvoiceline->fk_parent_line)) || $newinvoiceline->product_type == 9) {
827
							$fk_parent_line = 0;
828
						}
829
830
						// Complete vat rate with code
831
						$vatrate = $newinvoiceline->tva_tx;
832
						if ($newinvoiceline->vat_src_code && ! preg_match('/\(.*\)/', $vatrate)) $vatrate.=' ('.$newinvoiceline->vat_src_code.')';
833
834
						$newinvoiceline->fk_parent_line = $fk_parent_line;
835
836
						if ($this->type === Facture::TYPE_REPLACEMENT && $newinvoiceline->fk_remise_except) {
837
							$discount = new DiscountAbsolute($this->db);
838
							$discount->fetch($newinvoiceline->fk_remise_except);
839
840
							$discountId = $soc->set_remise_except($discount->amount_ht, $user, $discount->description, $discount->tva_tx);
841
							$newinvoiceline->fk_remise_except = $discountId;
842
						}
843
844
						$result = $this->addline(
845
							$newinvoiceline->desc,
846
							$newinvoiceline->subprice,
847
							$newinvoiceline->qty,
848
							$vatrate,
849
							$newinvoiceline->localtax1_tx,
850
							$newinvoiceline->localtax2_tx,
851
							$newinvoiceline->fk_product,
852
							$newinvoiceline->remise_percent,
853
							$newinvoiceline->date_start,
854
							$newinvoiceline->date_end,
855
							$newinvoiceline->fk_code_ventilation,
856
							$newinvoiceline->info_bits,
857
							$newinvoiceline->fk_remise_except,
858
							'HT',
859
							0,
860
							$newinvoiceline->product_type,
861
							$newinvoiceline->rang,
862
							$newinvoiceline->special_code,
863
							$newinvoiceline->element,
864
							$newinvoiceline->id,
865
							$fk_parent_line,
866
							$newinvoiceline->fk_fournprice,
867
							$newinvoiceline->pa_ht,
868
							$newinvoiceline->label,
869
							$newinvoiceline->array_options,
870
							$newinvoiceline->situation_percent,
871
							$newinvoiceline->fk_prev_id,
872
							$newinvoiceline->fk_unit,
873
							$newinvoiceline->multicurrency_subprice,
874
							$newinvoiceline->ref_ext,
875
							1
876
						);
877
878
						// Defined the new fk_parent_line
879
						if ($result > 0 && $newinvoiceline->product_type == 9) {
880
							$fk_parent_line = $result;
881
						}
882
					}
883
					if ($result < 0) {
884
						$this->error = $newinvoiceline->error;
885
						$this->errors = $newinvoiceline->errors;
886
						$error++;
887
						break;
888
					}
889
				}
890
			} elseif (!$error && empty($this->fac_rec)) { 		// If this->lines is an array of invoice line arrays
891
				$fk_parent_line = 0;
892
893
				dol_syslog("There is ".count($this->lines)." lines that are array lines");
894
895
				foreach ($this->lines as $i => $val) {
896
					$line = $this->lines[$i];
897
898
					// Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
899
					//if (! is_object($line)) $line=json_decode(json_encode($line), false);  // convert recursively array into object.
900
					if (!is_object($line)) {
901
						$line = (object) $line;
902
					}
903
904
					if ($result >= 0) {
905
						// Reset fk_parent_line for no child products and special product
906
						if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
907
							$fk_parent_line = 0;
908
						}
909
910
						// Complete vat rate with code
911
						$vatrate = $line->tva_tx;
912
						if ($line->vat_src_code && !preg_match('/\(.*\)/', $vatrate)) {
913
							$vatrate .= ' ('.$line->vat_src_code.')';
914
						}
915
916
						if (!empty($conf->global->MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION)) {
917
							$originid = $line->origin_id;
918
							$origintype = $line->origin;
919
						} else {
920
							$originid = $line->id;
921
							$origintype = $this->element;
922
						}
923
924
						// init ref_ext
925
						if (empty($line->ref_ext)) {
926
							$line->ref_ext = '';
927
						}
928
929
						$result = $this->addline(
930
							$line->desc,
931
							$line->subprice,
932
							$line->qty,
933
							$vatrate,
934
							$line->localtax1_tx,
935
							$line->localtax2_tx,
936
							$line->fk_product,
937
							$line->remise_percent,
938
							$line->date_start,
939
							$line->date_end,
940
							$line->fk_code_ventilation,
941
							$line->info_bits,
942
							$line->fk_remise_except,
943
							'HT',
944
							0,
945
							$line->product_type,
946
							$line->rang,
947
							$line->special_code,
948
							$origintype,
949
							$originid,
950
							$fk_parent_line,
951
							$line->fk_fournprice,
952
							$line->pa_ht,
953
							$line->label,
954
							$line->array_options,
955
							$line->situation_percent,
956
							$line->fk_prev_id,
957
							$line->fk_unit,
958
							$line->multicurrency_subprice,
959
							$line->ref_ext,
960
							1
961
						);
962
						if ($result < 0) {
963
							$this->error = $this->db->lasterror();
964
							dol_print_error($this->db);
965
							$this->db->rollback();
966
							return -1;
967
						}
968
969
						// Defined the new fk_parent_line
970
						if ($result > 0 && $line->product_type == 9) {
971
							$fk_parent_line = $result;
972
						}
973
					}
974
				}
975
			}
976
977
			/*
978
			 * Insert lines of template invoices
979
			 */
980
			if (!$error && $this->fac_rec > 0) {
981
				foreach ($_facrec->lines as $i => $val) {
982
					if ($_facrec->lines[$i]->fk_product) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $_facrec does not seem to be defined for all execution paths leading up to this point.
Loading history...
983
						$prod = new Product($this->db);
984
						$res = $prod->fetch($_facrec->lines[$i]->fk_product);
985
					}
986
987
					// For line from template invoice, we use data from template invoice
988
					/*
989
					$tva_tx = get_default_tva($mysoc,$soc,$prod->id);
990
					$tva_npr = get_default_npr($mysoc,$soc,$prod->id);
991
					if (empty($tva_tx)) $tva_npr=0;
992
					$localtax1_tx=get_localtax($tva_tx,1,$soc,$mysoc,$tva_npr);
993
					$localtax2_tx=get_localtax($tva_tx,2,$soc,$mysoc,$tva_npr);
994
					*/
995
					$tva_tx = $_facrec->lines[$i]->tva_tx.($_facrec->lines[$i]->vat_src_code ? '('.$_facrec->lines[$i]->vat_src_code.')' : '');
996
					$tva_npr = $_facrec->lines[$i]->info_bits;
997
					if (empty($tva_tx)) {
998
						$tva_npr = 0;
999
					}
1000
					$localtax1_tx = $_facrec->lines[$i]->localtax1_tx;
1001
					$localtax2_tx = $_facrec->lines[$i]->localtax2_tx;
1002
1003
					$fk_product_fournisseur_price = empty($_facrec->lines[$i]->fk_product_fournisseur_price) ? null : $_facrec->lines[$i]->fk_product_fournisseur_price;
1004
					$buyprice = empty($_facrec->lines[$i]->buyprice) ? 0 : $_facrec->lines[$i]->buyprice;
1005
1006
					// If buyprice not defined from template invoice, we try to guess the best value
1007
					if (!$buyprice && $_facrec->lines[$i]->fk_product > 0) {
1008
						require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
1009
						$producttmp = new ProductFournisseur($this->db);
1010
						$producttmp->fetch($_facrec->lines[$i]->fk_product);
1011
1012
						// If margin module defined on costprice, we try the costprice
1013
						// If not defined or if module margin defined and pmp and stock module enabled, we try pmp price
1014
						// else we get the best supplier price
1015
						if ($conf->global->MARGIN_TYPE == 'costprice' && !empty($producttmp->cost_price)) {
1016
							$buyprice = $producttmp->cost_price;
1017
						} elseif (isModEnabled('stock') && ($conf->global->MARGIN_TYPE == 'costprice' || $conf->global->MARGIN_TYPE == 'pmp') && !empty($producttmp->pmp)) {
1018
							$buyprice = $producttmp->pmp;
1019
						} else {
1020
							if ($producttmp->find_min_price_product_fournisseur($_facrec->lines[$i]->fk_product) > 0) {
1021
								if ($producttmp->product_fourn_price_id > 0) {
1022
									$buyprice = price2num($producttmp->fourn_unitprice * (1 - $producttmp->fourn_remise_percent / 100) + $producttmp->fourn_remise, 'MU');
1023
								}
1024
							}
1025
						}
1026
					}
1027
1028
					$result_insert = $this->addline(
1029
						$_facrec->lines[$i]->desc,
1030
						$_facrec->lines[$i]->subprice,
1031
						$_facrec->lines[$i]->qty,
1032
						$tva_tx,
1033
						$localtax1_tx,
1034
						$localtax2_tx,
1035
						$_facrec->lines[$i]->fk_product,
1036
						$_facrec->lines[$i]->remise_percent,
1037
						($_facrec->lines[$i]->date_start_fill == 1 && $originaldatewhen) ? $originaldatewhen : '',
1038
						($_facrec->lines[$i]->date_end_fill == 1 && $previousdaynextdatewhen) ? $previousdaynextdatewhen : '',
1039
						0,
1040
						$tva_npr,
1041
						'',
1042
						'HT',
1043
						0,
1044
						$_facrec->lines[$i]->product_type,
1045
						$_facrec->lines[$i]->rang,
1046
						$_facrec->lines[$i]->special_code,
1047
						'',
1048
						0,
1049
						0,
1050
						$fk_product_fournisseur_price,
1051
						$buyprice,
1052
						$_facrec->lines[$i]->label,
1053
						empty($_facrec->lines[$i]->array_options) ?null:$_facrec->lines[$i]->array_options,
1054
						$_facrec->lines[$i]->situation_percent,
1055
						'',
1056
						$_facrec->lines[$i]->fk_unit,
1057
						$_facrec->lines[$i]->multicurrency_subprice,
1058
						$_facrec->lines[$i]->ref_ext,
1059
						1
1060
					);
1061
1062
					if ($result_insert < 0) {
1063
						$error++;
1064
						$this->error = $this->db->error();
1065
						break;
1066
					}
1067
				}
1068
			}
1069
1070
			if (!$error) {
1071
				$result = $this->update_price(1, 'auto', 0, $mysoc);
1072
				if ($result > 0) {
1073
					$action = 'create';
1074
1075
					// Actions on extra fields
1076
					if (!$error) {
1077
						$result = $this->insertExtraFields();
1078
						if ($result < 0) {
1079
							$error++;
1080
						}
1081
					}
1082
1083
					if (!$error && !$notrigger) {
1084
						// Call trigger
1085
						$result = $this->call_trigger('BILL_CREATE', $user);
1086
						if ($result < 0) {
1087
							$error++;
1088
						}
1089
						// End call triggers
1090
					}
1091
1092
					if (!$error) {
1093
						$this->db->commit();
1094
						return $this->id;
1095
					} else {
1096
						$this->db->rollback();
1097
						return -4;
1098
					}
1099
				} else {
1100
					$this->error = $langs->trans('FailedToUpdatePrice');
1101
					$this->db->rollback();
1102
					return -3;
1103
				}
1104
			} else {
1105
				dol_syslog(get_class($this)."::create error ".$this->error, LOG_ERR);
1106
				$this->db->rollback();
1107
				return -2;
1108
			}
1109
		} else {
1110
			$this->error = $this->db->error();
1111
			$this->db->rollback();
1112
			return -1;
1113
		}
1114
	}
1115
1116
1117
	/**
1118
	 *	Create a new invoice in database from current invoice
1119
	 *
1120
	 *	@param      User	$user    		Object user that ask creation
1121
	 *	@param		int		$invertdetail	Reverse sign of amounts for lines
1122
	 *	@return		int						<0 if KO, >0 if OK
1123
	 */
1124
	public function createFromCurrent(User $user, $invertdetail = 0)
1125
	{
1126
		global $conf;
1127
1128
		// Source invoice load
1129
		$facture = new Facture($this->db);
1130
1131
		// Retrieve all extrafield
1132
		// fetch optionals attributes and labels
1133
		$this->fetch_optionals();
1134
1135
		if (!empty($this->array_options)) {
1136
					$facture->array_options = $this->array_options;
1137
		}
1138
1139
		foreach ($this->lines as &$line) {
1140
					$line->fetch_optionals(); //fetch extrafields
1141
		}
1142
1143
		$facture->fk_facture_source = $this->fk_facture_source;
1144
		$facture->type 			    = $this->type;
1145
		$facture->socid 		    = $this->socid;
1146
		$facture->date              = $this->date;
1147
		$facture->date_pointoftax   = $this->date_pointoftax;
1148
		$facture->note_public       = $this->note_public;
1149
		$facture->note_private      = $this->note_private;
1150
		$facture->ref_client        = $this->ref_client;
1151
		$facture->modelpdf          = $this->model_pdf; // deprecated
1152
		$facture->model_pdf         = $this->model_pdf;
1153
		$facture->fk_project        = $this->fk_project;
1154
		$facture->cond_reglement_id = $this->cond_reglement_id;
1155
		$facture->mode_reglement_id = $this->mode_reglement_id;
1156
		$facture->remise_absolue    = $this->remise_absolue;
1157
		$facture->remise_percent    = $this->remise_percent;
1158
1159
		$facture->origin            = $this->origin;
1160
		$facture->origin_id         = $this->origin_id;
1161
1162
		$facture->lines = $this->lines; // Array of lines of invoice
1163
		$facture->situation_counter = $this->situation_counter;
1164
		$facture->situation_cycle_ref = $this->situation_cycle_ref;
1165
		$facture->situation_final = $this->situation_final;
1166
1167
		$facture->retained_warranty = $this->retained_warranty;
1168
		$facture->retained_warranty_fk_cond_reglement = $this->retained_warranty_fk_cond_reglement;
1169
		$facture->retained_warranty_date_limit = $this->retained_warranty_date_limit;
1170
1171
		$facture->fk_user_author = $user->id;
1172
1173
1174
		// Loop on each line of new invoice
1175
		foreach ($facture->lines as $i => $tmpline) {
1176
			$facture->lines[$i]->fk_prev_id = $this->lines[$i]->rowid;
1177
			if ($invertdetail) {
1178
				$facture->lines[$i]->subprice  = -$facture->lines[$i]->subprice;
1179
				$facture->lines[$i]->total_ht  = -$facture->lines[$i]->total_ht;
1180
				$facture->lines[$i]->total_tva = -$facture->lines[$i]->total_tva;
1181
				$facture->lines[$i]->total_localtax1 = -$facture->lines[$i]->total_localtax1;
1182
				$facture->lines[$i]->total_localtax2 = -$facture->lines[$i]->total_localtax2;
1183
				$facture->lines[$i]->total_ttc = -$facture->lines[$i]->total_ttc;
1184
				$facture->lines[$i]->ref_ext = '';
1185
			}
1186
		}
1187
1188
		dol_syslog(get_class($this)."::createFromCurrent invertdetail=".$invertdetail." socid=".$this->socid." nboflines=".count($facture->lines));
1189
1190
		$facid = $facture->create($user);
1191
		if ($facid <= 0) {
1192
			$this->error = $facture->error;
1193
			$this->errors = $facture->errors;
1194
		} elseif ($this->type == self::TYPE_SITUATION && !empty($conf->global->INVOICE_USE_SITUATION)) {
1195
			$this->fetchObjectLinked('', '', $this->id, 'facture');
1196
1197
			foreach ($this->linkedObjectsIds as $typeObject => $Tfk_object) {
1198
				foreach ($Tfk_object as $fk_object) {
1199
					$facture->add_object_linked($typeObject, $fk_object);
1200
				}
1201
			}
1202
1203
			$facture->add_object_linked('facture', $this->fk_facture_source);
1204
		}
1205
1206
		return $facid;
1207
	}
1208
1209
1210
	/**
1211
	 *	Load an object from its id and create a new one in database
1212
	 *
1213
	 *	@param      User	$user        	User that clone
1214
	 *  @param  	int 	$fromid         Id of object to clone
1215
	 * 	@return		int					    New id of clone
1216
	 */
1217
	public function createFromClone(User $user, $fromid = 0)
1218
	{
1219
		global $conf, $hookmanager;
1220
1221
		$error = 0;
1222
1223
		$object = new Facture($this->db);
1224
1225
		$this->db->begin();
1226
1227
		$object->fetch($fromid);
1228
1229
		// Load source object
1230
		$objFrom = clone $object;
1231
1232
		// Change socid if needed
1233
		if (!empty($this->socid) && $this->socid != $object->socid) {
1234
			$objsoc = new Societe($this->db);
1235
1236
			if ($objsoc->fetch($this->socid) > 0) {
1237
				$object->socid = $objsoc->id;
1238
				$object->cond_reglement_id	= (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1239
				$object->mode_reglement_id	= (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1240
				$object->fk_project = '';
1241
				$object->fk_delivery_address = '';
1242
			}
1243
1244
			// TODO Change product price if multi-prices
1245
		}
1246
1247
		$object->id = 0;
1248
		$object->statut = self::STATUS_DRAFT;
1249
		$object->status = self::STATUS_DRAFT;
1250
1251
		// Clear fields
1252
		$object->date               = (empty($this->date) ? dol_now() : $this->date);
1253
		$object->user_author        = $user->id; // deprecated
1254
		$object->user_valid         = null; // deprecated
1255
		$object->fk_user_author     = $user->id;
1256
		$object->fk_user_valid      = null;
1257
		$object->fk_facture_source  = 0;
1258
		$object->date_creation      = '';
1259
		$object->date_modification = '';
1260
		$object->date_validation    = '';
1261
		$object->ref_client         = '';
1262
		$object->close_code         = '';
1263
		$object->close_note         = '';
1264
		if ($conf->global->MAIN_DONT_KEEP_NOTE_ON_CLONING == 1) {
1265
			$object->note_private = '';
1266
			$object->note_public = '';
1267
		}
1268
1269
		// Loop on each line of new invoice
1270
		foreach ($object->lines as $i => $line) {
1271
			if (($object->lines[$i]->info_bits & 0x02) == 0x02) {	// We do not clone line of discounts
1272
				unset($object->lines[$i]);
1273
				continue;
1274
			}
1275
1276
			// Bloc to update dates of service (month by month only if previously filled and similare to start and end of month)
1277
			// If it's a service with start and end dates
1278
			if (!empty($conf->global->INVOICE_AUTO_NEXT_MONTH_ON_LINES) && !empty($line->date_start) && !empty($line->date_end)) {
1279
				// Get the dates
1280
				$start = dol_getdate($line->date_start);
1281
				$end = dol_getdate($line->date_end);
1282
1283
				// Get the first and last day of the month
1284
				$first = dol_get_first_day($start['year'], $start['mon']);
1285
				$last = dol_get_last_day($end['year'], $end['mon']);
1286
1287
				//print dol_print_date(dol_mktime(0, 0, 0, $start['mon'], $start['mday'], $start['year'], 'gmt'), 'dayhour').' '.dol_print_date($first, 'dayhour').'<br>';
1288
				//print dol_mktime(23, 59, 59, $end['mon'], $end['mday'], $end['year'], 'gmt').' '.$last.'<br>';exit;
1289
				// If start date is first date of month and end date is last date of month
1290
				if (dol_mktime(0, 0, 0, $start['mon'], $start['mday'], $start['year'], 'gmt') == $first
1291
					&& dol_mktime(23, 59, 59, $end['mon'], $end['mday'], $end['year'], 'gmt') == $last) {
1292
					$nextMonth = dol_get_next_month($end['mon'], $end['year']);
1293
					$newFirst = dol_get_first_day($nextMonth['year'], $nextMonth['month']);
1294
					$newLast = dol_get_last_day($nextMonth['year'], $nextMonth['month']);
1295
					$object->lines[$i]->date_start = $newFirst;
1296
					$object->lines[$i]->date_end = $newLast;
1297
				}
1298
			}
1299
1300
			$object->lines[$i]->ref_ext = ''; // Do not clone ref_ext
1301
		}
1302
1303
		// Create clone
1304
		$object->context['createfromclone'] = 'createfromclone';
1305
		$result = $object->create($user);
1306
		if ($result < 0) {
1307
			$error++;
1308
			$this->error = $object->error;
1309
			$this->errors = $object->errors;
1310
		} else {
1311
			// copy internal contacts
1312
			if ($object->copy_linked_contact($objFrom, 'internal') < 0) {
1313
				$error++;
1314
				$this->error = $object->error;
1315
				$this->errors = $object->errors;
1316
			} elseif ($object->socid == $objFrom->socid) {
1317
				// copy external contacts if same company
1318
				if ($object->copy_linked_contact($objFrom, 'external') < 0) {
1319
					$error++;
1320
					$this->error = $object->error;
1321
					$this->errors = $object->errors;
1322
				}
1323
			}
1324
		}
1325
1326
		if (!$error) {
1327
			// Hook of thirdparty module
1328
			if (is_object($hookmanager)) {
1329
				$parameters = array('objFrom'=>$objFrom);
1330
				$action = '';
1331
				$reshook = $hookmanager->executeHooks('createFrom', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
1332
				if ($reshook < 0) {
1333
					$error++;
1334
				}
1335
			}
1336
		}
1337
1338
		unset($object->context['createfromclone']);
1339
1340
		// End
1341
		if (!$error) {
1342
			$this->db->commit();
1343
			return $object->id;
1344
		} else {
1345
			$this->db->rollback();
1346
			return -1;
1347
		}
1348
	}
1349
1350
	/**
1351
	 *  Load an object from an order and create a new invoice into database
1352
	 *
1353
	 *  @param      Object			$object         	Object source
1354
	 *  @param		User			$user				Object user
1355
	 *  @return     int             					<0 if KO, 0 if nothing done, 1 if OK
1356
	 */
1357
	public function createFromOrder($object, User $user)
1358
	{
1359
		global $conf, $hookmanager;
1360
1361
		$error = 0;
1362
1363
		// Closed order
1364
		$this->date = dol_now();
1365
		$this->source = 0;
1366
1367
		$num = count($object->lines);
1368
		for ($i = 0; $i < $num; $i++) {
1369
			$line = new FactureLigne($this->db);
1370
1371
			$line->libelle = $object->lines[$i]->libelle; // deprecated
1372
			$line->label			= $object->lines[$i]->label;
1373
			$line->desc				= $object->lines[$i]->desc;
1374
			$line->subprice			= $object->lines[$i]->subprice;
1375
			$line->total_ht			= $object->lines[$i]->total_ht;
1376
			$line->total_tva		= $object->lines[$i]->total_tva;
1377
			$line->total_localtax1	= $object->lines[$i]->total_localtax1;
1378
			$line->total_localtax2	= $object->lines[$i]->total_localtax2;
1379
			$line->total_ttc		= $object->lines[$i]->total_ttc;
1380
			$line->vat_src_code = $object->lines[$i]->vat_src_code;
1381
			$line->tva_tx = $object->lines[$i]->tva_tx;
1382
			$line->localtax1_tx		= $object->lines[$i]->localtax1_tx;
1383
			$line->localtax2_tx		= $object->lines[$i]->localtax2_tx;
1384
			$line->qty = $object->lines[$i]->qty;
1385
			$line->fk_remise_except = $object->lines[$i]->fk_remise_except;
1386
			$line->remise_percent = $object->lines[$i]->remise_percent;
1387
			$line->fk_product = $object->lines[$i]->fk_product;
1388
			$line->info_bits = $object->lines[$i]->info_bits;
1389
			$line->product_type		= $object->lines[$i]->product_type;
1390
			$line->rang = $object->lines[$i]->rang;
1391
			$line->special_code		= $object->lines[$i]->special_code;
1392
			$line->fk_parent_line = $object->lines[$i]->fk_parent_line;
1393
			$line->fk_unit = $object->lines[$i]->fk_unit;
1394
			$line->date_start = $object->lines[$i]->date_start;
1395
			$line->date_end = $object->lines[$i]->date_end;
1396
1397
			// Multicurrency
1398
			$line->fk_multicurrency = $object->lines[$i]->fk_multicurrency;
1399
			$line->multicurrency_code = $object->lines[$i]->multicurrency_code;
1400
			$line->multicurrency_subprice = $object->lines[$i]->multicurrency_subprice;
1401
			$line->multicurrency_total_ht = $object->lines[$i]->multicurrency_total_ht;
1402
			$line->multicurrency_total_tva = $object->lines[$i]->multicurrency_total_tva;
1403
			$line->multicurrency_total_ttc = $object->lines[$i]->multicurrency_total_ttc;
1404
1405
			$line->fk_fournprice = $object->lines[$i]->fk_fournprice;
1406
			$marginInfos			= getMarginInfos($object->lines[$i]->subprice, $object->lines[$i]->remise_percent, $object->lines[$i]->tva_tx, $object->lines[$i]->localtax1_tx, $object->lines[$i]->localtax2_tx, $object->lines[$i]->fk_fournprice, $object->lines[$i]->pa_ht);
1407
			$line->pa_ht			= $marginInfos[0];
1408
1409
			// get extrafields from original line
1410
			$object->lines[$i]->fetch_optionals();
1411
			foreach ($object->lines[$i]->array_options as $options_key => $value) {
1412
				$line->array_options[$options_key] = $value;
1413
			}
1414
1415
			$this->lines[$i] = $line;
1416
		}
1417
1418
		$this->socid                = $object->socid;
1419
		$this->fk_project           = $object->fk_project;
1420
		$this->fk_account = $object->fk_account;
1421
		$this->cond_reglement_id    = $object->cond_reglement_id;
1422
		$this->mode_reglement_id    = $object->mode_reglement_id;
1423
		$this->availability_id      = $object->availability_id;
1424
		$this->demand_reason_id     = $object->demand_reason_id;
1425
		$this->delivery_date        = (empty($object->delivery_date) ? $object->date_livraison : $object->delivery_date);
1426
		$this->date_livraison       = $object->delivery_date; // deprecated
1427
		$this->fk_delivery_address  = $object->fk_delivery_address; // deprecated
1428
		$this->contact_id           = $object->contact_id;
1429
		$this->ref_client           = $object->ref_client;
1430
1431
		if (empty($conf->global->MAIN_DISABLE_PROPAGATE_NOTES_FROM_ORIGIN)) {
1432
			$this->note_private = $object->note_private;
1433
			$this->note_public = $object->note_public;
1434
		}
1435
1436
		$this->module_source = $object->module_source;
1437
		$this->pos_source = $object->pos_source;
1438
1439
		$this->origin = $object->element;
1440
		$this->origin_id = $object->id;
1441
1442
		$this->fk_user_author = $user->id;
1443
1444
		// get extrafields from original line
1445
		$object->fetch_optionals();
1446
		foreach ($object->array_options as $options_key => $value) {
1447
			$this->array_options[$options_key] = $value;
1448
		}
1449
1450
		// Possibility to add external linked objects with hooks
1451
		$this->linked_objects[$this->origin] = $this->origin_id;
1452
		if (!empty($object->other_linked_objects) && is_array($object->other_linked_objects)) {
1453
			$this->linked_objects = array_merge($this->linked_objects, $object->other_linked_objects);
1454
		}
1455
1456
		$ret = $this->create($user);
1457
1458
		if ($ret > 0) {
1459
			// Actions hooked (by external module)
1460
			$hookmanager->initHooks(array('invoicedao'));
1461
1462
			$parameters = array('objFrom'=>$object);
1463
			$action = '';
1464
			$reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1465
			if ($reshook < 0) {
1466
				$error++;
1467
			}
1468
1469
			if (!$error) {
1470
				return 1;
1471
			} else {
1472
				return -1;
1473
			}
1474
		} else {
1475
			return -1;
1476
		}
1477
	}
1478
1479
	/**
1480
	 * Creates a deposit from a proposal or an order by grouping lines by VAT rates
1481
	 *
1482
	 * @param	Propal|Commande		$origin					The original proposal or order
1483
	 * @param	int					$date					Invoice date
1484
	 * @param	int					$payment_terms_id		Invoice payment terms
1485
	 * @param	User				$user					Object user
1486
	 * @param	int					$notrigger				1=Does not execute triggers, 0= execute triggers
1487
	 * @param	bool				$autoValidateDeposit	Whether to aumatically validate the deposit created
1488
	 * @param	array				$overrideFields			Array of fields to force values
1489
	 * @return	Facture|null								The deposit created, or null if error (populates $origin->error in this case)
1490
	 */
1491
	static public function createDepositFromOrigin(CommonObject $origin, $date, $payment_terms_id, User $user, $notrigger = 0, $autoValidateDeposit = false, $overrideFields = array())
1492
	{
1493
		global $conf, $langs, $hookmanager, $action;
1494
1495
		if (! in_array($origin->element, array('propal', 'commande'))) {
1496
			$origin->error = 'ErrorCanOnlyAutomaticallyGenerateADepositFromProposalOrOrder';
1497
			return null;
1498
		}
1499
1500
		if (empty($date)) {
1501
			$origin->error = $langs->trans('ErrorFieldRequired', $langs->transnoentities('DateInvoice'));
1502
			return null;
1503
		}
1504
1505
		require_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
1506
1507
		if ($date > (dol_get_last_hour(dol_now('tzuserrel')) + (empty($conf->global->INVOICE_MAX_FUTURE_DELAY) ? 0 : $conf->global->INVOICE_MAX_FUTURE_DELAY))) {
1508
			$origin->error = 'ErrorDateIsInFuture';
1509
			return null;
1510
		}
1511
1512
		if ($payment_terms_id <= 0) {
1513
			$origin->error = $langs->trans('ErrorFieldRequired', $langs->transnoentities('PaymentConditionsShort'));
1514
			return null;
1515
		}
1516
1517
		$payment_conditions_deposit_percent = getDictionaryValue('c_payment_term', 'deposit_percent', $origin->cond_reglement_id);
1518
1519
		if (empty($payment_conditions_deposit_percent)) {
1520
			$origin->error = 'ErrorPaymentConditionsNotEligibleToDepositCreation';
1521
			return null;
1522
		}
1523
1524
		if (empty($origin->deposit_percent)) {
1525
			$origin->error = $langs->trans('ErrorFieldRequired', $langs->transnoentities('DepositPercent'));
1526
			return null;
1527
		}
1528
1529
		$deposit = new self($origin->db);
1530
		$deposit->socid = $origin->socid;
1531
		$deposit->type = self::TYPE_DEPOSIT;
1532
		$deposit->fk_project = $origin->fk_project;
1533
		$deposit->ref_client = $origin->ref_client;
1534
		$deposit->date = $date;
1535
		$deposit->mode_reglement_id = $origin->mode_reglement_id;
1536
		$deposit->cond_reglement_id = $payment_terms_id;
1537
		$deposit->availability_id = $origin->availability_id;
1538
		$deposit->demand_reason_id = $origin->demand_reason_id;
1539
		$deposit->fk_account = $origin->fk_account;
1540
		$deposit->fk_incoterms = $origin->fk_incoterms;
1541
		$deposit->location_incoterms = $origin->location_incoterms;
1542
		$deposit->fk_multicurrency = $origin->fk_multicurrency;
1543
		$deposit->multicurrency_code = $origin->multicurrency_code;
1544
		$deposit->multicurrency_tx = $origin->multicurrency_tx;
1545
		$deposit->module_source = $origin->module_source;
1546
		$deposit->pos_source = $origin->pos_source;
1547
		$deposit->model_pdf = 'crabe';
1548
1549
		$modelByTypeConfName = 'FACTURE_ADDON_PDF_' . $deposit->type;
1550
1551
		if (!empty($conf->global->$modelByTypeConfName)) {
1552
			$deposit->model_pdf = $conf->global->$modelByTypeConfName;
1553
		} elseif (!empty($conf->global->FACTURE_ADDON_PDF)) {
1554
			$deposit->model_pdf = $conf->global->FACTURE_ADDON_PDF;
1555
		}
1556
1557
		if (empty($conf->global->MAIN_DISABLE_PROPAGATE_NOTES_FROM_ORIGIN)) {
1558
			$deposit->note_private = $origin->note_private;
1559
			$deposit->note_public = $origin->note_public;
1560
		}
1561
1562
		$deposit->origin = $origin->element;
1563
		$deposit->origin_id = $origin->id;
1564
1565
		$origin->fetch_optionals();
1566
1567
		foreach ($origin->array_options as $extrakey => $value) {
1568
			$deposit->array_options[$extrakey] = $value;
1569
		}
1570
1571
		$deposit->linked_objects[$deposit->origin] = $deposit->origin_id;
1572
1573
		foreach ($overrideFields as $key => $value) {
1574
			$deposit->$key = $value;
1575
		}
1576
1577
		$deposit->context['createdepositfromorigin'] = 'createdepositfromorigin';
1578
1579
		$origin->db->begin();
1580
1581
		// Facture::create() also imports contact from origin
1582
		$createReturn = $deposit->create($user, $notrigger);
1583
1584
		if ($createReturn <= 0) {
1585
			$origin->db->rollback();
1586
			$origin->error = $deposit->error;
1587
			$origin->errors = $deposit->errors;
1588
			return null;
1589
		}
1590
1591
		$amount_ttc_diff = 0;
1592
		$amountdeposit = array();
1593
		$descriptions = array();
1594
1595
		if (!empty($conf->global->MAIN_DEPOSIT_MULTI_TVA)) {
1596
			$amount = $origin->total_ttc * ($origin->deposit_percent / 100);
1597
1598
			$TTotalByTva = array();
1599
			foreach ($origin->lines as &$line) {
1600
				if (!empty($line->special_code)) {
1601
					continue;
1602
				}
1603
				$TTotalByTva[$line->tva_tx] += $line->total_ttc;
1604
				$descriptions[$line->tva_tx] .= '<li>' . (!empty($line->product_ref) ? $line->product_ref . ' - ' :  '');
1605
				$descriptions[$line->tva_tx] .= (!empty($line->product_label) ? $line->product_label . ' - ' : '');
1606
				$descriptions[$line->tva_tx] .= $langs->trans('Qty') . ' : ' . $line->qty;
1607
				$descriptions[$line->tva_tx] .= ' - ' . $langs->trans('TotalHT') . ' : ' . price($line->total_ht) . '</li>';
1608
			}
1609
1610
			foreach ($TTotalByTva as $tva => &$total) {
1611
				$coef = $total / $origin->total_ttc; // Calc coef
1612
				$am = $amount * $coef;
1613
				$amount_ttc_diff += $am;
1614
				$amountdeposit[$tva] += $am / (1 + $tva / 100); // Convert into HT for the addline
1615
			}
1616
		} else {
1617
			$totalamount = 0;
1618
			$lines = $origin->lines;
1619
			$numlines = count($lines);
1620
			for ($i = 0; $i < $numlines; $i++) {
1621
				if (empty($lines[$i]->qty)) {
1622
					continue; // We discard qty=0, it is an option
1623
				}
1624
				if (!empty($lines[$i]->special_code)) {
1625
					continue; // We discard special_code (frais port, ecotaxe, option, ...)
1626
				}
1627
1628
				$totalamount += $lines[$i]->total_ht; // Fixme : is it not for the customer ? Shouldn't we take total_ttc ?
1629
				$tva_tx = $lines[$i]->tva_tx;
1630
				$amountdeposit[$tva_tx] += ($lines[$i]->total_ht * $origin->deposit_percent) / 100;
1631
				$descriptions[$tva_tx] .= '<li>' . (!empty($lines[$i]->product_ref) ? $lines[$i]->product_ref . ' - ' :  '');
1632
				$descriptions[$tva_tx] .= (!empty($lines[$i]->product_label) ? $lines[$i]->product_label . ' - ' : '');
1633
				$descriptions[$tva_tx] .= $langs->trans('Qty') . ' : ' . $lines[$i]->qty;
1634
				$descriptions[$tva_tx] .= ' - ' . $langs->trans('TotalHT') . ' : ' . price($lines[$i]->total_ht) . '</li>';
1635
			}
1636
1637
			if ($totalamount == 0) {
1638
				$amountdeposit[0] = 0;
1639
			}
1640
1641
			$amount_ttc_diff = $amountdeposit[0];
1642
		}
1643
1644
		foreach ($amountdeposit as $tva => $amount) {
1645
			if (empty($amount)) {
1646
				continue;
1647
			}
1648
1649
			$descline = '(DEPOSIT) ('. $origin->deposit_percent .'%) - '.$origin->ref;
1650
1651
			// Hidden conf
1652
			if (!empty($conf->global->INVOICE_DEPOSIT_VARIABLE_MODE_DETAIL_LINES_IN_DESCRIPTION) && !empty($descriptions[$tva])) {
1653
				$descline .= '<ul>' . $descriptions[$tva] . '</ul>';
1654
			}
1655
1656
			$addlineResult = $deposit->addline(
1657
				$descline,
1658
				$amount, // subprice
1659
				1, // quantity
1660
				$tva, // vat rate
1661
				0, // localtax1_tx
1662
				0, // localtax2_tx
1663
				(empty($conf->global->INVOICE_PRODUCTID_DEPOSIT) ? 0 : $conf->global->INVOICE_PRODUCTID_DEPOSIT), // fk_product
1664
				0, // remise_percent
1665
				0, // date_start
1666
				0, // date_end
1667
				0,
1668
				0, // info_bits
1669
				0,
1670
				'HT',
1671
				0,
1672
				0, // product_type
1673
				1,
1674
				0, // special_code
1675
				$deposit->origin,
1676
				0,
1677
				0,
1678
				0,
1679
				0
1680
				//,$langs->trans('Deposit') //Deprecated
1681
			);
1682
1683
			if ($addlineResult < 0) {
1684
				$origin->db->rollback();
1685
				$origin->error = $deposit->error;
1686
				$origin->errors = $deposit->errors;
1687
				return null;
1688
			}
1689
		}
1690
1691
		$diff = $deposit->total_ttc - $amount_ttc_diff;
1692
1693
		if (!empty($conf->global->MAIN_DEPOSIT_MULTI_TVA) && $diff != 0) {
1694
			$deposit->fetch_lines();
1695
			$subprice_diff = $deposit->lines[0]->subprice - $diff / (1 + $deposit->lines[0]->tva_tx / 100);
1696
1697
			$updatelineResult = $deposit->updateline(
1698
				$deposit->lines[0]->id,
1699
				$deposit->lines[0]->desc,
1700
				$subprice_diff,
1701
				$deposit->lines[0]->qty,
1702
				$deposit->lines[0]->remise_percent,
1703
				$deposit->lines[0]->date_start,
1704
				$deposit->lines[0]->date_end,
1705
				$deposit->lines[0]->tva_tx,
1706
				0,
1707
				0,
1708
				'HT',
1709
				$deposit->lines[0]->info_bits,
1710
				$deposit->lines[0]->product_type,
1711
				0,
1712
				0,
1713
				0,
1714
				$deposit->lines[0]->pa_ht,
1715
				$deposit->lines[0]->label,
1716
				0,
1717
				array(),
1718
				100
1719
			);
1720
1721
			if ($updatelineResult < 0) {
1722
				$origin->db->rollback();
1723
				$origin->error = $deposit->error;
1724
				$origin->errors = $deposit->errors;
1725
				return null;
1726
			}
1727
		}
1728
1729
1730
		if (! is_object($hookmanager)) {
1731
			require_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
1732
			$hookmanager = new HookManager($origin->db);
1733
		}
1734
1735
		$hookmanager->initHooks(array('invoicedao'));
1736
1737
		$parameters = array('objFrom' => $origin);
1738
		$reshook = $hookmanager->executeHooks('createFrom', $parameters, $deposit, $action); // Note that $action and $object may have been
1739
		// modified by hook
1740
		if ($reshook < 0) {
1741
			$origin->db->rollback();
1742
			$origin->error = $hookmanager->error;
1743
			$origin->errors = $hookmanager->errors;
1744
			return null;
1745
		}
1746
1747
		if (!empty($autoValidateDeposit)) {
1748
			$validateReturn = $deposit->validate($user, '', 0, $notrigger);
1749
1750
			if ($validateReturn < 0) {
1751
				$origin->db->rollback();
1752
				$origin->error = $deposit->error;
1753
				$origin->errors = $deposit->errors;
1754
				return null;
1755
			}
1756
		}
1757
1758
		unset($deposit->context['createdepositfromorigin']);
1759
1760
		$origin->db->commit();
1761
1762
		return $deposit;
1763
	}
1764
1765
	/**
1766
	 *  Return clicable link of object (with eventually picto)
1767
	 *
1768
	 *  @param	int		$withpicto       			Add picto into link
1769
	 *  @param  string	$option          			Where point the link
1770
	 *  @param  int		$max             			Maxlength of ref
1771
	 *  @param  int		$short           			1=Return just URL
1772
	 *  @param  string  $moretitle       			Add more text to title tooltip
1773
	 *  @param	int  	$notooltip		 			1=Disable tooltip
1774
	 *  @param  int     $addlinktonotes  			1=Add link to notes
1775
	 *  @param  int     $save_lastsearch_value		-1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
1776
	 *  @param  string  $target                     Target of link ('', '_self', '_blank', '_parent', '_backoffice', ...)
1777
	 *  @return string 			         			String with URL
1778
	 */
1779
	public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $moretitle = '', $notooltip = 0, $addlinktonotes = 0, $save_lastsearch_value = -1, $target = '')
1780
	{
1781
		global $langs, $conf, $user, $mysoc;
1782
1783
		if (!empty($conf->dol_no_mouse_hover)) {
1784
			$notooltip = 1; // Force disable tooltips
1785
		}
1786
1787
		$result = '';
1788
1789
		if ($option == 'withdraw') {
1790
			$url = DOL_URL_ROOT.'/compta/facture/prelevement.php?facid='.$this->id;
1791
		} else {
1792
			$url = DOL_URL_ROOT.'/compta/facture/card.php?facid='.$this->id;
1793
		}
1794
1795
		if (!$user->hasRight("facture", "read")) {
1796
			$option = 'nolink';
1797
		}
1798
1799
		if ($option !== 'nolink') {
1800
			// Add param to save lastsearch_values or not
1801
			$add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1802
			if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1803
				$add_save_lastsearch_values = 1;
1804
			}
1805
			if ($add_save_lastsearch_values) {
1806
				$url .= '&save_lastsearch_values=1';
1807
			}
1808
		}
1809
1810
		if ($short) {
1811
			return $url;
1812
		}
1813
1814
		$picto = $this->picto;
1815
		if ($this->type == self::TYPE_REPLACEMENT) {
1816
			$picto .= 'r'; // Replacement invoice
1817
		}
1818
		if ($this->type == self::TYPE_CREDIT_NOTE) {
1819
			$picto .= 'a'; // Credit note
1820
		}
1821
		if ($this->type == self::TYPE_DEPOSIT) {
1822
			$picto .= 'd'; // Deposit invoice
1823
		}
1824
		$label = '';
1825
1826
		if ($user->hasRight("facture", "read")) {
1827
			$label = img_picto('', $picto).' <u class="paddingrightonly">'.$langs->trans("Invoice").'</u>';
1828
			if (isset($this->statut) && isset($this->alreadypaid)) {
1829
				$label .= ' '.$this->getLibStatut(5, $this->alreadypaid);
1830
			}
1831
			$label .= ' &nbsp; '.$this->getLibType(1);
1832
			if (!empty($this->ref)) {
1833
				$label .= '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1834
			}
1835
			if (!empty($this->ref_client)) {
1836
				$label .= '<br><b>'.$langs->trans('RefCustomer').':</b> '.$this->ref_client;
1837
			}
1838
			if (!empty($this->date)) {
1839
				$label .= '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
1840
			}
1841
			if (!empty($this->total_ht)) {
1842
				$label .= '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
1843
			}
1844
			if (!empty($this->total_tva)) {
1845
				$label .= '<br><b>'.$langs->trans('AmountVAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
1846
			}
1847
			if (!empty($this->total_localtax1) && $this->total_localtax1 != 0) {		// We keep test != 0 because $this->total_localtax1 can be '0.00000000'
1848
				$label .= '<br><b>'.$langs->transcountry('AmountLT1', $mysoc->country_code).':</b> '.price($this->total_localtax1, 0, $langs, 0, -1, -1, $conf->currency);
1849
			}
1850
			if (!empty($this->total_localtax2) && $this->total_localtax2 != 0) {
1851
				$label .= '<br><b>'.$langs->transcountry('AmountLT2', $mysoc->country_code).':</b> '.price($this->total_localtax2, 0, $langs, 0, -1, -1, $conf->currency);
1852
			}
1853
			if (!empty($this->total_ttc)) {
1854
				$label .= '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
1855
			}
1856
			if ($moretitle) {
1857
				$label .= ' - '.$moretitle;
1858
			}
1859
		}
1860
1861
		$linkclose = ($target ? ' target="'.$target.'"' : '');
1862
		if (empty($notooltip) && $user->hasRight("facture", "read")) {
1863
			if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
1864
				$label = $langs->trans("Invoice");
1865
				$linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1866
			}
1867
			$linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
1868
			$linkclose .= ' class="classfortooltip"';
1869
		}
1870
1871
		$linkstart = '<a href="'.$url.'"';
1872
		$linkstart .= $linkclose.'>';
1873
		$linkend = '</a>';
1874
1875
		if ($option == 'nolink') {
1876
			$linkstart = '';
1877
			$linkend = '';
1878
		}
1879
1880
		$result .= $linkstart;
1881
		if ($withpicto) {
1882
			$result .= img_object(($notooltip ? '' : $label), $picto, ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
1883
		}
1884
		if ($withpicto != 2) {
1885
			$result .= ($max ?dol_trunc($this->ref, $max) : $this->ref);
1886
		}
1887
		$result .= $linkend;
1888
1889
		if ($addlinktonotes) {
1890
			$txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
1891
			if ($txttoshow) {
1892
				//$notetoshow = $langs->trans("ViewPrivateNote").':<br>'.dol_string_nohtmltag($txttoshow, 1);
1893
				$notetoshow = $langs->trans("ViewPrivateNote").':<br>'.$txttoshow;
1894
				$result .= ' <span class="note inline-block">';
1895
				$result .= '<a href="'.DOL_URL_ROOT.'/compta/facture/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($notetoshow, 1, 1).'">';
1896
				$result .= img_picto('', 'note');
1897
				$result .= '</a>';
1898
				//$result.=img_picto($langs->trans("ViewNote"),'object_generic');
1899
				//$result.='</a>';
1900
				$result .= '</span>';
1901
			}
1902
		}
1903
1904
		global $action, $hookmanager;
1905
		$hookmanager->initHooks(array('invoicedao'));
1906
		$parameters = array('id'=>$this->id, 'getnomurl' => &$result, 'notooltip' => $notooltip, 'addlinktonotes' => $addlinktonotes, 'save_lastsearch_value'=> $save_lastsearch_value, 'target' => $target);
1907
		$reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1908
		if ($reshook > 0) {
1909
			$result = $hookmanager->resPrint;
1910
		} else {
1911
			$result .= $hookmanager->resPrint;
1912
		}
1913
1914
		return $result;
1915
	}
1916
1917
	/**
1918
	 *	Get object from database. Get also lines.
1919
	 *
1920
	 *	@param      int		$rowid       		Id of object to load
1921
	 * 	@param		string	$ref				Reference of invoice
1922
	 * 	@param		string	$ref_ext			External reference of invoice
1923
	 * 	@param		int		$notused			Not used
1924
	 *  @param		bool	$fetch_situation	Load also the previous and next situation invoice into $tab_previous_situation_invoice and $tab_next_situation_invoice
1925
	 *	@return     int         				>0 if OK, <0 if KO, 0 if not found
1926
	 */
1927
	public function fetch($rowid, $ref = '', $ref_ext = '', $notused = '', $fetch_situation = false)
1928
	{
1929
		if (empty($rowid) && empty($ref) && empty($ref_ext)) {
1930
			return -1;
1931
		}
1932
1933
		$sql = 'SELECT f.rowid, f.entity, f.ref, f.ref_client, f.ref_ext, f.type, f.fk_soc';
1934
		$sql .= ', f.total_tva, f.localtax1, f.localtax2, f.total_ht, f.total_ttc, f.revenuestamp';
1935
		$sql .= ', f.remise_percent, f.remise_absolue, f.remise';
1936
		$sql .= ', f.datef as df, f.date_pointoftax';
1937
		$sql .= ', f.date_lim_reglement as dlr';
1938
		$sql .= ', f.datec as datec';
1939
		$sql .= ', f.date_valid as datev';
1940
		$sql .= ', f.tms as datem';
1941
		$sql .= ', f.note_private, f.note_public, f.fk_statut, f.paye, f.close_code, f.close_note, f.fk_user_author, f.fk_user_valid, f.fk_user_modif, f.model_pdf, f.last_main_doc';
1942
		$sql .= ', f.fk_facture_source, f.fk_fac_rec_source';
1943
		$sql .= ', f.fk_mode_reglement, f.fk_cond_reglement, f.fk_projet as fk_project, f.extraparams';
1944
		$sql .= ', f.situation_cycle_ref, f.situation_counter, f.situation_final';
1945
		$sql .= ', f.fk_account';
1946
		$sql .= ", f.fk_multicurrency, f.multicurrency_code, f.multicurrency_tx, f.multicurrency_total_ht, f.multicurrency_total_tva, f.multicurrency_total_ttc";
1947
		$sql .= ', p.code as mode_reglement_code, p.libelle as mode_reglement_libelle';
1948
		$sql .= ', c.code as cond_reglement_code, c.libelle as cond_reglement_libelle, c.libelle_facture as cond_reglement_libelle_doc';
1949
		$sql .= ', f.fk_incoterms, f.location_incoterms';
1950
		$sql .= ', f.module_source, f.pos_source';
1951
		$sql .= ", i.libelle as label_incoterms";
1952
		$sql .= ", f.retained_warranty as retained_warranty, f.retained_warranty_date_limit as retained_warranty_date_limit, f.retained_warranty_fk_cond_reglement as retained_warranty_fk_cond_reglement";
1953
		$sql .= ' FROM '.MAIN_DB_PREFIX.'facture as f';
1954
		$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_payment_term as c ON f.fk_cond_reglement = c.rowid';
1955
		$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as p ON f.fk_mode_reglement = p.id';
1956
		$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON f.fk_incoterms = i.rowid';
1957
1958
		if ($rowid) {
1959
			$sql .= " WHERE f.rowid = ".((int) $rowid);
1960
		} else {
1961
			$sql .= ' WHERE f.entity IN ('.getEntity('invoice').')'; // Don't use entity if you use rowid
1962
			if ($ref) {
1963
				$sql .= " AND f.ref = '".$this->db->escape($ref)."'";
1964
			}
1965
			if ($ref_ext) {
1966
				$sql .= " AND f.ref_ext = '".$this->db->escape($ref_ext)."'";
1967
			}
1968
		}
1969
1970
		dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
1971
		$resql = $this->db->query($sql);
1972
		if ($resql) {
1973
			if ($this->db->num_rows($resql)) {
1974
				$obj = $this->db->fetch_object($resql);
1975
1976
				$this->id = $obj->rowid;
1977
				$this->entity = $obj->entity;
1978
1979
				$this->ref					= $obj->ref;
1980
				$this->ref_client			= $obj->ref_client;
1981
				$this->ref_customer			= $obj->ref_client;
1982
				$this->ref_ext				= $obj->ref_ext;
1983
				$this->type					= $obj->type;
1984
				$this->date					= $this->db->jdate($obj->df);
1985
				$this->date_pointoftax		= $this->db->jdate($obj->date_pointoftax);
1986
				$this->date_creation = $this->db->jdate($obj->datec);
1987
				$this->date_validation		= $this->db->jdate($obj->datev);
1988
				$this->date_modification = $this->db->jdate($obj->datem);
1989
				$this->datem = $this->db->jdate($obj->datem);
1990
				$this->remise_percent		= $obj->remise_percent;
1991
				$this->remise_absolue		= $obj->remise_absolue;
1992
				$this->total_ht				= $obj->total_ht;
1993
				$this->total_tva			= $obj->total_tva;
1994
				$this->total_localtax1		= $obj->localtax1;
1995
				$this->total_localtax2		= $obj->localtax2;
1996
				$this->total_ttc			= $obj->total_ttc;
1997
				$this->revenuestamp = $obj->revenuestamp;
1998
				$this->paye = $obj->paye;
1999
				$this->close_code			= $obj->close_code;
2000
				$this->close_note			= $obj->close_note;
2001
2002
				$this->socid = $obj->fk_soc;
2003
				$this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
2004
2005
				$this->fk_project = $obj->fk_project;
2006
				$this->project = null; // Clear if another value was already set by fetch_projet
2007
2008
				$this->statut = $obj->fk_statut;
2009
				$this->status = $obj->fk_statut;
2010
2011
				$this->date_lim_reglement = $this->db->jdate($obj->dlr);
2012
				$this->mode_reglement_id	= $obj->fk_mode_reglement;
2013
				$this->mode_reglement_code	= $obj->mode_reglement_code;
2014
				$this->mode_reglement		= $obj->mode_reglement_libelle;
2015
				$this->cond_reglement_id	= $obj->fk_cond_reglement;
2016
				$this->cond_reglement_code	= $obj->cond_reglement_code;
2017
				$this->cond_reglement		= $obj->cond_reglement_libelle;
2018
				$this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
2019
				$this->fk_account = ($obj->fk_account > 0) ? $obj->fk_account : null;
2020
				$this->fk_facture_source	= $obj->fk_facture_source;
2021
				$this->fk_fac_rec_source	= $obj->fk_fac_rec_source;
2022
				$this->note = $obj->note_private; // deprecated
2023
				$this->note_private = $obj->note_private;
2024
				$this->note_public			= $obj->note_public;
2025
				$this->user_author			= $obj->fk_user_author; // deprecated
2026
				$this->user_valid           = $obj->fk_user_valid; // deprecated
2027
				$this->user_modification    = $obj->fk_user_modif; // deprecated
2028
				$this->fk_user_author       = $obj->fk_user_author;
2029
				$this->fk_user_valid        = $obj->fk_user_valid;
2030
				$this->fk_user_modif        = $obj->fk_user_modif;
2031
				$this->model_pdf = $obj->model_pdf;
2032
				$this->modelpdf = $obj->model_pdf; // deprecated
2033
				$this->last_main_doc = $obj->last_main_doc;
2034
				$this->situation_cycle_ref  = $obj->situation_cycle_ref;
2035
				$this->situation_counter    = $obj->situation_counter;
2036
				$this->situation_final      = $obj->situation_final;
2037
				$this->retained_warranty    = $obj->retained_warranty;
2038
				$this->retained_warranty_date_limit         = $this->db->jdate($obj->retained_warranty_date_limit);
2039
				$this->retained_warranty_fk_cond_reglement  = $obj->retained_warranty_fk_cond_reglement;
2040
2041
				$this->extraparams = (array) json_decode($obj->extraparams, true);
2042
2043
				//Incoterms
2044
				$this->fk_incoterms         = $obj->fk_incoterms;
2045
				$this->location_incoterms   = $obj->location_incoterms;
2046
				$this->label_incoterms = $obj->label_incoterms;
2047
2048
				$this->module_source = $obj->module_source;
2049
				$this->pos_source = $obj->pos_source;
2050
2051
				// Multicurrency
2052
				$this->fk_multicurrency 		= $obj->fk_multicurrency;
2053
				$this->multicurrency_code = $obj->multicurrency_code;
2054
				$this->multicurrency_tx 		= $obj->multicurrency_tx;
2055
				$this->multicurrency_total_ht = $obj->multicurrency_total_ht;
2056
				$this->multicurrency_total_tva 	= $obj->multicurrency_total_tva;
2057
				$this->multicurrency_total_ttc 	= $obj->multicurrency_total_ttc;
2058
2059
				if (($this->type == self::TYPE_SITUATION || ($this->type == self::TYPE_CREDIT_NOTE && $this->situation_cycle_ref > 0)) && $fetch_situation) {
2060
					$this->fetchPreviousNextSituationInvoice();
2061
				}
2062
2063
				if ($this->status == self::STATUS_DRAFT) {
2064
					$this->brouillon = 1;
2065
				}
2066
2067
				// Retrieve all extrafield
2068
				// fetch optionals attributes and labels
2069
				$this->fetch_optionals();
2070
2071
				// Lines
2072
				$this->lines = array();
2073
2074
				$result = $this->fetch_lines();
2075
				if ($result < 0) {
2076
					$this->error = $this->db->error();
2077
					return -3;
2078
				}
2079
2080
				$this->db->free($resql);
2081
2082
				return 1;
2083
			} else {
2084
				$this->error = 'Invoice with id='.$rowid.' or ref='.$ref.' or ref_ext='.$ref_ext.' not found';
2085
2086
				dol_syslog(__METHOD__.$this->error, LOG_WARNING);
2087
				return 0;
2088
			}
2089
		} else {
2090
			$this->error = $this->db->lasterror();
2091
			return -1;
2092
		}
2093
	}
2094
2095
2096
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2097
	/**
2098
	 *	Load all detailed lines into this->lines
2099
	 *
2100
	 *	@param		int		$only_product	Return only physical products
2101
	 *	@param		int		$loadalsotranslation	Return translation for products
2102
	 *
2103
	 *	@return     int         1 if OK, < 0 if KO
2104
	 */
2105
	public function fetch_lines($only_product = 0, $loadalsotranslation = 0)
2106
	{
2107
		// phpcs:enable
2108
		global $langs, $conf;
2109
2110
		$this->lines = array();
2111
2112
		$sql = 'SELECT l.rowid, l.fk_facture, l.fk_product, l.fk_parent_line, l.label as custom_label, l.description, l.product_type, l.price, l.qty, l.vat_src_code, l.tva_tx,';
2113
		$sql .= ' l.localtax1_tx, l.localtax2_tx, l.localtax1_type, l.localtax2_type, l.remise_percent, l.fk_remise_except, l.subprice, l.ref_ext,';
2114
		$sql .= ' l.situation_percent, l.fk_prev_id,';
2115
		$sql .= ' l.rang, l.special_code,';
2116
		$sql .= ' l.date_start as date_start, l.date_end as date_end,';
2117
		$sql .= ' l.info_bits, l.total_ht, l.total_tva, l.total_localtax1, l.total_localtax2, l.total_ttc, l.fk_code_ventilation, l.fk_product_fournisseur_price as fk_fournprice, l.buy_price_ht as pa_ht,';
2118
		$sql .= ' l.fk_unit,';
2119
		$sql .= ' l.fk_multicurrency, l.multicurrency_code, l.multicurrency_subprice, l.multicurrency_total_ht, l.multicurrency_total_tva, l.multicurrency_total_ttc,';
2120
		$sql .= ' p.ref as product_ref, p.fk_product_type as fk_product_type, p.label as product_label, p.description as product_desc';
2121
		$sql .= ' FROM '.MAIN_DB_PREFIX.'facturedet as l';
2122
		$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON l.fk_product = p.rowid';
2123
		$sql .= ' WHERE l.fk_facture = '.((int) $this->id);
2124
		$sql .= ' ORDER BY l.rang, l.rowid';
2125
2126
		dol_syslog(get_class($this).'::fetch_lines', LOG_DEBUG);
2127
		$result = $this->db->query($sql);
2128
		if ($result) {
2129
			$num = $this->db->num_rows($result);
2130
			$i = 0;
2131
			while ($i < $num) {
2132
				$objp = $this->db->fetch_object($result);
2133
				$line = new FactureLigne($this->db);
2134
2135
				$line->id               = $objp->rowid;
2136
				$line->rowid = $objp->rowid; // deprecated
2137
				$line->fk_facture       = $objp->fk_facture;
2138
				$line->label            = $objp->custom_label; // deprecated
2139
				$line->desc             = $objp->description; // Description line
2140
				$line->description      = $objp->description; // Description line
2141
				$line->product_type     = $objp->product_type; // Type of line
2142
				$line->ref              = $objp->product_ref; // Ref product
2143
				$line->product_ref      = $objp->product_ref; // Ref product
2144
				$line->libelle          = $objp->product_label; // deprecated
2145
				$line->product_label = $objp->product_label; // Label product
2146
				$line->product_desc     = $objp->product_desc; // Description product
2147
				$line->fk_product_type  = $objp->fk_product_type; // Type of product
2148
				$line->qty              = $objp->qty;
2149
				$line->subprice         = $objp->subprice;
2150
				$line->ref_ext          = $objp->ref_ext; // line external ref
2151
2152
				$line->vat_src_code = $objp->vat_src_code;
2153
				$line->tva_tx           = $objp->tva_tx;
2154
				$line->localtax1_tx     = $objp->localtax1_tx;
2155
				$line->localtax2_tx     = $objp->localtax2_tx;
2156
				$line->localtax1_type   = $objp->localtax1_type;
2157
				$line->localtax2_type   = $objp->localtax2_type;
2158
				$line->remise_percent   = $objp->remise_percent;
2159
				$line->fk_remise_except = $objp->fk_remise_except;
2160
				$line->fk_product       = $objp->fk_product;
2161
				$line->date_start       = $this->db->jdate($objp->date_start);
2162
				$line->date_end         = $this->db->jdate($objp->date_end);
2163
				$line->date_start       = $this->db->jdate($objp->date_start);
2164
				$line->date_end         = $this->db->jdate($objp->date_end);
2165
				$line->info_bits        = $objp->info_bits;
2166
				$line->total_ht         = $objp->total_ht;
2167
				$line->total_tva        = $objp->total_tva;
2168
				$line->total_localtax1  = $objp->total_localtax1;
2169
				$line->total_localtax2  = $objp->total_localtax2;
2170
				$line->total_ttc        = $objp->total_ttc;
2171
				$line->code_ventilation = $objp->fk_code_ventilation;
0 ignored issues
show
Bug introduced by
The property code_ventilation does not exist on FactureLigne. Did you mean fk_code_ventilation?
Loading history...
2172
				$line->fk_fournprice = $objp->fk_fournprice;
2173
				$marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
2174
				$line->pa_ht = $marginInfos[0];
2175
				$line->marge_tx			= $marginInfos[1];
2176
				$line->marque_tx		= $marginInfos[2];
2177
				$line->rang = $objp->rang;
2178
				$line->special_code = $objp->special_code;
2179
				$line->fk_parent_line = $objp->fk_parent_line;
2180
				$line->situation_percent = $objp->situation_percent;
2181
				$line->fk_prev_id = $objp->fk_prev_id;
2182
				$line->fk_unit = $objp->fk_unit;
2183
2184
				// Accountancy
2185
				$line->fk_accounting_account = $objp->fk_code_ventilation;
2186
2187
				// Multicurrency
2188
				$line->fk_multicurrency = $objp->fk_multicurrency;
2189
				$line->multicurrency_code = $objp->multicurrency_code;
2190
				$line->multicurrency_subprice 	= $objp->multicurrency_subprice;
2191
				$line->multicurrency_total_ht 	= $objp->multicurrency_total_ht;
2192
				$line->multicurrency_total_tva 	= $objp->multicurrency_total_tva;
2193
				$line->multicurrency_total_ttc 	= $objp->multicurrency_total_ttc;
2194
2195
				$line->fetch_optionals();
2196
2197
				// multilangs
2198
				if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
2199
					$tmpproduct = new Product($this->db);
2200
					$tmpproduct->fetch($objp->fk_product);
2201
					$tmpproduct->getMultiLangs();
2202
2203
					$line->multilangs = $tmpproduct->multilangs;
2204
				}
2205
2206
				$this->lines[$i] = $line;
2207
2208
				$i++;
2209
			}
2210
			$this->db->free($result);
2211
			return 1;
2212
		} else {
2213
			$this->error = $this->db->error();
2214
			return -3;
2215
		}
2216
	}
2217
2218
	/**
2219
	 * Fetch previous and next situations invoices.
2220
	 * Return all previous and next invoices (both standard and credit notes).
2221
	 *
2222
	 * @return	void
2223
	 */
2224
	public function fetchPreviousNextSituationInvoice()
2225
	{
2226
		global $conf;
2227
2228
		$this->tab_previous_situation_invoice = array();
2229
		$this->tab_next_situation_invoice = array();
2230
2231
		$sql = 'SELECT rowid, type, situation_cycle_ref, situation_counter FROM '.MAIN_DB_PREFIX.'facture';
2232
		$sql .= " WHERE rowid <> ".((int) $this->id);
2233
		$sql .= ' AND entity = '.((int) $this->entity);
2234
		$sql .= ' AND situation_cycle_ref = '.(int) $this->situation_cycle_ref;
2235
		$sql .= ' ORDER BY situation_counter ASC';
2236
2237
		dol_syslog(get_class($this).'::fetchPreviousNextSituationInvoice ', LOG_DEBUG);
2238
		$result = $this->db->query($sql);
2239
		if ($result && $this->db->num_rows($result) > 0) {
2240
			while ($objp = $this->db->fetch_object($result)) {
2241
				$invoice = new Facture($this->db);
2242
				if ($invoice->fetch($objp->rowid) > 0) {
2243
					if ($objp->situation_counter < $this->situation_counter
2244
						|| ($objp->situation_counter == $this->situation_counter && $objp->rowid < $this->id) // This case appear when there are credit notes
2245
					   ) {
2246
						$this->tab_previous_situation_invoice[] = $invoice;
2247
					} else {
2248
						$this->tab_next_situation_invoice[] = $invoice;
2249
					}
2250
				}
2251
			}
2252
		}
2253
	}
2254
2255
	/**
2256
	 *      Update database
2257
	 *
2258
	 *      @param      User	$user        	User that modify
2259
	 *      @param      int		$notrigger	    0=launch triggers after, 1=disable triggers
2260
	 *      @return     int      			   	<0 if KO, >0 if OK
2261
	 */
2262
	public function update(User $user, $notrigger = 0)
2263
	{
2264
		global $conf;
2265
2266
		$error = 0;
2267
2268
		// Clean parameters
2269
		if (empty($this->type)) {
2270
			$this->type = self::TYPE_STANDARD;
2271
		}
2272
		if (isset($this->ref)) {
2273
			$this->ref = trim($this->ref);
2274
		}
2275
		if (isset($this->ref_ext)) {
2276
			$this->ref_ext = trim($this->ref_ext);
2277
		}
2278
		if (isset($this->ref_client)) {
2279
			$this->ref_client = trim($this->ref_client);
2280
		}
2281
		if (isset($this->increment)) {
2282
			$this->increment = trim($this->increment);
2283
		}
2284
		if (isset($this->close_code)) {
2285
			$this->close_code = trim($this->close_code);
2286
		}
2287
		if (isset($this->close_note)) {
2288
			$this->close_note = trim($this->close_note);
2289
		}
2290
		if (isset($this->note) || isset($this->note_private)) {
2291
			$this->note = (isset($this->note) ? trim($this->note) : trim($this->note_private)); // deprecated
2292
		}
2293
		if (isset($this->note) || isset($this->note_private)) {
2294
			$this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
2295
		}
2296
		if (isset($this->note_public)) {
2297
			$this->note_public = trim($this->note_public);
2298
		}
2299
		if (isset($this->model_pdf)) {
2300
			$this->model_pdf = trim($this->model_pdf);
2301
		}
2302
		if (isset($this->import_key)) {
2303
			$this->import_key = trim($this->import_key);
2304
		}
2305
		if (isset($this->retained_warranty)) {
2306
			$this->retained_warranty = floatval($this->retained_warranty);
2307
		}
2308
2309
2310
		// Check parameters
2311
		// Put here code to add control on parameters values
2312
2313
		// Update request
2314
		$sql = "UPDATE ".MAIN_DB_PREFIX."facture SET";
2315
		$sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
2316
		$sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
2317
		$sql .= " type=".(isset($this->type) ? $this->db->escape($this->type) : "null").",";
2318
		$sql .= " ref_client=".(isset($this->ref_client) ? "'".$this->db->escape($this->ref_client)."'" : "null").",";
2319
		$sql .= " increment=".(isset($this->increment) ? "'".$this->db->escape($this->increment)."'" : "null").",";
2320
		$sql .= " fk_soc=".(isset($this->socid) ? $this->db->escape($this->socid) : "null").",";
2321
		$sql .= " datec=".(strval($this->date_creation) != '' ? "'".$this->db->idate($this->date_creation)."'" : 'null').",";
2322
		$sql .= " datef=".(strval($this->date) != '' ? "'".$this->db->idate($this->date)."'" : 'null').",";
2323
		$sql .= " date_pointoftax=".(strval($this->date_pointoftax) != '' ? "'".$this->db->idate($this->date_pointoftax)."'" : 'null').",";
2324
		$sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
2325
		$sql .= " paye=".(isset($this->paye) ? $this->db->escape($this->paye) : 0).",";
2326
		$sql .= " remise_percent=".(isset($this->remise_percent) ? $this->db->escape($this->remise_percent) : "null").",";
2327
		$sql .= " remise_absolue=".(isset($this->remise_absolue) ? $this->db->escape($this->remise_absolue) : "null").",";
2328
		$sql .= " close_code=".(isset($this->close_code) ? "'".$this->db->escape($this->close_code)."'" : "null").",";
2329
		$sql .= " close_note=".(isset($this->close_note) ? "'".$this->db->escape($this->close_note)."'" : "null").",";
2330
		$sql .= " total_tva=".(isset($this->total_tva) ? $this->total_tva : "null").",";
2331
		$sql .= " localtax1=".(isset($this->total_localtax1) ? $this->total_localtax1 : "null").",";
2332
		$sql .= " localtax2=".(isset($this->total_localtax2) ? $this->total_localtax2 : "null").",";
2333
		$sql .= " total_ht=".(isset($this->total_ht) ? $this->total_ht : "null").",";
2334
		$sql .= " total_ttc=".(isset($this->total_ttc) ? $this->total_ttc : "null").",";
2335
		$sql .= " revenuestamp=".((isset($this->revenuestamp) && $this->revenuestamp != '') ? $this->db->escape($this->revenuestamp) : "null").",";
2336
		$sql .= " fk_statut=".(isset($this->statut) ? $this->db->escape($this->statut) : "null").",";
2337
		$sql .= " fk_user_author=".(isset($this->user_author) ? $this->db->escape($this->user_author) : "null").",";
2338
		$sql .= " fk_user_valid=".(isset($this->fk_user_valid) ? $this->db->escape($this->fk_user_valid) : "null").",";
2339
		$sql .= " fk_facture_source=".(isset($this->fk_facture_source) ? $this->db->escape($this->fk_facture_source) : "null").",";
2340
		$sql .= " fk_projet=".(isset($this->fk_project) ? $this->db->escape($this->fk_project) : "null").",";
2341
		$sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? $this->db->escape($this->cond_reglement_id) : "null").",";
2342
		$sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? $this->db->escape($this->mode_reglement_id) : "null").",";
2343
		$sql .= " date_lim_reglement=".(strval($this->date_lim_reglement) != '' ? "'".$this->db->idate($this->date_lim_reglement)."'" : 'null').",";
2344
		$sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
2345
		$sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
2346
		$sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
2347
		$sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null").",";
2348
		$sql .= " situation_cycle_ref=".(empty($this->situation_cycle_ref) ? "null" : $this->db->escape($this->situation_cycle_ref)).",";
2349
		$sql .= " situation_counter=".(empty($this->situation_counter) ? "null" : $this->db->escape($this->situation_counter)).",";
2350
		$sql .= " situation_final=".(empty($this->situation_final) ? "0" : $this->db->escape($this->situation_final)).",";
2351
		$sql .= " retained_warranty=".(empty($this->retained_warranty) ? "0" : $this->db->escape($this->retained_warranty)).",";
2352
		$sql .= " retained_warranty_date_limit=".(strval($this->retained_warranty_date_limit) != '' ? "'".$this->db->idate($this->retained_warranty_date_limit)."'" : 'null').",";
2353
		$sql .= " retained_warranty_fk_cond_reglement=".(isset($this->retained_warranty_fk_cond_reglement) ?intval($this->retained_warranty_fk_cond_reglement) : "null");
2354
		$sql .= " WHERE rowid=".((int) $this->id);
2355
2356
		$this->db->begin();
2357
2358
		dol_syslog(get_class($this)."::update", LOG_DEBUG);
2359
		$resql = $this->db->query($sql);
2360
		if (!$resql) {
2361
			$error++;
2362
			$this->errors[] = "Error ".$this->db->lasterror();
2363
		}
2364
2365
		if (!$error) {
2366
			$result = $this->insertExtraFields();
2367
			if ($result < 0) {
2368
				$error++;
2369
			}
2370
		}
2371
2372
		if (!$error && !$notrigger) {
2373
			// Call trigger
2374
			$result = $this->call_trigger('BILL_MODIFY', $user);
2375
			if ($result < 0) {
2376
				$error++;
2377
			}
2378
			// End call triggers
2379
		}
2380
2381
		// Commit or rollback
2382
		if ($error) {
2383
			foreach ($this->errors as $errmsg) {
2384
				dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
2385
				$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2386
			}
2387
			$this->db->rollback();
2388
			return -1 * $error;
2389
		} else {
2390
			$this->db->commit();
2391
			return 1;
2392
		}
2393
	}
2394
2395
2396
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2397
	/**
2398
	 *    Add a discount line into an invoice (as an invoice line) using an existing absolute discount (Consume the discount)
2399
	 *
2400
	 *    @param     int	$idremise	Id of absolute discount
2401
	 *    @return    int          		>0 if OK, <0 if KO
2402
	 */
2403
	public function insert_discount($idremise)
2404
	{
2405
		// phpcs:enable
2406
		global $conf, $langs;
2407
2408
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2409
		include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
2410
2411
		$this->db->begin();
2412
2413
		$remise = new DiscountAbsolute($this->db);
2414
		$result = $remise->fetch($idremise);
2415
2416
		if ($result > 0) {
2417
			if ($remise->fk_facture) {	// Protection against multiple submission
2418
				$this->error = $langs->trans("ErrorDiscountAlreadyUsed");
2419
				$this->db->rollback();
2420
				return -5;
2421
			}
2422
2423
			$facligne = new FactureLigne($this->db);
2424
			$facligne->fk_facture = $this->id;
2425
			$facligne->fk_remise_except = $remise->id;
2426
			$facligne->desc = $remise->description; // Description ligne
2427
			$facligne->vat_src_code = $remise->vat_src_code;
2428
			$facligne->tva_tx = $remise->tva_tx;
2429
			$facligne->subprice = -$remise->amount_ht;
2430
			$facligne->fk_product = 0; // Id produit predefini
2431
			$facligne->qty = 1;
2432
			$facligne->remise_percent = 0;
2433
			$facligne->rang = -1;
2434
			$facligne->info_bits = 2;
2435
2436
			if (!empty($conf->global->MAIN_ADD_LINE_AT_POSITION)) {
2437
				$facligne->rang = 1;
2438
				$linecount = count($this->lines);
2439
				for ($ii = 1; $ii <= $linecount; $ii++) {
2440
					$this->updateRangOfLine($this->lines[$ii - 1]->id, $ii+1);
2441
				}
2442
			}
2443
2444
			// Get buy/cost price of invoice that is source of discount
2445
			if ($remise->fk_facture_source > 0) {
2446
				$srcinvoice = new Facture($this->db);
2447
				$srcinvoice->fetch($remise->fk_facture_source);
2448
				include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmargin.class.php'; // TODO Move this into commonobject
2449
				$formmargin = new FormMargin($this->db);
2450
				$arraytmp = $formmargin->getMarginInfosArray($srcinvoice, false);
2451
				$facligne->pa_ht = $arraytmp['pa_total'];
2452
			}
2453
2454
			$facligne->total_ht  = -$remise->amount_ht;
2455
			$facligne->total_tva = -$remise->amount_tva;
2456
			$facligne->total_ttc = -$remise->amount_ttc;
2457
2458
			$facligne->multicurrency_subprice = -$remise->multicurrency_subprice;
2459
			$facligne->multicurrency_total_ht = -$remise->multicurrency_amount_ht;
2460
			$facligne->multicurrency_total_tva = -$remise->multicurrency_amount_tva;
2461
			$facligne->multicurrency_total_ttc = -$remise->multicurrency_amount_ttc;
2462
2463
			$lineid = $facligne->insert();
2464
			if ($lineid > 0) {
2465
				$result = $this->update_price(1);
2466
				if ($result > 0) {
2467
					// Create link between discount and invoice line
2468
					$result = $remise->link_to_invoice($lineid, 0);
2469
					if ($result < 0) {
2470
						$this->error = $remise->error;
2471
						$this->db->rollback();
2472
						return -4;
2473
					}
2474
2475
					$this->db->commit();
2476
					return 1;
2477
				} else {
2478
					$this->error = $facligne->error;
2479
					$this->db->rollback();
2480
					return -1;
2481
				}
2482
			} else {
2483
				$this->error = $facligne->error;
2484
				$this->db->rollback();
2485
				return -2;
2486
			}
2487
		} else {
2488
			$this->db->rollback();
2489
			return -3;
2490
		}
2491
	}
2492
2493
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2494
	/**
2495
	 *	Set customer ref
2496
	 *
2497
	 *	@param     	string	$ref_client		Customer ref
2498
	 *  @param     	int		$notrigger		1=Does not execute triggers, 0= execute triggers
2499
	 *	@return		int						<0 if KO, >0 if OK
2500
	 */
2501
	public function set_ref_client($ref_client, $notrigger = 0)
2502
	{
2503
		// phpcs:enable
2504
		global $user;
2505
2506
		$error = 0;
2507
2508
		$this->db->begin();
2509
2510
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
2511
		if (empty($ref_client)) {
2512
			$sql .= ' SET ref_client = NULL';
2513
		} else {
2514
			$sql .= ' SET ref_client = \''.$this->db->escape($ref_client).'\'';
2515
		}
2516
		$sql .= " WHERE rowid = ".((int) $this->id);
2517
2518
		dol_syslog(__METHOD__.' this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
2519
		$resql = $this->db->query($sql);
2520
		if (!$resql) {
2521
			$this->errors[] = $this->db->error();
2522
			$error++;
2523
		}
2524
2525
		if (!$error) {
2526
			$this->ref_client = $ref_client;
2527
		}
2528
2529
		if (!$notrigger && empty($error)) {
2530
			// Call trigger
2531
			$result = $this->call_trigger('BILL_MODIFY', $user);
2532
			if ($result < 0) {
2533
				$error++;
2534
			}
2535
			// End call triggers
2536
		}
2537
2538
		if (!$error) {
2539
			$this->ref_client = $ref_client;
2540
2541
			$this->db->commit();
2542
			return 1;
2543
		} else {
2544
			foreach ($this->errors as $errmsg) {
2545
				dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2546
				$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2547
			}
2548
			$this->db->rollback();
2549
			return -1 * $error;
2550
		}
2551
	}
2552
2553
	/**
2554
	 *	Delete invoice
2555
	 *
2556
	 *	@param     	User	$user      	    User making the deletion.
2557
	 *	@param		int		$notrigger		1=Does not execute triggers, 0= execute triggers
2558
	 *	@param		int		$idwarehouse	Id warehouse to use for stock change.
2559
	 *	@return		int						<0 if KO, 0=Refused, >0 if OK
2560
	 */
2561
	public function delete($user, $notrigger = 0, $idwarehouse = -1)
2562
	{
2563
		global $langs, $conf;
2564
		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2565
2566
		$rowid = $this->id;
2567
2568
		dol_syslog(get_class($this)."::delete rowid=".$rowid.", ref=".$this->ref.", thirdparty=".(empty($this->thirdparty) ? '' : $this->thirdparty->name), LOG_DEBUG);
2569
2570
		// Test to avoid invoice deletion (allowed if draft)
2571
		$result = $this->is_erasable();
2572
2573
		if ($result <= 0) {
2574
			return 0;
2575
		}
2576
2577
		$error = 0;
2578
2579
		$this->db->begin();
2580
2581
		if (!$error && !$notrigger) {
2582
			// Call trigger
2583
			$result = $this->call_trigger('BILL_DELETE', $user);
2584
			if ($result < 0) {
2585
				$error++;
2586
			}
2587
			// End call triggers
2588
		}
2589
2590
		// Removed extrafields
2591
		if (!$error) {
2592
			$result = $this->deleteExtraFields();
2593
			if ($result < 0) {
2594
				$error++;
2595
				dol_syslog(get_class($this)."::delete error deleteExtraFields ".$this->error, LOG_ERR);
2596
			}
2597
		}
2598
2599
		if (!$error) {
2600
			// Delete linked object
2601
			$res = $this->deleteObjectLinked();
2602
			if ($res < 0) {
2603
				$error++;
2604
			}
2605
		}
2606
2607
		if (!$error) {
2608
			// If invoice was converted into a discount not yet consumed, we remove discount
2609
			$sql = 'DELETE FROM '.MAIN_DB_PREFIX.'societe_remise_except';
2610
			$sql .= ' WHERE fk_facture_source = '.((int) $rowid);
2611
			$sql .= ' AND fk_facture_line IS NULL';
2612
			$resql = $this->db->query($sql);
2613
2614
			// If invoice has consumed discounts
2615
			$this->fetch_lines();
2616
			$list_rowid_det = array();
2617
			foreach ($this->lines as $key => $invoiceline) {
2618
				$list_rowid_det[] = $invoiceline->id;
2619
			}
2620
2621
			// Consumed discounts are freed
2622
			if (count($list_rowid_det)) {
2623
				$sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
2624
				$sql .= ' SET fk_facture = NULL, fk_facture_line = NULL';
2625
				$sql .= ' WHERE fk_facture_line IN ('.$this->db->sanitize(join(',', $list_rowid_det)).')';
2626
2627
				if (!$this->db->query($sql)) {
2628
					$this->error = $this->db->error()." sql=".$sql;
2629
					$this->errors[] = $this->error;
2630
					$this->db->rollback();
2631
					return -5;
2632
				}
2633
			}
2634
2635
			// Remove other links to the deleted invoice
2636
2637
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'eventorganization_conferenceorboothattendee';
2638
			$sql .= ' SET fk_invoice = NULL';
2639
			$sql .= ' WHERE fk_invoice = '.((int) $rowid);
2640
2641
			if (!$this->db->query($sql)) {
2642
				$this->error = $this->db->error()." sql=".$sql;
2643
				$this->errors[] = $this->error;
2644
				$this->db->rollback();
2645
				return -5;
2646
			}
2647
2648
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'projet_task_time';
2649
			$sql .= ' SET invoice_id = NULL, invoice_line_id = NULL';
2650
			$sql .= ' WHERE invoice_id = '.((int) $rowid);
2651
2652
			if (!$this->db->query($sql)) {
2653
				$this->error = $this->db->error()." sql=".$sql;
2654
				$this->errors[] = $this->error;
2655
				$this->db->rollback();
2656
				return -5;
2657
			}
2658
2659
			// If we decrease stock on invoice validation, we increase back if a warehouse id was provided
2660
			if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && isModEnabled('stock') && !empty($conf->global->STOCK_CALCULATE_ON_BILL) && $idwarehouse != -1) {
2661
				require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2662
				$langs->load("agenda");
2663
2664
				$num = count($this->lines);
2665
				for ($i = 0; $i < $num; $i++) {
2666
					if ($this->lines[$i]->fk_product > 0) {
2667
						$mouvP = new MouvementStock($this->db);
2668
						$mouvP->origin = &$this;
2669
						$mouvP->setOrigin($this->element, $this->id);
2670
						// We decrease stock for product
2671
						if ($this->type == self::TYPE_CREDIT_NOTE) {
2672
							$result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceDeleteDolibarr", $this->ref));
2673
						} else {
2674
							$result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, 0, $langs->trans("InvoiceDeleteDolibarr", $this->ref)); // we use 0 for price, to not change the weighted average value
2675
						}
2676
					}
2677
				}
2678
			}
2679
2680
			// Invoice line extrafileds
2681
			$main = MAIN_DB_PREFIX.'facturedet';
2682
			$ef = $main."_extrafields";
2683
			$sqlef = "DELETE FROM ".$ef." WHERE fk_object IN (SELECT rowid FROM ".$main." WHERE fk_facture = ".((int) $rowid).")";
2684
			// Delete invoice line
2685
			$sql = 'DELETE FROM '.MAIN_DB_PREFIX.'facturedet WHERE fk_facture = '.((int) $rowid);
2686
2687
			if ($this->db->query($sqlef) && $this->db->query($sql) && $this->delete_linked_contact()) {
2688
				$sql = 'DELETE FROM '.MAIN_DB_PREFIX.'facture WHERE rowid = '.((int) $rowid);
2689
2690
				$resql = $this->db->query($sql);
2691
				if ($resql) {
2692
					// Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
2693
					$this->deleteEcmFiles();
2694
2695
					// On efface le repertoire de pdf provisoire
2696
					$ref = dol_sanitizeFileName($this->ref);
2697
					if ($conf->facture->dir_output && !empty($this->ref)) {
2698
						$dir = $conf->facture->dir_output."/".$ref;
2699
						$file = $conf->facture->dir_output."/".$ref."/".$ref.".pdf";
2700
						if (file_exists($file)) {	// We must delete all files before deleting directory
2701
							$ret = dol_delete_preview($this);
2702
2703
							if (!dol_delete_file($file, 0, 0, 0, $this)) { // For triggers
2704
								$langs->load("errors");
2705
								$this->error = $langs->trans("ErrorFailToDeleteFile", $file);
2706
								$this->errors[] = $this->error;
2707
								$this->db->rollback();
2708
								return 0;
2709
							}
2710
						}
2711
						if (file_exists($dir)) {
2712
							if (!dol_delete_dir_recursive($dir)) { // For remove dir and meta
2713
								$langs->load("errors");
2714
								$this->error = $langs->trans("ErrorFailToDeleteDir", $dir);
2715
								$this->errors[] = $this->error;
2716
								$this->db->rollback();
2717
								return 0;
2718
							}
2719
						}
2720
					}
2721
2722
					$this->db->commit();
2723
					return 1;
2724
				} else {
2725
					$this->error = $this->db->lasterror()." sql=".$sql;
2726
					$this->errors[] = $this->error;
2727
					$this->db->rollback();
2728
					return -6;
2729
				}
2730
			} else {
2731
				$this->error = $this->db->lasterror()." sql=".$sql;
2732
				$this->errors[] = $this->error;
2733
				$this->db->rollback();
2734
				return -4;
2735
			}
2736
		} else {
2737
			$this->db->rollback();
2738
			return -2;
2739
		}
2740
	}
2741
2742
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2743
	/**
2744
	 *  Tag the invoice as paid completely (if close_code is filled) => this->fk_statut=2, this->paye=1
2745
	 *  or partialy (if close_code filled) + appel trigger BILL_PAYED => this->fk_statut=2, this->paye stay 0
2746
	 *
2747
	 *	@deprecated
2748
	 *  @see setPaid()
2749
	 *  @param	User	$user      	Object user that modify
2750
	 *	@param  string	$close_code	Code renseigne si on classe a payee completement alors que paiement incomplet (cas escompte par exemple)
2751
	 *	@param  string	$close_note	Commentaire renseigne si on classe a payee alors que paiement incomplet (cas escompte par exemple)
2752
	 *  @return int         		<0 if KO, >0 if OK
2753
	 */
2754
	public function set_paid($user, $close_code = '', $close_note = '')
2755
	{
2756
		// phpcs:enable
2757
		dol_syslog(get_class($this)."::set_paid is deprecated, use setPaid instead", LOG_NOTICE);
2758
		return $this->setPaid($user, $close_code, $close_note);
2759
	}
2760
2761
	/**
2762
	 *  Tag the invoice as paid completely (if close_code is filled) => this->fk_statut=2, this->paye=1
2763
	 *  or partially (if close_code filled) + appel trigger BILL_PAYED => this->fk_statut=2, this->paye stay 0
2764
	 *
2765
	 *  @param	User	$user      	Object user that modify
2766
	 *	@param  string	$close_code	Code renseigne si on classe a payee completement alors que paiement incomplet (cas escompte par exemple)
2767
	 *	@param  string	$close_note	Commentaire renseigne si on classe a payee alors que paiement incomplet (cas escompte par exemple)
2768
	 *  @return int         		<0 if KO, >0 if OK
2769
	 */
2770
	public function setPaid($user, $close_code = '', $close_note = '')
2771
	{
2772
		$error = 0;
2773
2774
		if ($this->paye != 1) {
2775
			$this->db->begin();
2776
2777
			$now = dol_now();
2778
2779
			dol_syslog(get_class($this)."::setPaid rowid=".((int) $this->id), LOG_DEBUG);
2780
2781
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture SET';
2782
			$sql .= ' fk_statut='.self::STATUS_CLOSED;
2783
			if (!$close_code) {
2784
				$sql .= ', paye=1';
2785
			}
2786
			if ($close_code) {
2787
				$sql .= ", close_code='".$this->db->escape($close_code)."'";
2788
			}
2789
			if ($close_note) {
2790
				$sql .= ", close_note='".$this->db->escape($close_note)."'";
2791
			}
2792
			$sql .= ', fk_user_closing = '.((int) $user->id);
2793
			$sql .= ", date_closing = '".$this->db->idate($now)."'";
2794
			$sql .= " WHERE rowid = ".((int) $this->id);
2795
2796
			$resql = $this->db->query($sql);
2797
			if ($resql) {
2798
				// Call trigger
2799
				$result = $this->call_trigger('BILL_PAYED', $user);
2800
				if ($result < 0) {
2801
					$error++;
2802
				}
2803
				// End call triggers
2804
			} else {
2805
				$error++;
2806
				$this->error = $this->db->lasterror();
2807
			}
2808
2809
			if (!$error) {
2810
				$this->db->commit();
2811
				return 1;
2812
			} else {
2813
				$this->db->rollback();
2814
				return -1;
2815
			}
2816
		} else {
2817
			return 0;
2818
		}
2819
	}
2820
2821
2822
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2823
	/**
2824
	 *  Tag la facture comme non payee completement + appel trigger BILL_UNPAYED
2825
	 *	Fonction utilisee quand un paiement prelevement est refuse,
2826
	 * 	ou quand une facture annulee et reouverte.
2827
	 *
2828
	 *	@deprecated
2829
	 *  @see setUnpaid()
2830
	 *  @param	User	$user       Object user that change status
2831
	 *  @return int         		<0 if KO, >0 if OK
2832
	 */
2833
	public function set_unpaid($user)
2834
	{
2835
		// phpcs:enable
2836
		dol_syslog(get_class($this)."::set_unpaid is deprecated, use setUnpaid instead", LOG_NOTICE);
2837
		return $this->setUnpaid($user);
2838
	}
2839
2840
	/**
2841
	 *  Tag la facture comme non payee completement + appel trigger BILL_UNPAYED
2842
	 *	Fonction utilisee quand un paiement prelevement est refuse,
2843
	 * 	ou quand une facture annulee et reouverte.
2844
	 *
2845
	 *  @param	User	$user       Object user that change status
2846
	 *  @return int         		<0 if KO, >0 if OK
2847
	 */
2848
	public function setUnpaid($user)
2849
	{
2850
		$error = 0;
2851
2852
		$this->db->begin();
2853
2854
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
2855
		$sql .= ' SET paye=0, fk_statut='.self::STATUS_VALIDATED.', close_code=null, close_note=null,';
2856
		$sql .= ' date_closing=null,';
2857
		$sql .= ' fk_user_closing=null';
2858
		$sql .= " WHERE rowid = ".((int) $this->id);
2859
2860
		dol_syslog(get_class($this)."::setUnpaid", LOG_DEBUG);
2861
		$resql = $this->db->query($sql);
2862
		if ($resql) {
2863
			// Call trigger
2864
			$result = $this->call_trigger('BILL_UNPAYED', $user);
2865
			if ($result < 0) {
2866
				$error++;
2867
			}
2868
			// End call triggers
2869
		} else {
2870
			$error++;
2871
			$this->error = $this->db->error();
2872
			dol_print_error($this->db);
2873
		}
2874
2875
		if (!$error) {
2876
			$this->db->commit();
2877
			return 1;
2878
		} else {
2879
			$this->db->rollback();
2880
			return -1;
2881
		}
2882
	}
2883
2884
2885
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2886
	/**
2887
	 *	Tag invoice as canceled, with no payment on it (example for replacement invoice or payment never received) + call trigger BILL_CANCEL
2888
	 *	Warning, if option to decrease stock on invoice was set, this function does not change stock (it might be a cancel because
2889
	 *  of no payment even if merchandises were sent).
2890
	 *
2891
	 *	@deprecated
2892
	 *  @see setCanceled()
2893
	 *	@param	User	$user        	Object user making change
2894
	 *	@param	string	$close_code		Code of closing invoice (CLOSECODE_REPLACED, CLOSECODE_...)
2895
	 *	@param	string	$close_note		Comment
2896
	 *	@return int         			<0 if KO, >0 if OK
2897
	 */
2898
	public function set_canceled($user, $close_code = '', $close_note = '')
2899
	{
2900
		// phpcs:enable
2901
		dol_syslog(get_class($this)."::set_canceled is deprecated, use setCanceled instead", LOG_NOTICE);
2902
		return $this->setCanceled($user, $close_code, $close_note);
2903
	}
2904
2905
	/**
2906
	 *	Tag invoice as canceled, with no payment on it (example for replacement invoice or payment never received) + call trigger BILL_CANCEL
2907
	 *	Warning, if option to decrease stock on invoice was set, this function does not change stock (it might be a cancel because
2908
	 *  of no payment even if merchandises were sent).
2909
	 *
2910
	 *	@param	User	$user        	Object user making change
2911
	 *	@param	string	$close_code		Code of closing invoice (CLOSECODE_REPLACED, CLOSECODE_...)
2912
	 *	@param	string	$close_note		Comment
2913
	 *	@return int         			<0 if KO, >0 if OK
2914
	 */
2915
	public function setCanceled($user, $close_code = '', $close_note = '')
2916
	{
2917
		dol_syslog(get_class($this)."::setCanceled rowid=".((int) $this->id), LOG_DEBUG);
2918
2919
		$this->db->begin();
2920
2921
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture SET';
2922
		$sql .= ' fk_statut='.self::STATUS_ABANDONED;
2923
		if ($close_code) {
2924
			$sql .= ", close_code='".$this->db->escape($close_code)."'";
2925
		}
2926
		if ($close_note) {
2927
			$sql .= ", close_note='".$this->db->escape($close_note)."'";
2928
		}
2929
		$sql .= " WHERE rowid = ".((int) $this->id);
2930
2931
		$resql = $this->db->query($sql);
2932
		if ($resql) {
2933
			// Bound discounts are deducted from the invoice
2934
			// as they have not been used since the invoice is abandoned.
2935
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
2936
			$sql .= ' SET fk_facture = NULL';
2937
			$sql .= ' WHERE fk_facture = '.((int) $this->id);
2938
2939
			$resql = $this->db->query($sql);
2940
			if ($resql) {
2941
				// Call trigger
2942
				$result = $this->call_trigger('BILL_CANCEL', $user);
2943
				if ($result < 0) {
2944
					$this->db->rollback();
2945
					return -1;
2946
				}
2947
				// End call triggers
2948
2949
				$this->db->commit();
2950
				return 1;
2951
			} else {
2952
				$this->error = $this->db->error()." sql=".$sql;
2953
				$this->db->rollback();
2954
				return -1;
2955
			}
2956
		} else {
2957
			$this->error = $this->db->error()." sql=".$sql;
2958
			$this->db->rollback();
2959
			return -2;
2960
		}
2961
	}
2962
2963
	/**
2964
	 * Tag invoice as validated + call trigger BILL_VALIDATE
2965
	 * Object must have lines loaded with fetch_lines
2966
	 *
2967
	 * @param	User	$user           Object user that validate
2968
	 * @param   string	$force_number	Reference to force on invoice
2969
	 * @param	int		$idwarehouse	Id of warehouse to use for stock decrease if option to decreasenon stock is on (0=no decrease)
2970
	 * @param	int		$notrigger		1=Does not execute triggers, 0= execute triggers
2971
	 * @param	int		$batch_rule		0=do not decrement batch, else batch rule to use, 1=take in batches ordered by sellby and eatby dates
2972
	 * @return	int						<0 if KO, 0=Nothing done because invoice is not a draft, >0 if OK
2973
	 */
2974
	public function validate($user, $force_number = '', $idwarehouse = 0, $notrigger = 0, $batch_rule = 0)
2975
	{
2976
		global $conf, $langs, $mysoc;
2977
		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2978
2979
		$productStatic = null;
2980
		$warehouseStatic = null;
2981
		if ($batch_rule > 0) {
2982
			require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
2983
			require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
2984
			require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
2985
			$productStatic = new Product($this->db);
2986
			$warehouseStatic = new Entrepot($this->db);
2987
			$productbatch = new ProductBatch($this->db);
2988
		}
2989
2990
		$now = dol_now();
2991
2992
		$error = 0;
2993
		dol_syslog(get_class($this).'::validate user='.$user->id.', force_number='.$force_number.', idwarehouse='.$idwarehouse);
2994
2995
		// Force to have object complete for checks
2996
		$this->fetch_thirdparty();
2997
		$this->fetch_lines();
2998
2999
		// Check parameters
3000
		if ($this->statut != self::STATUS_DRAFT) {
3001
			dol_syslog(get_class($this)."::validate status is not draft. operation canceled.", LOG_WARNING);
3002
			return 0;
3003
		}
3004
		if (count($this->lines) <= 0) {
3005
			$langs->load("errors");
3006
			$this->error = $langs->trans("ErrorObjectMustHaveLinesToBeValidated", $this->ref);
3007
			return -1;
3008
		}
3009
		if ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && empty($user->rights->facture->creer))
3010
		|| (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && empty($user->rights->facture->invoice_advance->validate))) {
3011
			$this->error = 'Permission denied';
3012
			dol_syslog(get_class($this)."::validate ".$this->error.' MAIN_USE_ADVANCED_PERMS='.$conf->global->MAIN_USE_ADVANCED_PERMS, LOG_ERR);
3013
			return -1;
3014
		}
3015
		if (!empty($conf->global-> INVOICE_CHECK_POSTERIOR_DATE)) {
3016
			$last_of_type = $this->willBeLastOfSameType(true);
3017
			if (!$last_of_type[0]) {
3018
				$this->error = $langs->transnoentities("ErrorInvoiceIsNotLastOfSameType", $this->ref, dol_print_date($this->date, 'day'), dol_print_date($last_of_type[1], 'day'));
3019
				return -1;
3020
			}
3021
		}
3022
3023
		// Check for mandatory fields in thirdparty (defined into setup)
3024
		if (!empty($this->thirdparty) && is_object($this->thirdparty)) {
3025
			$array_to_check = array('IDPROF1', 'IDPROF2', 'IDPROF3', 'IDPROF4', 'IDPROF5', 'IDPROF6', 'EMAIL', 'ACCOUNTANCY_CODE_CUSTOMER');
3026
			foreach ($array_to_check as $key) {
3027
				$keymin = strtolower($key);
3028
				if (!property_exists($this->thirdparty, $keymin)) {
3029
					continue;
3030
				}
3031
				$vallabel = $this->thirdparty->$keymin;
3032
3033
				$i = (int) preg_replace('/[^0-9]/', '', $key);
3034
				if ($i > 0) {
3035
					if ($this->thirdparty->isACompany()) {
3036
						// Check for mandatory prof id (but only if country is other than ours)
3037
						if ($mysoc->country_id > 0 && $this->thirdparty->country_id == $mysoc->country_id) {
3038
							$idprof_mandatory = 'SOCIETE_'.$key.'_INVOICE_MANDATORY';
3039
							if (!$vallabel && !empty($conf->global->$idprof_mandatory)) {
3040
								$langs->load("errors");
3041
								$this->error = $langs->trans('ErrorProdIdIsMandatory', $langs->transcountry('ProfId'.$i, $this->thirdparty->country_code)).' ('.$langs->trans("ForbiddenBySetupRules").') ['.$langs->trans('Company').' : '.$this->thirdparty->name.']';
3042
								dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
3043
								return -1;
3044
							}
3045
						}
3046
					}
3047
				} else {
3048
					if ($key == 'EMAIL') {
3049
						// Check for mandatory
3050
						if (!empty($conf->global->SOCIETE_EMAIL_INVOICE_MANDATORY) && !isValidEMail($this->thirdparty->email)) {
3051
							$langs->load("errors");
3052
							$this->error = $langs->trans("ErrorBadEMail", $this->thirdparty->email).' ('.$langs->trans("ForbiddenBySetupRules").') ['.$langs->trans('Company').' : '.$this->thirdparty->name.']';
3053
							dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
3054
							return -1;
3055
						}
3056
					}
3057
					if ($key == 'ACCOUNTANCY_CODE_CUSTOMER') {
3058
						// Check for mandatory
3059
						if (!empty($conf->global->SOCIETE_ACCOUNTANCY_CODE_CUSTOMER_INVOICE_MANDATORY) && empty($this->thirdparty->code_compta)) {
3060
							$langs->load("errors");
3061
							$this->error = $langs->trans("ErrorAccountancyCodeCustomerIsMandatory", $this->thirdparty->name).' ('.$langs->trans("ForbiddenBySetupRules").')';
3062
							dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
3063
							return -1;
3064
						}
3065
					}
3066
				}
3067
			}
3068
		}
3069
3070
		// Check for mandatory fields in $this
3071
		$array_to_check = array('REF_CLIENT'=>'RefCustomer');
3072
		foreach ($array_to_check as $key => $val) {
3073
			$keymin = strtolower($key);
3074
			$vallabel = $this->$keymin;
3075
3076
			// Check for mandatory
3077
			$keymandatory = 'INVOICE_'.$key.'_MANDATORY_FOR_VALIDATION';
3078
			if (!$vallabel && !empty($conf->global->$keymandatory)) {
3079
				$langs->load("errors");
3080
				$error++;
3081
				setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv($val)), null, 'errors');
3082
			}
3083
		}
3084
3085
		$this->db->begin();
3086
3087
		// Check parameters
3088
		if ($this->type == self::TYPE_REPLACEMENT) {		// if this is a replacement invoice
3089
			// Check that source invoice is known
3090
			if ($this->fk_facture_source <= 0) {
3091
				$this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("InvoiceReplacement"));
3092
				$this->db->rollback();
3093
				return -10;
3094
			}
3095
3096
			// Load source invoice that has been replaced
3097
			$facreplaced = new Facture($this->db);
3098
			$result = $facreplaced->fetch($this->fk_facture_source);
3099
			if ($result <= 0) {
3100
				$this->error = $langs->trans("ErrorBadInvoice");
3101
				$this->db->rollback();
3102
				return -11;
3103
			}
3104
3105
			// Check that source invoice not already replaced by another one.
3106
			$idreplacement = $facreplaced->getIdReplacingInvoice('validated');
3107
			if ($idreplacement && $idreplacement != $this->id) {
3108
				$facreplacement = new Facture($this->db);
3109
				$facreplacement->fetch($idreplacement);
3110
				$this->error = $langs->trans("ErrorInvoiceAlreadyReplaced", $facreplaced->ref, $facreplacement->ref);
3111
				$this->db->rollback();
3112
				return -12;
3113
			}
3114
3115
			$result = $facreplaced->setCanceled($user, self::CLOSECODE_REPLACED, '');
3116
			if ($result < 0) {
3117
				$this->error = $facreplaced->error;
3118
				$this->db->rollback();
3119
				return -13;
3120
			}
3121
		}
3122
3123
		// Define new ref
3124
		if ($force_number) {
3125
			$num = $force_number;
3126
		} elseif (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
3127
			if (!empty($conf->global->FAC_FORCE_DATE_VALIDATION)) {	// If option enabled, we force invoice date
3128
				$this->date = dol_now();
3129
				$this->date_lim_reglement = $this->calculate_date_lim_reglement();
3130
			}
3131
			$num = $this->getNextNumRef($this->thirdparty);
3132
		} else {
3133
			$num = $this->ref;
3134
		}
3135
3136
		$this->newref = dol_sanitizeFileName($num);
3137
3138
		if ($num) {
3139
			$this->update_price(1);
3140
3141
			// Validate
3142
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
3143
			$sql .= " SET ref = '".$this->db->escape($num)."', fk_statut = ".self::STATUS_VALIDATED.", fk_user_valid = ".($user->id > 0 ? $user->id : "null").", date_valid = '".$this->db->idate($now)."'";
3144
			if (!empty($conf->global->FAC_FORCE_DATE_VALIDATION)) {	// If option enabled, we force invoice date
3145
				$sql .= ", datef='".$this->db->idate($this->date)."'";
3146
				$sql .= ", date_lim_reglement='".$this->db->idate($this->date_lim_reglement)."'";
3147
			}
3148
			$sql .= " WHERE rowid = ".((int) $this->id);
3149
3150
			dol_syslog(get_class($this)."::validate", LOG_DEBUG);
3151
			$resql = $this->db->query($sql);
3152
			if (!$resql) {
3153
				dol_print_error($this->db);
3154
				$error++;
3155
			}
3156
3157
			// We check if the invoice was provisional
3158
			if (!$error && (preg_match('/^[\(]?PROV/i', $this->ref))) {
3159
				// La verif qu'une remise n'est pas utilisee 2 fois est faite au moment de l'insertion de ligne
3160
			}
3161
3162
			if (!$error) {
3163
				// Define third party as a customer
3164
				$result = $this->thirdparty->set_as_client();
3165
3166
				// If active we decrement the main product and its components at invoice validation
3167
				if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && isModEnabled('stock') && !empty($conf->global->STOCK_CALCULATE_ON_BILL) && $idwarehouse > 0) {
3168
					require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
3169
					$langs->load("agenda");
3170
3171
					// Loop on each line
3172
					$cpt = count($this->lines);
3173
					for ($i = 0; $i < $cpt; $i++) {
3174
						if ($this->lines[$i]->fk_product > 0) {
3175
							$mouvP = new MouvementStock($this->db);
3176
							$mouvP->origin = &$this;
3177
							$mouvP->setOrigin($this->element, $this->id);
3178
							// We decrease stock for product
3179
							if ($this->type == self::TYPE_CREDIT_NOTE) {
3180
								$result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, 0, $langs->trans("InvoiceValidatedInDolibarr", $num));
3181
								if ($result < 0) {
3182
									$error++;
3183
									$this->error = $mouvP->error;
3184
								}
3185
							} else {
3186
								$is_batch_line = false;
3187
								if ($batch_rule > 0) {
3188
									$productStatic->fetch($this->lines[$i]->fk_product);
3189
									if ($productStatic->hasbatch()) {
3190
										$is_batch_line = true;
3191
										$product_qty_remain = $this->lines[$i]->qty;
3192
3193
										$sortfield = null;
3194
										$sortorder = null;
3195
										// find all batch order by sellby (DLC) and eatby dates (DLUO) first
3196
										if ($batch_rule == Productbatch::BATCH_RULE_SELLBY_EATBY_DATES_FIRST) {
3197
											$sortfield = 'pl.sellby,pl.eatby,pb.qty,pl.rowid';
3198
											$sortorder = 'ASC,ASC,ASC,ASC';
3199
										}
3200
3201
										$resBatchList = $productbatch->findAllForProduct($productStatic->id, $idwarehouse, (!empty($conf->global->STOCK_ALLOW_NEGATIVE_TRANSFER) ? null : 0), $sortfield, $sortorder);
3202
										if (!is_array($resBatchList)) {
3203
											$error++;
3204
											$this->error = $this->db->lasterror();
3205
										}
3206
3207
										if (!$error) {
3208
											$batchList = $resBatchList;
3209
											if (empty($batchList)) {
3210
												$error++;
3211
												$langs->load('errors');
3212
												$warehouseStatic->fetch($idwarehouse);
3213
												$this->error = $langs->trans('ErrorBatchNoFoundForProductInWarehouse', $productStatic->label, $warehouseStatic->ref);
3214
												dol_syslog(__METHOD__.' Error: '.$langs->transnoentitiesnoconv('ErrorBatchNoFoundForProductInWarehouse', $productStatic->label, $warehouseStatic->ref), LOG_ERR);
3215
											}
3216
3217
											foreach ($batchList as $batch) {
3218
												if ($batch->qty <= 0) {
3219
													continue; // try to decrement only batches have positive quantity first
3220
												}
3221
3222
												// enough quantity in this batch
3223
												if ($batch->qty >= $product_qty_remain) {
3224
													$product_batch_qty = $product_qty_remain;
3225
												} else {
3226
													// not enough (take all in batch)
3227
													$product_batch_qty = $batch->qty;
3228
												}
3229
												$result = $mouvP->livraison($user, $productStatic->id, $idwarehouse, $product_batch_qty, $this->lines[$i]->subprice, $langs->trans('InvoiceValidatedInDolibarr', $num), '', '', '', $batch->batch);
3230
												if ($result < 0) {
3231
													$error++;
3232
													$this->error = $mouvP->error;
3233
													break;
3234
												}
3235
3236
												$product_qty_remain -= $product_batch_qty;
3237
												// all product quantity was decremented
3238
												if ($product_qty_remain <= 0) {
3239
													break;
3240
												}
3241
											}
3242
3243
											if (!$error && $product_qty_remain > 0) {
3244
												if ($conf->global->STOCK_ALLOW_NEGATIVE_TRANSFER) {
3245
													// take in the first batch
3246
													$batch = $batchList[0];
3247
													$result = $mouvP->livraison($user, $productStatic->id, $idwarehouse, $product_qty_remain, $this->lines[$i]->subprice, $langs->trans('InvoiceValidatedInDolibarr', $num), '', '', '', $batch->batch);
3248
													if ($result < 0) {
3249
														$error++;
3250
														$this->error = $mouvP->error;
3251
													}
3252
												} else {
3253
													$error++;
3254
													$langs->load('errors');
3255
													$warehouseStatic->fetch($idwarehouse);
3256
													$this->error = $langs->trans('ErrorBatchNoFoundEnoughQuantityForProductInWarehouse', $productStatic->label, $warehouseStatic->ref);
3257
													dol_syslog(__METHOD__.' Error: '.$langs->transnoentitiesnoconv('ErrorBatchNoFoundEnoughQuantityForProductInWarehouse', $productStatic->label, $warehouseStatic->ref), LOG_ERR);
3258
												}
3259
											}
3260
										}
3261
									}
3262
								}
3263
3264
								if (!$is_batch_line) {
3265
									$result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceValidatedInDolibarr", $num));
3266
									if ($result < 0) {
3267
										$error++;
3268
										$this->error = $mouvP->error;
3269
									}
3270
								}
3271
							}
3272
						}
3273
					}
3274
				}
3275
			}
3276
3277
			/*
3278
			 * Set situation_final to 0 if is a credit note and the invoice source is a invoice situation (case when invoice situation is at 100%)
3279
			 * So we can continue to create new invoice situation
3280
			 */
3281
			if (!$error && $this->type == self::TYPE_CREDIT_NOTE && $this->fk_facture_source > 0) {
3282
				$invoice_situation = new Facture($this->db);
3283
				$result = $invoice_situation->fetch($this->fk_facture_source);
3284
				if ($result > 0 && $invoice_situation->type == self::TYPE_SITUATION && $invoice_situation->situation_final == 1) {
3285
					$invoice_situation->situation_final = 0;
3286
					// Disable triggers because module can force situation_final to 1 by triggers (ex: SubTotal)
3287
					$result = $invoice_situation->setFinal($user, 1);
3288
				}
3289
				if ($result < 0) {
3290
					$this->error = $invoice_situation->error;
3291
					$this->errors = $invoice_situation->errors;
3292
					$error++;
3293
				}
3294
			}
3295
3296
			// Trigger calls
3297
			if (!$error && !$notrigger) {
3298
				// Call trigger
3299
				$result = $this->call_trigger('BILL_VALIDATE', $user);
3300
				if ($result < 0) {
3301
					$error++;
3302
				}
3303
				// End call triggers
3304
			}
3305
3306
			if (!$error) {
3307
				$this->oldref = $this->ref;
3308
3309
				// Rename directory if dir was a temporary ref
3310
				if (preg_match('/^[\(]?PROV/i', $this->ref)) {
3311
					// Now we rename also files into index
3312
					$sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'facture/".$this->db->escape($this->newref)."'";
3313
					$sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'facture/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
3314
					$resql = $this->db->query($sql);
3315
					if (!$resql) {
3316
						$error++;
3317
						$this->error = $this->db->lasterror();
3318
					}
3319
3320
					// We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
3321
					$oldref = dol_sanitizeFileName($this->ref);
3322
					$newref = dol_sanitizeFileName($num);
3323
					$dirsource = $conf->facture->dir_output.'/'.$oldref;
3324
					$dirdest = $conf->facture->dir_output.'/'.$newref;
3325
					if (!$error && file_exists($dirsource)) {
3326
						dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
3327
3328
						if (@rename($dirsource, $dirdest)) {
3329
							dol_syslog("Rename ok");
3330
							// Rename docs starting with $oldref with $newref
3331
							$listoffiles = dol_dir_list($conf->facture->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
3332
							foreach ($listoffiles as $fileentry) {
3333
								$dirsource = $fileentry['name'];
3334
								$dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
3335
								$dirsource = $fileentry['path'].'/'.$dirsource;
3336
								$dirdest = $fileentry['path'].'/'.$dirdest;
3337
								@rename($dirsource, $dirdest);
3338
							}
3339
						}
3340
					}
3341
				}
3342
			}
3343
3344
			if (!$error && !$this->is_last_in_cycle()) {
3345
				if (!$this->updatePriceNextInvoice($langs)) {
3346
					$error++;
3347
				}
3348
			}
3349
3350
			// Set new ref and define current status
3351
			if (!$error) {
3352
				$this->ref = $num;
3353
				$this->ref = $num;
3354
				$this->statut = self::STATUS_VALIDATED;
3355
				$this->status = self::STATUS_VALIDATED;
3356
				$this->brouillon = 0;
3357
				$this->date_validation = $now;
3358
				$i = 0;
3359
3360
				if (!empty($conf->global->INVOICE_USE_SITUATION)) {
3361
					$final = true;
3362
					$nboflines = count($this->lines);
3363
					while (($i < $nboflines) && $final) {
3364
						$final = ($this->lines[$i]->situation_percent == 100);
3365
						$i++;
3366
					}
3367
3368
					if (empty($final)) {
3369
						$this->situation_final = 0;
3370
					} else {
3371
						$this->situation_final = 1;
3372
					}
3373
3374
					$this->setFinal($user);
3375
				}
3376
			}
3377
		} else {
3378
			$error++;
3379
		}
3380
3381
		if (!$error) {
3382
			$this->db->commit();
3383
			return 1;
3384
		} else {
3385
			$this->db->rollback();
3386
			return -1;
3387
		}
3388
	}
3389
3390
	/**
3391
	 * Update price of next invoice
3392
	 *
3393
	 * @param	Translate	$langs	Translate object
3394
	 * @return 	bool				false if KO, true if OK
3395
	 */
3396
	public function updatePriceNextInvoice(&$langs)
3397
	{
3398
		foreach ($this->tab_next_situation_invoice as $next_invoice) {
3399
			$is_last = $next_invoice->is_last_in_cycle();
3400
3401
			if ($next_invoice->statut == self::STATUS_DRAFT && $is_last != 1) {
3402
				$this->error = $langs->trans('updatePriceNextInvoiceErrorUpdateline', $next_invoice->ref);
3403
				return false;
3404
			}
3405
3406
			$next_invoice->brouillon = 1;
3407
3408
			foreach ($next_invoice->lines as $line) {
3409
				$result = $next_invoice->updateline(
3410
					$line->id,
3411
					$line->desc,
3412
					$line->subprice,
3413
					$line->qty,
3414
					$line->remise_percent,
3415
					$line->date_start,
3416
					$line->date_end,
3417
					$line->tva_tx,
3418
					$line->localtax1_tx,
3419
					$line->localtax2_tx,
3420
					'HT',
3421
					$line->info_bits,
3422
					$line->product_type,
3423
					$line->fk_parent_line,
3424
					0,
3425
					$line->fk_fournprice,
3426
					$line->pa_ht,
3427
					$line->label,
3428
					$line->special_code,
3429
					$line->array_options,
3430
					$line->situation_percent,
3431
					$line->fk_unit
3432
				);
3433
3434
				if ($result < 0) {
3435
					$this->error = $langs->trans('updatePriceNextInvoiceErrorUpdateline', $next_invoice->ref);
3436
					return false;
3437
				}
3438
			}
3439
3440
			break; // Only the next invoice and not each next invoice
3441
		}
3442
3443
		return true;
3444
	}
3445
3446
	/**
3447
	 *	Set draft status
3448
	 *
3449
	 *	@param	User	$user			Object user that modify
3450
	 *	@param	int		$idwarehouse	Id warehouse to use for stock change.
3451
	 *	@return	int						<0 if KO, >0 if OK
3452
	 */
3453
	public function setDraft($user, $idwarehouse = -1)
3454
	{
3455
		// phpcs:enable
3456
		global $conf, $langs;
3457
3458
		$error = 0;
3459
3460
		if ($this->statut == self::STATUS_DRAFT) {
3461
			dol_syslog(__METHOD__." already draft status", LOG_WARNING);
3462
			return 0;
3463
		}
3464
3465
		dol_syslog(__METHOD__, LOG_DEBUG);
3466
3467
		$this->db->begin();
3468
3469
		$sql = "UPDATE ".MAIN_DB_PREFIX."facture";
3470
		$sql .= " SET fk_statut = ".self::STATUS_DRAFT;
3471
		$sql .= " WHERE rowid = ".((int) $this->id);
3472
3473
		$result = $this->db->query($sql);
3474
		if ($result) {
3475
			if (!$error) {
3476
				$this->oldcopy = clone $this;
3477
			}
3478
3479
			// If we decrease stock on invoice validation, we increase back
3480
			if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && isModEnabled('stock') && !empty($conf->global->STOCK_CALCULATE_ON_BILL)) {
3481
				require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
3482
				$langs->load("agenda");
3483
3484
				$num = count($this->lines);
3485
				for ($i = 0; $i < $num; $i++) {
3486
					if ($this->lines[$i]->fk_product > 0) {
3487
						$mouvP = new MouvementStock($this->db);
3488
						$mouvP->origin = &$this;
3489
						$mouvP->setOrigin($this->element, $this->id);
3490
						// We decrease stock for product
3491
						if ($this->type == self::TYPE_CREDIT_NOTE) {
3492
							$result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceBackToDraftInDolibarr", $this->ref));
3493
						} else {
3494
							$result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, 0, $langs->trans("InvoiceBackToDraftInDolibarr", $this->ref)); // we use 0 for price, to not change the weighted average value
3495
						}
3496
					}
3497
				}
3498
			}
3499
3500
			if ($error == 0) {
3501
				$old_statut = $this->statut;
3502
				$this->brouillon = 1;
3503
				$this->statut = self::STATUS_DRAFT;
3504
				$this->status = self::STATUS_DRAFT;
3505
3506
				// Call trigger
3507
				$result = $this->call_trigger('BILL_UNVALIDATE', $user);
3508
				if ($result < 0) {
3509
					$error++;
3510
					$this->statut = $old_statut;
3511
					$this->status = $old_statut;
3512
					$this->brouillon = 0;
3513
				}
3514
				// End call triggers
3515
			} else {
3516
				$this->db->rollback();
3517
				return -1;
3518
			}
3519
3520
			if ($error == 0) {
3521
				$this->db->commit();
3522
				return 1;
3523
			} else {
3524
				$this->db->rollback();
3525
				return -1;
3526
			}
3527
		} else {
3528
			$this->error = $this->db->error();
3529
			$this->db->rollback();
3530
			return -1;
3531
		}
3532
	}
3533
3534
3535
	/**
3536
	 *  Add an invoice line into database (linked to product/service or not).
3537
	 *  Les parametres sont deja cense etre juste et avec valeurs finales a l'appel
3538
	 *  de cette methode. Aussi, pour le taux tva, il doit deja avoir ete defini
3539
	 *  par l'appelant par la methode get_default_tva(societe_vendeuse,societe_acheteuse,produit)
3540
	 *  et le desc doit deja avoir la bonne valeur (a l'appelant de gerer le multilangue)
3541
	 *
3542
	 *  @param    	string		$desc            	Description of line
3543
	 *  @param    	double		$pu_ht              Unit price without tax (> 0 even for credit note)
3544
	 *  @param    	double		$qty             	Quantity
3545
	 *  @param    	double		$txtva           	Force Vat rate, -1 for auto (Can contain the vat_src_code too with syntax '9.9 (CODE)')
3546
	 *  @param		double		$txlocaltax1		Local tax 1 rate (deprecated, use instead txtva with code inside)
3547
	 *  @param		double		$txlocaltax2		Local tax 2 rate (deprecated, use instead txtva with code inside)
3548
	 *  @param    	int			$fk_product      	Id of predefined product/service
3549
	 *  @param    	double		$remise_percent  	Percent of discount on line
3550
	 *  @param    	int			$date_start      	Date start of service
3551
	 *  @param    	int			$date_end        	Date end of service
3552
	 *  @param    	int			$ventil          	Code of dispatching into accountancy
3553
	 *  @param    	int			$info_bits			Bits of type of lines
3554
	 *  @param    	int			$fk_remise_except	Id discount used
3555
	 *  @param		string		$price_base_type	'HT' or 'TTC'
3556
	 *  @param    	double		$pu_ttc             Unit price with tax (> 0 even for credit note)
3557
	 *  @param		int			$type				Type of line (0=product, 1=service). Not used if fk_product is defined, the type of product is used.
3558
	 *  @param      int			$rang               Position of line (-1 means last value + 1)
3559
	 *  @param		int			$special_code		Special code (also used by externals modules!)
3560
	 *  @param		string		$origin				Depend on global conf MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION can be 'orderdet', 'propaldet'..., else 'order','propal,'....
3561
	 *  @param		int			$origin_id			Depend on global conf MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION can be Id of origin object (aka line id), else object id
3562
	 *  @param		int			$fk_parent_line		Id of parent line
3563
	 *  @param		int			$fk_fournprice		Supplier price id (to calculate margin) or ''
3564
	 *  @param		int			$pa_ht				Buying price of line (to calculate margin) or ''
3565
	 *  @param		string		$label				Label of the line (deprecated, do not use)
3566
	 *  @param		array		$array_options		extrafields array
3567
	 *  @param      int         $situation_percent  Situation advance percentage
3568
	 *  @param      int         $fk_prev_id         Previous situation line id reference
3569
	 *  @param 		string		$fk_unit 			Code of the unit to use. Null to use the default one
3570
	 *  @param		double		$pu_ht_devise		Unit price in foreign currency
3571
	 *  @param		string		$ref_ext		    External reference of the line
3572
	 *  @param		int			$noupdateafterinsertline	No update after insert of line
3573
	 *  @return    	int             				<0 if KO, Id of line if OK
3574
	 */
3575
	public function addline(
3576
		$desc,
3577
		$pu_ht,
3578
		$qty,
3579
		$txtva,
3580
		$txlocaltax1 = 0,
3581
		$txlocaltax2 = 0,
3582
		$fk_product = 0,
3583
		$remise_percent = 0,
3584
		$date_start = '',
3585
		$date_end = '',
3586
		$ventil = 0,
3587
		$info_bits = 0,
3588
		$fk_remise_except = '',
3589
		$price_base_type = 'HT',
3590
		$pu_ttc = 0,
3591
		$type = 0,
3592
		$rang = -1,
3593
		$special_code = 0,
3594
		$origin = '',
3595
		$origin_id = 0,
3596
		$fk_parent_line = 0,
3597
		$fk_fournprice = null,
3598
		$pa_ht = 0,
3599
		$label = '',
3600
		$array_options = 0,
3601
		$situation_percent = 100,
3602
		$fk_prev_id = 0,
3603
		$fk_unit = null,
3604
		$pu_ht_devise = 0,
3605
		$ref_ext = '',
3606
		$noupdateafterinsertline = 0
3607
	) {
3608
		// Deprecation warning
3609
		if ($label) {
3610
			dol_syslog(__METHOD__.": using line label is deprecated", LOG_WARNING);
3611
			//var_dump(debug_backtrace(false));exit;
3612
		}
3613
3614
		global $mysoc, $conf, $langs;
3615
3616
		dol_syslog(get_class($this)."::addline id=$this->id, pu_ht=$pu_ht, qty=$qty, txtva=$txtva, txlocaltax1=$txlocaltax1, txlocaltax2=$txlocaltax2, fk_product=$fk_product, remise_percent=$remise_percent, date_start=$date_start, date_end=$date_end, ventil=$ventil, info_bits=$info_bits, fk_remise_except=$fk_remise_except, price_base_type=$price_base_type, pu_ttc=$pu_ttc, type=$type, fk_unit=$fk_unit, desc=".dol_trunc($desc, 25), LOG_DEBUG);
3617
3618
		if ($this->statut == self::STATUS_DRAFT) {
3619
			include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
3620
3621
			// Clean parameters
3622
			if (empty($remise_percent)) {
3623
				$remise_percent = 0;
3624
			}
3625
			if (empty($qty)) {
3626
				$qty = 0;
3627
			}
3628
			if (empty($info_bits)) {
3629
				$info_bits = 0;
3630
			}
3631
			if (empty($rang)) {
3632
				$rang = 0;
3633
			}
3634
			if (empty($ventil)) {
3635
				$ventil = 0;
3636
			}
3637
			if (empty($txtva)) {
3638
				$txtva = 0;
3639
			}
3640
			if (empty($txlocaltax1)) {
3641
				$txlocaltax1 = 0;
3642
			}
3643
			if (empty($txlocaltax2)) {
3644
				$txlocaltax2 = 0;
3645
			}
3646
			if (empty($fk_parent_line) || $fk_parent_line < 0) {
3647
				$fk_parent_line = 0;
3648
			}
3649
			if (empty($fk_prev_id)) {
3650
				$fk_prev_id = 'null';
3651
			}
3652
			if (!isset($situation_percent) || $situation_percent > 100 || (string) $situation_percent == '') {
3653
				$situation_percent = 100;
3654
			}
3655
			if (empty($ref_ext)) {
3656
				$ref_ext = '';
3657
			}
3658
3659
			$remise_percent = price2num($remise_percent);
3660
			$qty = price2num($qty);
3661
			$pu_ht = price2num($pu_ht);
3662
			$pu_ht_devise = price2num($pu_ht_devise);
3663
			$pu_ttc = price2num($pu_ttc);
3664
			$pa_ht = price2num($pa_ht);
3665
			if (!preg_match('/\((.*)\)/', $txtva)) {
3666
				$txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
3667
			}
3668
			$txlocaltax1 = price2num($txlocaltax1);
3669
			$txlocaltax2 = price2num($txlocaltax2);
3670
3671
			if ($price_base_type == 'HT') {
3672
				$pu = $pu_ht;
3673
			} else {
3674
				$pu = $pu_ttc;
3675
			}
3676
3677
			// Check parameters
3678
			if ($type < 0) {
3679
				return -1;
3680
			}
3681
3682
			if ($date_start && $date_end && $date_start > $date_end) {
3683
				$langs->load("errors");
3684
				$this->error = $langs->trans('ErrorStartDateGreaterEnd');
3685
				return -1;
3686
			}
3687
3688
			$this->db->begin();
3689
3690
			$product_type = $type;
3691
			if (!empty($fk_product) && $fk_product > 0) {
3692
				$product = new Product($this->db);
3693
				$result = $product->fetch($fk_product);
3694
				$product_type = $product->type;
3695
3696
				if (!empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_INVOICE) && $product_type == 0 && $product->stock_reel < $qty) {
3697
					$langs->load("errors");
3698
					$this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnInvoice', $product->ref);
3699
					$this->db->rollback();
3700
					return -3;
3701
				}
3702
			}
3703
3704
			$localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
3705
3706
			// Clean vat code
3707
			$reg = array();
3708
			$vat_src_code = '';
3709
			if (preg_match('/\((.*)\)/', $txtva, $reg)) {
3710
				$vat_src_code = $reg[1];
3711
				$txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
3712
			}
3713
3714
			// Calcul du total TTC et de la TVA pour la ligne a partir de
3715
			// qty, pu, remise_percent et txtva
3716
			// TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
3717
			// la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
3718
3719
			$tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $product_type, $mysoc, $localtaxes_type, $situation_percent, $this->multicurrency_tx, $pu_ht_devise);
3720
3721
			$total_ht  = $tabprice[0];
3722
			$total_tva = $tabprice[1];
3723
			$total_ttc = $tabprice[2];
3724
			$total_localtax1 = $tabprice[9];
3725
			$total_localtax2 = $tabprice[10];
3726
			$pu_ht = $tabprice[3];
3727
3728
			// MultiCurrency
3729
			$multicurrency_total_ht = $tabprice[16];
3730
			$multicurrency_total_tva = $tabprice[17];
3731
			$multicurrency_total_ttc = $tabprice[18];
3732
			$pu_ht_devise = $tabprice[19];
3733
3734
			// Rank to use
3735
			$ranktouse = $rang;
3736
			if ($ranktouse == -1) {
3737
				$rangmax = $this->line_max($fk_parent_line);
3738
				$ranktouse = $rangmax + 1;
3739
			}
3740
3741
			// Insert line
3742
			$this->line = new FactureLigne($this->db);
3743
3744
			$this->line->context = $this->context;
3745
3746
			$this->line->fk_facture = $this->id;
3747
			$this->line->label = $label; // deprecated
3748
			$this->line->desc = $desc;
3749
			$this->line->ref_ext = $ref_ext;
3750
3751
			$this->line->qty = ($this->type == self::TYPE_CREDIT_NOTE ? abs($qty) : $qty); // For credit note, quantity is always positive and unit price negative
3752
			$this->line->subprice = ($this->type == self::TYPE_CREDIT_NOTE ? -abs($pu_ht) : $pu_ht); // For credit note, unit price always negative, always positive otherwise
3753
3754
			$this->line->vat_src_code = $vat_src_code;
3755
			$this->line->tva_tx = $txtva;
3756
			$this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
3757
			$this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
3758
			$this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
3759
			$this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
3760
3761
			$this->line->total_ht = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($total_ht) : $total_ht); // For credit note and if qty is negative, total is negative
3762
			$this->line->total_ttc = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($total_ttc) : $total_ttc); // For credit note and if qty is negative, total is negative
3763
			$this->line->total_tva = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($total_tva) : $total_tva); // For credit note and if qty is negative, total is negative
3764
			$this->line->total_localtax1 = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($total_localtax1) : $total_localtax1); // For credit note and if qty is negative, total is negative
3765
			$this->line->total_localtax2 = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($total_localtax2) : $total_localtax2); // For credit note and if qty is negative, total is negative
3766
3767
			$this->line->fk_product = $fk_product;
3768
			$this->line->product_type = $product_type;
3769
			$this->line->remise_percent = $remise_percent;
3770
			$this->line->date_start = $date_start;
3771
			$this->line->date_end = $date_end;
3772
			$this->line->ventil = $ventil;
0 ignored issues
show
Bug introduced by
The property ventil does not exist on FactureLigne. Did you mean fk_code_ventilation?
Loading history...
3773
			$this->line->rang = $ranktouse;
3774
			$this->line->info_bits = $info_bits;
3775
			$this->line->fk_remise_except = $fk_remise_except;
3776
3777
			$this->line->special_code = $special_code;
3778
			$this->line->fk_parent_line = $fk_parent_line;
3779
			$this->line->origin = $origin;
3780
			$this->line->origin_id = $origin_id;
3781
			$this->line->situation_percent = $situation_percent;
3782
			$this->line->fk_prev_id = $fk_prev_id;
3783
			$this->line->fk_unit = $fk_unit;
3784
3785
			// infos marge
3786
			$this->line->fk_fournprice = $fk_fournprice;
3787
			$this->line->pa_ht = $pa_ht;
3788
3789
			// Multicurrency
3790
			$this->line->fk_multicurrency = $this->fk_multicurrency;
3791
			$this->line->multicurrency_code = $this->multicurrency_code;
3792
			$this->line->multicurrency_subprice	= ($this->type == self::TYPE_CREDIT_NOTE ? -abs($pu_ht_devise) : $pu_ht_devise); // For credit note, unit price always negative, always positive otherwise
3793
3794
			$this->line->multicurrency_total_ht = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($multicurrency_total_ht) : $multicurrency_total_ht); // For credit note and if qty is negative, total is negative
3795
			$this->line->multicurrency_total_tva = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($multicurrency_total_tva) : $multicurrency_total_tva); // For credit note and if qty is negative, total is negative
3796
			$this->line->multicurrency_total_ttc = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($multicurrency_total_ttc) : $multicurrency_total_ttc); // For credit note and if qty is negative, total is negative
3797
3798
			if (is_array($array_options) && count($array_options) > 0) {
3799
				$this->line->array_options = $array_options;
3800
			}
3801
3802
			$result = $this->line->insert();
3803
			if ($result > 0) {
3804
				// Reorder if child line
3805
				if (!empty($fk_parent_line)) {
3806
					$this->line_order(true, 'DESC');
3807
				} elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) { // Update all rank of all other lines
3808
					$linecount = count($this->lines);
3809
					for ($ii = $ranktouse; $ii <= $linecount; $ii++) {
3810
						$this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
3811
					}
3812
				}
3813
3814
				// Mise a jour informations denormalisees au niveau de la facture meme
3815
				if (empty($noupdateafterinsertline)) {
3816
					$result = $this->update_price(1, 'auto', 0, $mysoc); // The addline method is designed to add line from user input so total calculation with update_price must be done using 'auto' mode.
3817
				}
3818
3819
				if ($result > 0) {
3820
					$this->db->commit();
3821
					return $this->line->id;
3822
				} else {
3823
					$this->error = $this->db->lasterror();
3824
					$this->db->rollback();
3825
					return -1;
3826
				}
3827
			} else {
3828
				$this->error = $this->line->error;
3829
				$this->errors = $this->line->errors;
3830
				$this->db->rollback();
3831
				return -2;
3832
			}
3833
		} else {
3834
			$this->errors[]='status of invoice must be Draft to allow use of ->addline()';
3835
			dol_syslog(get_class($this)."::addline status of invoice must be Draft to allow use of ->addline()", LOG_ERR);
3836
			return -3;
3837
		}
3838
	}
3839
3840
	/**
3841
	 *  Update a detail line
3842
	 *
3843
	 *  @param     	int			$rowid           	Id of line to update
3844
	 *  @param     	string		$desc            	Description of line
3845
	 *  @param     	double		$pu              	Prix unitaire (HT ou TTC selon price_base_type) (> 0 even for credit note lines)
3846
	 *  @param     	double		$qty             	Quantity
3847
	 *  @param     	double		$remise_percent  	Percentage discount of the line
3848
	 *  @param     	int		    $date_start      	Date de debut de validite du service
3849
	 *  @param     	int		    $date_end        	Date de fin de validite du service
3850
	 *  @param     	double		$txtva          	VAT Rate (Can be '8.5', '8.5 (ABC)')
3851
	 * 	@param		double		$txlocaltax1		Local tax 1 rate
3852
	 *  @param		double		$txlocaltax2		Local tax 2 rate
3853
	 * 	@param     	string		$price_base_type 	HT or TTC
3854
	 * 	@param     	int			$info_bits 		    Miscellaneous informations
3855
	 * 	@param		int			$type				Type of line (0=product, 1=service)
3856
	 * 	@param		int			$fk_parent_line		Id of parent line (0 in most cases, used by modules adding sublevels into lines).
3857
	 * 	@param		int			$skip_update_total	Keep fields total_xxx to 0 (used for special lines by some modules)
3858
	 * 	@param		int			$fk_fournprice		Id of origin supplier price
3859
	 * 	@param		int			$pa_ht				Price (without tax) of product when it was bought
3860
	 * 	@param		string		$label				Label of the line (deprecated, do not use)
3861
	 * 	@param		int			$special_code		Special code (also used by externals modules!)
3862
	 *  @param		array		$array_options		extrafields array
3863
	 * 	@param      int         $situation_percent  Situation advance percentage
3864
	 * 	@param 		string		$fk_unit 			Code of the unit to use. Null to use the default one
3865
	 * 	@param		double		$pu_ht_devise		Unit price in currency
3866
	 * 	@param		int			$notrigger			disable line update trigger
3867
	 *  @param		string		$ref_ext		    External reference of the line
3868
	 *  @param		integer		$rang		    	rank of line
3869
	 *  @return    	int             				< 0 if KO, > 0 if OK
3870
	 */
3871
	public function updateline($rowid, $desc, $pu, $qty, $remise_percent, $date_start, $date_end, $txtva, $txlocaltax1 = 0, $txlocaltax2 = 0, $price_base_type = 'HT', $info_bits = 0, $type = self::TYPE_STANDARD, $fk_parent_line = 0, $skip_update_total = 0, $fk_fournprice = null, $pa_ht = 0, $label = '', $special_code = 0, $array_options = 0, $situation_percent = 100, $fk_unit = null, $pu_ht_devise = 0, $notrigger = 0, $ref_ext = '', $rang = 0)
3872
	{
3873
		global $conf, $user;
3874
		// Deprecation warning
3875
		if ($label) {
3876
			dol_syslog(__METHOD__.": using line label is deprecated", LOG_WARNING);
3877
		}
3878
3879
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
3880
3881
		global $mysoc, $langs;
3882
3883
		dol_syslog(get_class($this)."::updateline rowid=$rowid, desc=$desc, pu=$pu, qty=$qty, remise_percent=$remise_percent, date_start=$date_start, date_end=$date_end, txtva=$txtva, txlocaltax1=$txlocaltax1, txlocaltax2=$txlocaltax2, price_base_type=$price_base_type, info_bits=$info_bits, type=$type, fk_parent_line=$fk_parent_line pa_ht=$pa_ht, special_code=$special_code, fk_unit=$fk_unit, pu_ht_devise=$pu_ht_devise", LOG_DEBUG);
3884
3885
		if ($this->statut == self::STATUS_DRAFT) {
3886
			if (!$this->is_last_in_cycle() && empty($this->error)) {
3887
				if (!$this->checkProgressLine($rowid, $situation_percent)) {
3888
					if (!$this->error) {
3889
						$this->error = $langs->trans('invoiceLineProgressError');
3890
					}
3891
					return -3;
3892
				}
3893
			}
3894
3895
			if ($date_start && $date_end && $date_start > $date_end) {
3896
				$langs->load("errors");
3897
				$this->error = $langs->trans('ErrorStartDateGreaterEnd');
3898
				return -1;
3899
			}
3900
3901
			$this->db->begin();
3902
3903
			// Clean parameters
3904
			if (empty($qty)) {
3905
				$qty = 0;
3906
			}
3907
			if (empty($fk_parent_line) || $fk_parent_line < 0) {
3908
				$fk_parent_line = 0;
3909
			}
3910
			if (empty($special_code) || $special_code == 3) {
3911
				$special_code = 0;
3912
			}
3913
			if (!isset($situation_percent) || $situation_percent > 100 || (string) $situation_percent == '') {
3914
				$situation_percent = 100;
3915
			}
3916
			if (empty($ref_ext)) {
3917
				$ref_ext = '';
3918
			}
3919
3920
			$remise_percent = price2num($remise_percent);
3921
			$qty			= price2num($qty);
3922
			$pu 			= price2num($pu);
3923
			$pu_ht_devise = price2num($pu_ht_devise);
3924
			$pa_ht = price2num($pa_ht);
3925
			if (!preg_match('/\((.*)\)/', $txtva)) {
3926
				$txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
3927
			}
3928
			$txlocaltax1	= price2num($txlocaltax1);
3929
			$txlocaltax2	= price2num($txlocaltax2);
3930
3931
			// Check parameters
3932
			if ($type < 0) {
3933
				return -1;
3934
			}
3935
3936
			// Calculate total with, without tax and tax from qty, pu, remise_percent and txtva
3937
			// TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
3938
			// la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
3939
3940
			$localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
3941
3942
			// Clean vat code
3943
			$reg = array();
3944
			$vat_src_code = '';
3945
			if (preg_match('/\((.*)\)/', $txtva, $reg)) {
3946
				$vat_src_code = $reg[1];
3947
				$txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
3948
			}
3949
3950
			$tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $mysoc, $localtaxes_type, $situation_percent, $this->multicurrency_tx, $pu_ht_devise);
3951
3952
			$total_ht  = $tabprice[0];
3953
			$total_tva = $tabprice[1];
3954
			$total_ttc = $tabprice[2];
3955
			$total_localtax1 = $tabprice[9];
3956
			$total_localtax2 = $tabprice[10];
3957
			$pu_ht  = $tabprice[3];
3958
			$pu_tva = $tabprice[4];
3959
			$pu_ttc = $tabprice[5];
3960
3961
			// MultiCurrency
3962
			$multicurrency_total_ht = $tabprice[16];
3963
			$multicurrency_total_tva = $tabprice[17];
3964
			$multicurrency_total_ttc = $tabprice[18];
3965
			$pu_ht_devise = $tabprice[19];
3966
3967
			// Old properties: $price, $remise (deprecated)
3968
			$price = $pu;
3969
			$remise = 0;
3970
			if ($remise_percent > 0) {
3971
				$remise = round(($pu * $remise_percent / 100), 2);
3972
				$price = ($pu - $remise);
3973
			}
3974
			$price = price2num($price);
3975
3976
			//Fetch current line from the database and then clone the object and set it in $oldline property
3977
			$line = new FactureLigne($this->db);
3978
			$line->fetch($rowid);
3979
			$line->fetch_optionals();
3980
3981
			if (!empty($line->fk_product)) {
3982
				$product = new Product($this->db);
3983
				$result = $product->fetch($line->fk_product);
3984
				$product_type = $product->type;
3985
3986
				if (!empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_INVOICE) && $product_type == 0 && $product->stock_reel < $qty) {
3987
					$langs->load("errors");
3988
					$this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnInvoice', $product->ref);
3989
					$this->db->rollback();
3990
					return -3;
3991
				}
3992
			}
3993
3994
			$staticline = clone $line;
3995
3996
			$line->oldline = $staticline;
3997
			$this->line = $line;
3998
			$this->line->context = $this->context;
3999
			$this->line->rang = $rang;
4000
4001
			// Reorder if fk_parent_line change
4002
			if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) {
4003
				$rangmax = $this->line_max($fk_parent_line);
4004
				$this->line->rang = $rangmax + 1;
4005
			}
4006
4007
			$this->line->id = $rowid;
4008
			$this->line->rowid = $rowid;
4009
			$this->line->label = $label;
4010
			$this->line->desc = $desc;
4011
			$this->line->ref_ext = $ref_ext;
4012
			$this->line->qty = ($this->type == self::TYPE_CREDIT_NOTE ?abs($qty) : $qty); // For credit note, quantity is always positive and unit price negative
4013
4014
			$this->line->vat_src_code = $vat_src_code;
4015
			$this->line->tva_tx = $txtva;
4016
			$this->line->localtax1_tx		= $txlocaltax1;
4017
			$this->line->localtax2_tx		= $txlocaltax2;
4018
			$this->line->localtax1_type		= empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
4019
			$this->line->localtax2_type		= empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
4020
4021
			$this->line->remise_percent		= $remise_percent;
4022
			$this->line->subprice			= ($this->type == self::TYPE_CREDIT_NOTE ?-abs($pu_ht) : $pu_ht); // For credit note, unit price always negative, always positive otherwise
4023
			$this->line->date_start = $date_start;
4024
			$this->line->date_end			= $date_end;
4025
			$this->line->total_ht			= (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ?-abs($total_ht) : $total_ht); // For credit note and if qty is negative, total is negative
4026
			$this->line->total_tva			= (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ?-abs($total_tva) : $total_tva);
4027
			$this->line->total_localtax1	= $total_localtax1;
4028
			$this->line->total_localtax2	= $total_localtax2;
4029
			$this->line->total_ttc			= (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ?-abs($total_ttc) : $total_ttc);
4030
			$this->line->info_bits			= $info_bits;
4031
			$this->line->special_code		= $special_code;
4032
			$this->line->product_type		= $type;
4033
			$this->line->fk_parent_line = $fk_parent_line;
4034
			$this->line->skip_update_total = $skip_update_total;
4035
			$this->line->situation_percent = $situation_percent;
4036
			$this->line->fk_unit = $fk_unit;
4037
4038
			$this->line->fk_fournprice = $fk_fournprice;
4039
			$this->line->pa_ht = $pa_ht;
4040
4041
			// Multicurrency
4042
			$this->line->multicurrency_subprice		= ($this->type == self::TYPE_CREDIT_NOTE ?-abs($pu_ht_devise) : $pu_ht_devise); // For credit note, unit price always negative, always positive otherwise
4043
			$this->line->multicurrency_total_ht 	= (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ?-abs($multicurrency_total_ht) : $multicurrency_total_ht); // For credit note and if qty is negative, total is negative
4044
			$this->line->multicurrency_total_tva 	= (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ?-abs($multicurrency_total_tva) : $multicurrency_total_tva);
4045
			$this->line->multicurrency_total_ttc 	= (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ?-abs($multicurrency_total_ttc) : $multicurrency_total_ttc);
4046
4047
			if (is_array($array_options) && count($array_options) > 0) {
4048
				// We replace values in this->line->array_options only for entries defined into $array_options
4049
				foreach ($array_options as $key => $value) {
4050
					$this->line->array_options[$key] = $array_options[$key];
4051
				}
4052
			}
4053
4054
			$result = $this->line->update($user, $notrigger);
4055
			if ($result > 0) {
4056
				// Reorder if child line
4057
				if (!empty($fk_parent_line)) {
4058
					$this->line_order(true, 'DESC');
4059
				}
4060
4061
				// Mise a jour info denormalisees au niveau facture
4062
				$this->update_price(1);
4063
				$this->db->commit();
4064
				return $result;
4065
			} else {
4066
				$this->error = $this->line->error;
4067
				$this->db->rollback();
4068
				return -1;
4069
			}
4070
		} else {
4071
			$this->error = "Invoice statut makes operation forbidden";
4072
			return -2;
4073
		}
4074
	}
4075
4076
	/**
4077
	 * Check if the percent edited is lower of next invoice line
4078
	 *
4079
	 * @param	int		$idline				id of line to check
4080
	 * @param	float	$situation_percent	progress percentage need to be test
4081
	 * @return 	bool						false if KO, true if OK
4082
	 */
4083
	public function checkProgressLine($idline, $situation_percent)
4084
	{
4085
		$sql = 'SELECT fd.situation_percent FROM '.MAIN_DB_PREFIX.'facturedet fd
4086
				INNER JOIN '.MAIN_DB_PREFIX.'facture f ON (fd.fk_facture = f.rowid)
4087
				WHERE fd.fk_prev_id = '.((int) $idline).' AND f.fk_statut <> 0';
4088
4089
		$result = $this->db->query($sql);
4090
		if (!$result) {
4091
			$this->error = $this->db->error();
4092
			return false;
4093
		}
4094
4095
		$obj = $this->db->fetch_object($result);
4096
4097
		if ($obj === null) {
4098
			return true;
4099
		} else {
4100
			return ($situation_percent < $obj->situation_percent);
4101
		}
4102
	}
4103
4104
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4105
	/**
4106
	 * Update invoice line with percentage
4107
	 *
4108
	 * @param  FactureLigne $line       	Invoice line
4109
	 * @param  int          $percent    	Percentage
4110
	 * @param  boolean      $update_price   Update object price
4111
	 * @return void
4112
	 */
4113
	public function update_percent($line, $percent, $update_price = true)
4114
	{
4115
		// phpcs:enable
4116
		global $mysoc, $user;
4117
4118
		// Progress should never be changed for discount lines
4119
		if (($line->info_bits & 2) == 2) {
4120
			return;
4121
		}
4122
4123
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
4124
4125
		// Cap percentages to 100
4126
		if ($percent > 100) {
4127
			$percent = 100;
4128
		}
4129
		$line->situation_percent = $percent;
4130
		$tabprice = calcul_price_total($line->qty, $line->subprice, $line->remise_percent, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 0, 'HT', 0, $line->product_type, $mysoc, '', $percent);
4131
		$line->total_ht = $tabprice[0];
4132
		$line->total_tva = $tabprice[1];
4133
		$line->total_ttc = $tabprice[2];
4134
		$line->total_localtax1 = $tabprice[9];
4135
		$line->total_localtax2 = $tabprice[10];
4136
		$line->multicurrency_total_ht  = $tabprice[16];
4137
		$line->multicurrency_total_tva = $tabprice[17];
4138
		$line->multicurrency_total_ttc = $tabprice[18];
4139
		$line->update($user);
4140
4141
		// sometimes it is better to not update price for each line, ie when updating situation on all lines
4142
		if ($update_price) {
4143
			$this->update_price(1);
4144
		}
4145
	}
4146
4147
	/**
4148
	 *	Delete line in database
4149
	 *
4150
	 *	@param		int		$rowid		Id of line to delete
4151
	 *  @param		int		$id			Id of object (for a check)
4152
	 *	@return		int					<0 if KO, >0 if OK
4153
	 */
4154
	public function deleteline($rowid, $id = 0)
4155
	{
4156
		global $user;
4157
4158
		dol_syslog(get_class($this)."::deleteline rowid=".((int) $rowid), LOG_DEBUG);
4159
4160
		if ($this->statut != self::STATUS_DRAFT) {
4161
			$this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
4162
			return -1;
4163
		}
4164
4165
		$line = new FactureLigne($this->db);
4166
4167
		$line->context = $this->context;
4168
4169
		// Load line
4170
		$result = $line->fetch($rowid);
4171
		if (!($result > 0)) {
4172
			dol_print_error($this->db, $line->error, $line->errors);
4173
			return -1;
4174
		}
4175
4176
		if ($id > 0 && $line->fk_facture != $id) {
4177
			$this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
4178
			return -1;
4179
		}
4180
4181
		$this->db->begin();
4182
4183
		// Free discount linked to invoice line
4184
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
4185
		$sql .= ' SET fk_facture_line = NULL';
4186
		$sql .= ' WHERE fk_facture_line = '.((int) $rowid);
4187
4188
		dol_syslog(get_class($this)."::deleteline", LOG_DEBUG);
4189
		$result = $this->db->query($sql);
4190
		if (!$result) {
4191
			$this->error = $this->db->error();
4192
			$this->db->rollback();
4193
			return -1;
4194
		}
4195
4196
		// Memorize previous line for triggers
4197
		$staticline = clone $line;
4198
		$line->oldline = $staticline;
4199
4200
		if ($line->delete($user) > 0) {
4201
			$result = $this->update_price(1);
4202
4203
			if ($result > 0) {
4204
				$this->db->commit();
4205
				return 1;
4206
			} else {
4207
				$this->db->rollback();
4208
				$this->error = $this->db->lasterror();
4209
				return -1;
4210
			}
4211
		} else {
4212
			$this->db->rollback();
4213
			$this->error = $line->error;
4214
			return -1;
4215
		}
4216
	}
4217
4218
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4219
	/**
4220
	 *	Set percent discount
4221
	 *
4222
	 *  @deprecated
4223
	 *  @see setDiscount()
4224
	 *	@param     	User	$user		User that set discount
4225
	 *	@param     	double	$remise		Discount
4226
	 *  @param     	int		$notrigger	1=Does not execute triggers, 0= execute triggers
4227
	 *	@return		int 				<0 if KO, >0 if OK
4228
	 */
4229
	public function set_remise($user, $remise, $notrigger = 0)
4230
	{
4231
		// phpcs:enable
4232
		dol_syslog(get_class($this)."::set_remise is deprecated, use setDiscount instead", LOG_NOTICE);
4233
		return $this->setDiscount($user, $remise, $notrigger);
4234
	}
4235
4236
	/**
4237
	 *	Set percent discount
4238
	 *
4239
	 *	@param     	User	$user		User that set discount
4240
	 *	@param     	double	$remise		Discount
4241
	 *  @param     	int		$notrigger	1=Does not execute triggers, 0= execute triggers
4242
	 *	@return		int 				<0 if KO, >0 if OK
4243
	 */
4244
	public function setDiscount($user, $remise, $notrigger = 0)
4245
	{
4246
		// Clean parameters
4247
		if (empty($remise)) {
4248
			$remise = 0;
4249
		}
4250
4251
		if ($user->rights->facture->creer) {
4252
			$remise = price2num($remise, 2);
4253
4254
			$error = 0;
4255
4256
			$this->db->begin();
4257
4258
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
4259
			$sql .= ' SET remise_percent = '.((float) $remise);
4260
			$sql .= " WHERE rowid = ".((int) $this->id);
4261
			$sql .= ' AND fk_statut = '.self::STATUS_DRAFT;
4262
4263
			dol_syslog(__METHOD__, LOG_DEBUG);
4264
			$resql = $this->db->query($sql);
4265
			if (!$resql) {
4266
				$this->errors[] = $this->db->error();
4267
				$error++;
4268
			}
4269
4270
			if (!$notrigger && empty($error)) {
4271
				// Call trigger
4272
				$result = $this->call_trigger('BILL_MODIFY', $user);
4273
				if ($result < 0) {
4274
					$error++;
4275
				}
4276
				// End call triggers
4277
			}
4278
4279
			if (!$error) {
4280
				$this->remise_percent = $remise;
4281
				$this->update_price(1);
4282
4283
				$this->db->commit();
4284
				return 1;
4285
			} else {
4286
				foreach ($this->errors as $errmsg) {
4287
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
4288
					$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
4289
				}
4290
				$this->db->rollback();
4291
				return -1 * $error;
4292
			}
4293
		}
4294
4295
		return 0;
4296
	}
4297
4298
4299
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4300
	/**
4301
	 *	Set absolute discount
4302
	 *
4303
	 *	@param     	User	$user 		User that set discount
4304
	 *	@param     	double	$remise		Discount
4305
	 *  @param     	int		$notrigger	1=Does not execute triggers, 0= execute triggers
4306
	 *	@return		int 				<0 if KO, >0 if OK
4307
	 */
4308
	public function set_remise_absolue($user, $remise, $notrigger = 0)
4309
	{
4310
		// phpcs:enable
4311
		if (empty($remise)) {
4312
			$remise = 0;
4313
		}
4314
4315
		if ($user->rights->facture->creer) {
4316
			$error = 0;
4317
4318
			$this->db->begin();
4319
4320
			$remise = price2num($remise);
4321
4322
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
4323
			$sql .= ' SET remise_absolue = '.((float) $remise);
4324
			$sql .= " WHERE rowid = ".((int) $this->id);
4325
			$sql .= ' AND fk_statut = '.self::STATUS_DRAFT;
4326
4327
			dol_syslog(__METHOD__, LOG_DEBUG);
4328
			$resql = $this->db->query($sql);
4329
			if (!$resql) {
4330
				$this->errors[] = $this->db->error();
4331
				$error++;
4332
			}
4333
4334
			if (!$error) {
4335
				$this->oldcopy = clone $this;
4336
				$this->remise_absolue = $remise;
4337
				$this->update_price(1);
4338
			}
4339
4340
			if (!$notrigger && empty($error)) {
4341
				// Call trigger
4342
				$result = $this->call_trigger('BILL_MODIFY', $user);
4343
				if ($result < 0) {
4344
					$error++;
4345
				}
4346
				// End call triggers
4347
			}
4348
4349
			if (!$error) {
4350
				$this->db->commit();
4351
				return 1;
4352
			} else {
4353
				foreach ($this->errors as $errmsg) {
4354
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
4355
					$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
4356
				}
4357
				$this->db->rollback();
4358
				return -1 * $error;
4359
			}
4360
		}
4361
4362
		return 0;
4363
	}
4364
4365
	/**
4366
	 *      Return next reference of customer invoice not already used (or last reference)
4367
	 *      according to numbering module defined into constant FACTURE_ADDON
4368
	 *
4369
	 *      @param	   Societe		$soc		object company
4370
	 *      @param     string		$mode		'next' for next value or 'last' for last value
4371
	 *      @return    string					free ref or last ref
4372
	 */
4373
	public function getNextNumRef($soc, $mode = 'next')
4374
	{
4375
		global $conf, $langs;
4376
4377
		if ($this->module_source == 'takepos') {
4378
			$langs->load('cashdesk');
4379
4380
			$moduleName = 'takepos';
4381
			$moduleSourceName = 'Takepos';
4382
			$addonConstName = 'TAKEPOS_REF_ADDON';
4383
4384
			// Clean parameters (if not defined or using deprecated value)
4385
			if (empty($conf->global->TAKEPOS_REF_ADDON)) {
4386
				$conf->global->TAKEPOS_REF_ADDON = 'mod_takepos_ref_simple';
4387
			}
4388
4389
			$addon = $conf->global->TAKEPOS_REF_ADDON;
4390
		} else {
4391
			$langs->load('bills');
4392
4393
			$moduleName = 'facture';
4394
			$moduleSourceName = 'Invoice';
4395
			$addonConstName = 'FACTURE_ADDON';
4396
4397
			// Clean parameters (if not defined or using deprecated value)
4398
			if (empty($conf->global->FACTURE_ADDON)) {
4399
				$conf->global->FACTURE_ADDON = 'mod_facture_terre';
4400
			} elseif ($conf->global->FACTURE_ADDON == 'terre') {
4401
				$conf->global->FACTURE_ADDON = 'mod_facture_terre';
4402
			} elseif ($conf->global->FACTURE_ADDON == 'mercure') {
4403
				$conf->global->FACTURE_ADDON = 'mod_facture_mercure';
4404
			}
4405
4406
			$addon = $conf->global->FACTURE_ADDON;
4407
		}
4408
4409
		if (!empty($addon)) {
4410
			dol_syslog("Call getNextNumRef with ".$addonConstName." = ".$conf->global->FACTURE_ADDON.", thirdparty=".$soc->name.", type=".$soc->typent_code.", mode=".$mode, LOG_DEBUG);
4411
4412
			$mybool = false;
4413
4414
			$file = $addon.'.php';
4415
			$classname = $addon;
4416
4417
4418
			// Include file with class
4419
			$dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
4420
			foreach ($dirmodels as $reldir) {
4421
				$dir = dol_buildpath($reldir.'core/modules/'.$moduleName.'/');
4422
4423
				// Load file with numbering class (if found)
4424
				if (is_file($dir.$file) && is_readable($dir.$file)) {
4425
					$mybool |= include_once $dir.$file;
4426
				}
4427
			}
4428
4429
			// For compatibility
4430
			if (!$mybool) {
4431
				$file = $addon.'/'.$addon.'.modules.php';
4432
				$classname = 'mod_'.$moduleName.'_'.$addon;
4433
				$classname = preg_replace('/\-.*$/', '', $classname);
4434
				// Include file with class
4435
				foreach ($conf->file->dol_document_root as $dirroot) {
4436
					$dir = $dirroot.'/core/modules/'.$moduleName.'/';
4437
4438
					// Load file with numbering class (if found)
4439
					if (is_file($dir.$file) && is_readable($dir.$file)) {
4440
						$mybool |= include_once $dir.$file;
4441
					}
4442
				}
4443
			}
4444
4445
			if (!$mybool) {
4446
				dol_print_error('', 'Failed to include file '.$file);
4447
				return '';
4448
			}
4449
4450
			$obj = new $classname();
4451
4452
			$numref = $obj->getNextValue($soc, $this, $mode);
4453
4454
4455
			/**
4456
			 * $numref can be empty in case we ask for the last value because if there is no invoice created with the
4457
			 * set up mask.
4458
			 */
4459
			if ($mode != 'last' && !$numref) {
4460
				$this->error = $obj->error;
4461
				return '';
4462
			}
4463
4464
			return $numref;
4465
		} else {
4466
			$langs->load('errors');
4467
			print $langs->trans('Error').' '.$langs->trans('ErrorModuleSetupNotComplete', $langs->transnoentitiesnoconv($moduleSourceName));
4468
			return '';
4469
		}
4470
	}
4471
4472
	/**
4473
	 *	Load miscellaneous information for tab "Info"
4474
	 *
4475
	 *	@param  int		$id		Id of object to load
4476
	 *	@return	void
4477
	 */
4478
	public function info($id)
4479
	{
4480
		$sql = 'SELECT c.rowid, datec, date_valid as datev, tms as datem,';
4481
		$sql .= ' date_closing as dateclosing,';
4482
		$sql .= ' fk_user_author, fk_user_valid, fk_user_closing';
4483
		$sql .= ' FROM '.MAIN_DB_PREFIX.'facture as c';
4484
		$sql .= ' WHERE c.rowid = '.((int) $id);
4485
4486
		$result = $this->db->query($sql);
4487
		if ($result) {
4488
			if ($this->db->num_rows($result)) {
4489
				$obj = $this->db->fetch_object($result);
4490
				$this->id = $obj->rowid;
4491
				if ($obj->fk_user_author) {
4492
					$cuser = new User($this->db);
4493
					$cuser->fetch($obj->fk_user_author);
4494
					$this->user_creation = $cuser;
4495
				}
4496
				if ($obj->fk_user_valid) {
4497
					$vuser = new User($this->db);
4498
					$vuser->fetch($obj->fk_user_valid);
4499
					$this->user_validation = $vuser;
4500
				}
4501
				if ($obj->fk_user_closing) {
4502
					$cluser = new User($this->db);
4503
					$cluser->fetch($obj->fk_user_closing);
4504
					$this->user_closing = $cluser;
4505
				}
4506
4507
				$this->date_creation     = $this->db->jdate($obj->datec);
4508
				$this->date_modification = $this->db->jdate($obj->datem);
4509
				$this->date_validation   = $this->db->jdate($obj->datev);
4510
				$this->date_closing      = $this->db->jdate($obj->dateclosing);
4511
			}
4512
			$this->db->free($result);
4513
		} else {
4514
			dol_print_error($this->db);
4515
		}
4516
	}
4517
4518
4519
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4520
	/**
4521
	 *  Return list of invoices (eventually filtered on a user) into an array
4522
	 *
4523
	 *  @param		int		$shortlist		0=Return array[id]=ref, 1=Return array[](id=>id,ref=>ref,name=>name)
4524
	 *  @param      int		$draft      	0=not draft, 1=draft
4525
	 *  @param      User	$excluser      	Objet user to exclude
4526
	 *  @param    	int		$socid			Id third pary
4527
	 *  @param    	int		$limit			For pagination
4528
	 *  @param    	int		$offset			For pagination
4529
	 *  @param    	string	$sortfield		Sort criteria
4530
	 *  @param    	string	$sortorder		Sort order
4531
	 *  @return     array|int             	-1 if KO, array with result if OK
4532
	 */
4533
	public function liste_array($shortlist = 0, $draft = 0, $excluser = '', $socid = 0, $limit = 0, $offset = 0, $sortfield = 'f.datef,f.rowid', $sortorder = 'DESC')
4534
	{
4535
		// phpcs:enable
4536
		global $conf, $user;
4537
4538
		$ga = array();
4539
4540
		$sql = "SELECT s.rowid, s.nom as name, s.client,";
4541
		$sql .= " f.rowid as fid, f.ref as ref, f.datef as df";
4542
		if (empty($user->rights->societe->client->voir) && !$socid) {
4543
			$sql .= ", sc.fk_soc, sc.fk_user";
4544
		}
4545
		$sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."facture as f";
4546
		if (empty($user->rights->societe->client->voir) && !$socid) {
4547
			$sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
4548
		}
4549
		$sql .= " WHERE f.entity IN (".getEntity('invoice').")";
4550
		$sql .= " AND f.fk_soc = s.rowid";
4551
		if (empty($user->rights->societe->client->voir) && !$socid) { //restriction
4552
			$sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
4553
		}
4554
		if ($socid) {
4555
			$sql .= " AND s.rowid = ".((int) $socid);
4556
		}
4557
		if ($draft) {
4558
			$sql .= " AND f.fk_statut = ".self::STATUS_DRAFT;
4559
		}
4560
		if (is_object($excluser)) {
4561
			$sql .= " AND f.fk_user_author <> ".((int) $excluser->id);
4562
		}
4563
		$sql .= $this->db->order($sortfield, $sortorder);
4564
		$sql .= $this->db->plimit($limit, $offset);
4565
4566
		$result = $this->db->query($sql);
4567
		if ($result) {
4568
			$numc = $this->db->num_rows($result);
4569
			if ($numc) {
4570
				$i = 0;
4571
				while ($i < $numc) {
4572
					$obj = $this->db->fetch_object($result);
4573
4574
					if ($shortlist == 1) {
4575
						$ga[$obj->fid] = $obj->ref;
4576
					} elseif ($shortlist == 2) {
4577
						$ga[$obj->fid] = $obj->ref.' ('.$obj->name.')';
4578
					} else {
4579
						$ga[$i]['id'] = $obj->fid;
4580
						$ga[$i]['ref'] 	= $obj->ref;
4581
						$ga[$i]['name'] = $obj->name;
4582
					}
4583
					$i++;
4584
				}
4585
			}
4586
			return $ga;
4587
		} else {
4588
			dol_print_error($this->db);
4589
			return -1;
4590
		}
4591
	}
4592
4593
4594
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4595
	/**
4596
	 *	Return list of invoices qualified to be replaced by another invoice.
4597
	 *	Invoices matching the following rules are returned:
4598
	 *	(Status validated or abandonned for a reason 'other') + not payed + no payment at all + not already replaced
4599
	 *
4600
	 *	@param		int			$socid		Id thirdparty
4601
	 *	@return    	array|int				Array of invoices ('id'=>id, 'ref'=>ref, 'status'=>status, 'paymentornot'=>0/1)
4602
	 */
4603
	public function list_replacable_invoices($socid = 0)
4604
	{
4605
		// phpcs:enable
4606
		global $conf;
4607
4608
		$return = array();
4609
4610
		$sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut as status, f.paye as paid,";
4611
		$sql .= " ff.rowid as rowidnext";
4612
		//$sql .= ", SUM(pf.amount) as alreadypaid";
4613
		$sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
4614
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."paiement_facture as pf ON f.rowid = pf.fk_facture";
4615
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facture as ff ON f.rowid = ff.fk_facture_source";
4616
		$sql .= " WHERE (f.fk_statut = ".self::STATUS_VALIDATED." OR (f.fk_statut = ".self::STATUS_ABANDONED." AND f.close_code = '".self::CLOSECODE_ABANDONED."'))";
4617
		$sql .= " AND f.entity IN (".getEntity('invoice').")";
4618
		$sql .= " AND f.paye = 0"; // Not paid completely
4619
		$sql .= " AND pf.fk_paiement IS NULL"; // No payment already done
4620
		$sql .= " AND ff.fk_statut IS NULL"; // Return true if it is not a replacement invoice
4621
		if ($socid > 0) {
4622
			$sql .= " AND f.fk_soc = ".((int) $socid);
4623
		}
4624
		//$sql .= " GROUP BY f.rowid, f.ref, f.fk_statut, f.paye, ff.rowid";
4625
		$sql .= " ORDER BY f.ref";
4626
4627
		dol_syslog(get_class($this)."::list_replacable_invoices", LOG_DEBUG);
4628
		$resql = $this->db->query($sql);
4629
		if ($resql) {
4630
			while ($obj = $this->db->fetch_object($resql)) {
4631
				$return[$obj->rowid] = array(
4632
					'id' => $obj->rowid,
4633
					'ref' => $obj->ref,
4634
					'status' => $obj->status,
4635
					'paid' => $obj->paid,
4636
					'alreadypaid' => 0
4637
				);
4638
			}
4639
			//print_r($return);
4640
			return $return;
4641
		} else {
4642
			$this->error = $this->db->error();
4643
			return -1;
4644
		}
4645
	}
4646
4647
4648
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4649
	/**
4650
	 *	Return list of invoices qualified to be corrected by a credit note.
4651
	 *	Invoices matching the following rules are returned:
4652
	 *	(validated + payment on process) or classified (payed completely or payed partiely) + not already replaced + not already a credit note
4653
	 *
4654
	 *	@param		int			$socid		Id thirdparty
4655
	 *	@return    	array|int				Array of invoices ($id => array('ref'=>,'paymentornot'=>,'status'=>,'paye'=>)
4656
	 */
4657
	public function list_qualified_avoir_invoices($socid = 0)
4658
	{
4659
		// phpcs:enable
4660
		global $conf;
4661
4662
		$return = array();
4663
4664
4665
		$sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut, f.type, f.paye, pf.fk_paiement";
4666
		$sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
4667
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."paiement_facture as pf ON f.rowid = pf.fk_facture";
4668
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facture as ff ON (f.rowid = ff.fk_facture_source AND ff.type=".self::TYPE_REPLACEMENT.")";
4669
		$sql .= " WHERE f.entity IN (".getEntity('invoice').")";
4670
		$sql .= " AND f.fk_statut in (".self::STATUS_VALIDATED.",".self::STATUS_CLOSED.")";
4671
		//  $sql.= " WHERE f.fk_statut >= 1";
4672
		//	$sql.= " AND (f.paye = 1";				// Classee payee completement
4673
		//	$sql.= " OR f.close_code IS NOT NULL)";	// Classee payee partiellement
4674
		$sql .= " AND ff.type IS NULL"; // Renvoi vrai si pas facture de remplacement
4675
		$sql .= " AND f.type <> ".self::TYPE_CREDIT_NOTE; // Exclude credit note invoices from selection
4676
4677
		if (!empty($conf->global->INVOICE_USE_SITUATION_CREDIT_NOTE)) {
4678
			// Keep invoices that are not situation invoices or that are the last in serie if it is a situation invoice
4679
			$sql .= " AND (f.type <> ".self::TYPE_SITUATION." OR f.rowid IN ";
4680
			$sql .= '(SELECT MAX(fs.rowid)'; // This select returns several ID becasue of the group by later
4681
			$sql .= " FROM ".MAIN_DB_PREFIX."facture as fs";
4682
			$sql .= " WHERE fs.entity IN (".getEntity('invoice').")";
4683
			$sql .= " AND fs.type = ".self::TYPE_SITUATION;
4684
			$sql .= " AND fs.fk_statut IN (".self::STATUS_VALIDATED.",".self::STATUS_CLOSED.")";
4685
			if ($socid > 0) {
4686
				$sql .= " AND fs.fk_soc = ".((int) $socid);
4687
			}
4688
			$sql .= " GROUP BY fs.situation_cycle_ref)"; // For each situation_cycle_ref, we take the higher rowid
4689
			$sql .= ")";
4690
		} else {
4691
			$sql .= " AND f.type <> ".self::TYPE_SITUATION; // Keep invoices that are not situation invoices
4692
		}
4693
4694
		if ($socid > 0) {
4695
			$sql .= " AND f.fk_soc = ".((int) $socid);
4696
		}
4697
		$sql .= " ORDER BY f.ref";
4698
4699
		dol_syslog(get_class($this)."::list_qualified_avoir_invoices", LOG_DEBUG);
4700
		$resql = $this->db->query($sql);
4701
		if ($resql) {
4702
			while ($obj = $this->db->fetch_object($resql)) {
4703
				$qualified = 0;
4704
				if ($obj->fk_statut == self::STATUS_VALIDATED) {
4705
					$qualified = 1;
4706
				}
4707
				if ($obj->fk_statut == self::STATUS_CLOSED) {
4708
					$qualified = 1;
4709
				}
4710
				if ($qualified) {
4711
					//$ref=$obj->ref;
4712
					$paymentornot = ($obj->fk_paiement ? 1 : 0);
4713
					$return[$obj->rowid] = array('ref'=>$obj->ref, 'status'=>$obj->fk_statut, 'type'=>$obj->type, 'paye'=>$obj->paye, 'paymentornot'=>$paymentornot);
4714
				}
4715
			}
4716
4717
			return $return;
4718
		} else {
4719
			$this->error = $this->db->error();
4720
			return -1;
4721
		}
4722
	}
4723
4724
4725
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4726
	/**
4727
	 *	Load indicators for dashboard (this->nbtodo and this->nbtodolate)
4728
	 *
4729
	 *	@param  User					$user    	Object user
4730
	 *	@return WorkboardResponse|int 				<0 if KO, WorkboardResponse if OK
4731
	 */
4732
	public function load_board($user)
4733
	{
4734
		// phpcs:enable
4735
		global $conf, $langs;
4736
4737
		$clause = " WHERE";
4738
4739
		$sql = "SELECT f.rowid, f.date_lim_reglement as datefin, f.fk_statut, f.total_ht";
4740
		$sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
4741
		if (empty($user->rights->societe->client->voir) && !$user->socid) {
4742
			$sql .= " JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON f.fk_soc = sc.fk_soc";
4743
			$sql .= " WHERE sc.fk_user = ".((int) $user->id);
4744
			$clause = " AND";
4745
		}
4746
		$sql .= $clause." f.paye=0";
4747
		$sql .= " AND f.entity IN (".getEntity('invoice').")";
4748
		$sql .= " AND f.fk_statut = ".self::STATUS_VALIDATED;
4749
		if ($user->socid) {
4750
			$sql .= " AND f.fk_soc = ".((int) $user->socid);
4751
		}
4752
4753
		$resql = $this->db->query($sql);
4754
		if ($resql) {
4755
			$langs->load("bills");
4756
			$now = dol_now();
4757
4758
			$response = new WorkboardResponse();
4759
			$response->warning_delay = $conf->facture->client->warning_delay / 60 / 60 / 24;
4760
			$response->label = $langs->trans("CustomerBillsUnpaid");
4761
			$response->labelShort = $langs->trans("Unpaid");
4762
			$response->url = DOL_URL_ROOT.'/compta/facture/list.php?search_status=1&mainmenu=billing&leftmenu=customers_bills';
4763
			$response->img = img_object('', "bill");
4764
4765
			$generic_facture = new Facture($this->db);
4766
4767
			while ($obj = $this->db->fetch_object($resql)) {
4768
				$generic_facture->date_lim_reglement = $this->db->jdate($obj->datefin);
4769
				$generic_facture->statut = $obj->fk_statut;
4770
4771
				$response->nbtodo++;
4772
				$response->total += $obj->total_ht;
4773
4774
				if ($generic_facture->hasDelay()) {
4775
					$response->nbtodolate++;
4776
					$response->url_late = DOL_URL_ROOT.'/compta/facture/list.php?search_option=late&mainmenu=billing&leftmenu=customers_bills';
4777
				}
4778
			}
4779
4780
			$this->db->free($resql);
4781
			return $response;
4782
		} else {
4783
			dol_print_error($this->db);
4784
			$this->error = $this->db->error();
4785
			return -1;
4786
		}
4787
	}
4788
4789
4790
	/* gestion des contacts d'une facture */
4791
4792
	/**
4793
	 *	Retourne id des contacts clients de facturation
4794
	 *
4795
	 *	@return     array       Liste des id contacts facturation
4796
	 */
4797
	public function getIdBillingContact()
4798
	{
4799
		return $this->getIdContact('external', 'BILLING');
4800
	}
4801
4802
	/**
4803
	 *	Retourne id des contacts clients de livraison
4804
	 *
4805
	 *	@return     array       Liste des id contacts livraison
4806
	 */
4807
	public function getIdShippingContact()
4808
	{
4809
		return $this->getIdContact('external', 'SHIPPING');
4810
	}
4811
4812
4813
	/**
4814
	 *  Initialise an instance with random values.
4815
	 *  Used to build previews or test instances.
4816
	 *	id must be 0 if object instance is a specimen.
4817
	 *
4818
	 *	@param	string		$option		''=Create a specimen invoice with lines, 'nolines'=No lines
4819
	 *  @return	void
4820
	 */
4821
	public function initAsSpecimen($option = '')
4822
	{
4823
		global $conf, $langs, $user;
4824
4825
		$now = dol_now();
4826
		$arraynow = dol_getdate($now);
4827
		$nownotime = dol_mktime(0, 0, 0, $arraynow['mon'], $arraynow['mday'], $arraynow['year']);
4828
4829
		// Load array of products prodids
4830
		$num_prods = 0;
4831
		$prodids = array();
4832
		$sql = "SELECT rowid";
4833
		$sql .= " FROM ".MAIN_DB_PREFIX."product";
4834
		$sql .= " WHERE entity IN (".getEntity('product').")";
4835
		$sql .= $this->db->plimit(100);
4836
4837
		$resql = $this->db->query($sql);
4838
		if ($resql) {
4839
			$num_prods = $this->db->num_rows($resql);
4840
			$i = 0;
4841
			while ($i < $num_prods) {
4842
				$i++;
4843
				$row = $this->db->fetch_row($resql);
4844
				$prodids[$i] = $row[0];
4845
			}
4846
		}
4847
		//Avoid php warning Warning: mt_rand(): max(0) is smaller than min(1) when no product exists
4848
		if (empty($num_prods)) {
4849
			$num_prods = 1;
4850
		}
4851
4852
		// Initialize parameters
4853
		$this->id = 0;
4854
		$this->entity = 1;
4855
		$this->ref = 'SPECIMEN';
4856
		$this->specimen = 1;
4857
		$this->socid = 1;
4858
		$this->date = $nownotime;
4859
		$this->date_lim_reglement = $nownotime + 3600 * 24 * 30;
4860
		$this->cond_reglement_id   = 1;
4861
		$this->cond_reglement_code = 'RECEP';
4862
		$this->date_lim_reglement = $this->calculate_date_lim_reglement();
4863
		$this->mode_reglement_id   = 0; // Not forced to show payment mode CHQ + VIR
4864
		$this->mode_reglement_code = ''; // Not forced to show payment mode CHQ + VIR
4865
4866
		$this->note_public = 'This is a comment (public)';
4867
		$this->note_private = 'This is a comment (private)';
4868
		$this->note = 'This is a comment (private)';
4869
4870
		$this->fk_user_author = $user->id;
4871
4872
		$this->multicurrency_tx = 1;
4873
		$this->multicurrency_code = $conf->currency;
4874
4875
		$this->fk_incoterms = 0;
4876
		$this->location_incoterms = '';
4877
4878
		if (empty($option) || $option != 'nolines') {
4879
			// Lines
4880
			$nbp = 5;
4881
			$xnbp = 0;
4882
			while ($xnbp < $nbp) {
4883
				$line = new FactureLigne($this->db);
4884
				$line->desc = $langs->trans("Description")." ".$xnbp;
4885
				$line->qty = 1;
4886
				$line->subprice = 100;
4887
				$line->tva_tx = 19.6;
4888
				$line->localtax1_tx = 0;
4889
				$line->localtax2_tx = 0;
4890
				$line->remise_percent = 0;
4891
				if ($xnbp == 1) {        // Qty is negative (product line)
4892
					$prodid = mt_rand(1, $num_prods);
4893
					$line->fk_product = $prodids[$prodid];
4894
					$line->qty = -1;
4895
					$line->total_ht = -100;
4896
					$line->total_ttc = -119.6;
4897
					$line->total_tva = -19.6;
4898
					$line->multicurrency_total_ht = -200;
4899
					$line->multicurrency_total_ttc = -239.2;
4900
					$line->multicurrency_total_tva = -39.2;
4901
				} elseif ($xnbp == 2) {    // UP is negative (free line)
4902
					$line->subprice = -100;
4903
					$line->total_ht = -100;
4904
					$line->total_ttc = -119.6;
4905
					$line->total_tva = -19.6;
4906
					$line->remise_percent = 0;
4907
					$line->multicurrency_total_ht = -200;
4908
					$line->multicurrency_total_ttc = -239.2;
4909
					$line->multicurrency_total_tva = -39.2;
4910
				} elseif ($xnbp == 3) {    // Discount is 50% (product line)
4911
					$prodid = mt_rand(1, $num_prods);
4912
					$line->fk_product = $prodids[$prodid];
4913
					$line->total_ht = 50;
4914
					$line->total_ttc = 59.8;
4915
					$line->total_tva = 9.8;
4916
					$line->multicurrency_total_ht = 100;
4917
					$line->multicurrency_total_ttc = 119.6;
4918
					$line->multicurrency_total_tva = 19.6;
4919
					$line->remise_percent = 50;
4920
				} else // (product line)
4921
				{
4922
					$prodid = mt_rand(1, $num_prods);
4923
					$line->fk_product = $prodids[$prodid];
4924
					$line->total_ht = 100;
4925
					$line->total_ttc = 119.6;
4926
					$line->total_tva = 19.6;
4927
					$line->multicurrency_total_ht = 200;
4928
					$line->multicurrency_total_ttc = 239.2;
4929
					$line->multicurrency_total_tva = 39.2;
4930
					$line->remise_percent = 0;
4931
				}
4932
4933
				$this->lines[$xnbp] = $line;
4934
4935
4936
				$this->total_ht       += $line->total_ht;
4937
				$this->total_tva      += $line->total_tva;
4938
				$this->total_ttc      += $line->total_ttc;
4939
4940
				$this->multicurrency_total_ht       += $line->multicurrency_total_ht;
4941
				$this->multicurrency_total_tva      += $line->multicurrency_total_tva;
4942
				$this->multicurrency_total_ttc      += $line->multicurrency_total_ttc;
4943
4944
				$xnbp++;
4945
			}
4946
			$this->revenuestamp = 0;
4947
4948
			// Add a line "offered"
4949
			$line = new FactureLigne($this->db);
4950
			$line->desc = $langs->trans("Description")." (offered line)";
4951
			$line->qty = 1;
4952
			$line->subprice = 100;
4953
			$line->tva_tx = 19.6;
4954
			$line->localtax1_tx = 0;
4955
			$line->localtax2_tx = 0;
4956
			$line->remise_percent = 100;
4957
			$line->total_ht = 0;
4958
			$line->total_ttc = 0; // 90 * 1.196
4959
			$line->total_tva = 0;
4960
			$line->multicurrency_total_ht = 0;
4961
			$line->multicurrency_total_ttc = 0;
4962
			$line->multicurrency_total_tva = 0;
4963
			$prodid = mt_rand(1, $num_prods);
4964
			$line->fk_product = $prodids[$prodid];
4965
4966
			$this->lines[$xnbp] = $line;
4967
			$xnbp++;
4968
		}
4969
	}
4970
4971
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4972
	/**
4973
	 *      Load indicators for dashboard (this->nbtodo and this->nbtodolate)
4974
	 *
4975
	 *      @return         int     <0 if KO, >0 if OK
4976
	 */
4977
	public function load_state_board()
4978
	{
4979
		// phpcs:enable
4980
		global $conf, $user;
4981
4982
		$this->nb = array();
4983
4984
		$clause = "WHERE";
4985
4986
		$sql = "SELECT count(f.rowid) as nb";
4987
		$sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
4988
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON f.fk_soc = s.rowid";
4989
		if (empty($user->rights->societe->client->voir) && !$user->socid) {
4990
			$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
4991
			$sql .= " WHERE sc.fk_user = ".((int) $user->id);
4992
			$clause = "AND";
4993
		}
4994
		$sql .= " ".$clause." f.entity IN (".getEntity('invoice').")";
4995
4996
		$resql = $this->db->query($sql);
4997
		if ($resql) {
4998
			while ($obj = $this->db->fetch_object($resql)) {
4999
				$this->nb["invoices"] = $obj->nb;
5000
			}
5001
			$this->db->free($resql);
5002
			return 1;
5003
		} else {
5004
			dol_print_error($this->db);
5005
			$this->error = $this->db->error();
5006
			return -1;
5007
		}
5008
	}
5009
5010
	/**
5011
	 * 	Create an array of invoice lines
5012
	 *
5013
	 * 	@return int		>0 if OK, <0 if KO
5014
	 */
5015
	public function getLinesArray()
5016
	{
5017
		return $this->fetch_lines();
5018
	}
5019
5020
	/**
5021
	 *  Create a document onto disk according to template module.
5022
	 *
5023
	 *	@param	string		$modele			Generator to use. Caller must set it to obj->model_pdf or GETPOST('model','alpha') for example.
5024
	 *	@param	Translate	$outputlangs	Object lang to use for translation
5025
	 *  @param  int			$hidedetails    Hide details of lines
5026
	 *  @param  int			$hidedesc       Hide description
5027
	 *  @param  int			$hideref        Hide ref
5028
	 *  @param  null|array  $moreparams     Array to provide more information
5029
	 *	@return int        					<0 if KO, >0 if OK
5030
	 */
5031
	public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
5032
	{
5033
		global $conf, $langs;
5034
5035
		$outputlangs->loadLangs(array("bills", "products"));
5036
5037
		if (!dol_strlen($modele)) {
5038
			$modele = 'crabe';
5039
			$thisTypeConfName = 'FACTURE_ADDON_PDF_'.$this->type;
5040
5041
			if (!empty($this->model_pdf)) {
5042
				$modele = $this->model_pdf;
5043
			} elseif (!empty($this->modelpdf)) {	// deprecated
5044
				$modele = $this->modelpdf;
5045
			} elseif (!empty($conf->global->$thisTypeConfName)) {
5046
				$modele = $conf->global->$thisTypeConfName;
5047
			} elseif (!empty($conf->global->FACTURE_ADDON_PDF)) {
5048
				$modele = $conf->global->FACTURE_ADDON_PDF;
5049
			}
5050
		}
5051
5052
		$modelpath = "core/modules/facture/doc/";
5053
5054
		return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
5055
	}
5056
5057
	/**
5058
	 * Gets the smallest reference available for a new cycle
5059
	 *
5060
	 * @return int >= 1 if OK, -1 if error
5061
	 */
5062
	public function newCycle()
5063
	{
5064
		$sql = 'SELECT max(situation_cycle_ref) FROM '.MAIN_DB_PREFIX.'facture as f';
5065
		$sql .= " WHERE f.entity IN (".getEntity('invoice', 0).")";
5066
		$resql = $this->db->query($sql);
5067
		if ($resql) {
5068
			if ($this->db->num_rows($resql) > 0) {
5069
				$res = $this->db->fetch_array($resql);
5070
				$ref = $res['max(situation_cycle_ref)'];
5071
				$ref++;
5072
			} else {
5073
				$ref = 1;
5074
			}
5075
			$this->db->free($resql);
5076
			return $ref;
5077
		} else {
5078
			$this->error = $this->db->lasterror();
5079
			dol_syslog("Error sql=".$sql.", error=".$this->error, LOG_ERR);
5080
			return -1;
5081
		}
5082
	}
5083
5084
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5085
	/**
5086
	 * Checks if the invoice is the first of a cycle
5087
	 *
5088
	 * @return boolean
5089
	 */
5090
	public function is_first()
5091
	{
5092
		// phpcs:enable
5093
		return ($this->situation_counter == 1);
5094
	}
5095
5096
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5097
	/**
5098
	 * Returns an array containing the previous situations as Facture objects
5099
	 *
5100
	 * @return mixed -1 if error, array of previous situations
5101
	 */
5102
	public function get_prev_sits()
5103
	{
5104
		// phpcs:enable
5105
		global $conf;
5106
5107
		$sql = 'SELECT rowid FROM '.MAIN_DB_PREFIX.'facture';
5108
		$sql .= ' WHERE situation_cycle_ref = '.((int) $this->situation_cycle_ref);
5109
		$sql .= ' AND situation_counter < '.((int) $this->situation_counter);
5110
		$sql .= ' AND entity = '.($this->entity > 0 ? $this->entity : $conf->entity);
5111
		$resql = $this->db->query($sql);
5112
		$res = array();
5113
		if ($resql && $this->db->num_rows($resql) > 0) {
5114
			while ($row = $this->db->fetch_object($resql)) {
5115
				$id = $row->rowid;
5116
				$situation = new Facture($this->db);
5117
				$situation->fetch($id);
5118
				$res[] = $situation;
5119
			}
5120
		} else {
5121
			$this->error = $this->db->error();
5122
			dol_syslog("Error sql=".$sql.", error=".$this->error, LOG_ERR);
5123
			return -1;
5124
		}
5125
5126
		return $res;
5127
	}
5128
5129
	/**
5130
	 * Sets the invoice as a final situation
5131
	 *
5132
	 *  @param  	User	$user    	Object user
5133
	 *  @param     	int		$notrigger	1=Does not execute triggers, 0= execute triggers
5134
	 *	@return		int 				<0 if KO, >0 if OK
5135
	 */
5136
	public function setFinal(User $user, $notrigger = 0)
5137
	{
5138
		$error = 0;
5139
5140
		$this->db->begin();
5141
5142
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture SET situation_final = '.((int) $this->situation_final).' WHERE rowid = '.((int) $this->id);
5143
5144
		dol_syslog(__METHOD__, LOG_DEBUG);
5145
		$resql = $this->db->query($sql);
5146
		if (!$resql) {
5147
			$this->errors[] = $this->db->error();
5148
			$error++;
5149
		}
5150
5151
		if (!$notrigger && empty($error)) {
5152
			// Call trigger
5153
			$result = $this->call_trigger('BILL_MODIFY', $user);
5154
			if ($result < 0) {
5155
				$error++;
5156
			}
5157
			// End call triggers
5158
		}
5159
5160
		if (!$error) {
5161
			$this->db->commit();
5162
			return 1;
5163
		} else {
5164
			foreach ($this->errors as $errmsg) {
5165
				dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
5166
				$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
5167
			}
5168
			$this->db->rollback();
5169
			return -1 * $error;
5170
		}
5171
	}
5172
5173
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5174
	/**
5175
	 * Checks if the invoice is the last in its cycle
5176
	 *
5177
	 * @return bool Last of the cycle status
5178
	 */
5179
	public function is_last_in_cycle()
5180
	{
5181
		// phpcs:enable
5182
		global $conf;
5183
5184
		if (!empty($this->situation_cycle_ref)) {
5185
			// No point in testing anything if we're not inside a cycle
5186
			$sql = 'SELECT max(situation_counter) FROM '.MAIN_DB_PREFIX.'facture';
5187
			$sql .= ' WHERE situation_cycle_ref = '.((int) $this->situation_cycle_ref);
5188
			$sql .= ' AND entity = '.($this->entity > 0 ? $this->entity : $conf->entity);
5189
			$resql = $this->db->query($sql);
5190
5191
			if ($resql && $this->db->num_rows($resql) > 0) {
5192
				$res = $this->db->fetch_array($resql);
5193
				$last = $res['max(situation_counter)'];
5194
				return ($last == $this->situation_counter);
5195
			} else {
5196
				$this->error = $this->db->lasterror();
5197
				dol_syslog(get_class($this)."::select Error ".$this->error, LOG_ERR);
5198
				return false;
5199
			}
5200
		} else {
5201
			return true;
5202
		}
5203
	}
5204
5205
	/**
5206
	 * Function used to replace a thirdparty id with another one.
5207
	 *
5208
	 * @param 	DoliDB 	$dbs 		Database handler, because function is static we name it $dbs not $db to avoid breaking coding test
5209
	 * @param 	int 	$origin_id 	Old thirdparty id
5210
	 * @param 	int 	$dest_id 	New thirdparty id
5211
	 * @return 	bool
5212
	 */
5213
	public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
5214
	{
5215
		$tables = array(
5216
			'facture'
5217
		);
5218
5219
		return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
5220
	}
5221
5222
	/**
5223
	 * Function used to replace a product id with another one.
5224
	 *
5225
	 * @param DoliDB $db Database handler
5226
	 * @param int $origin_id Old product id
5227
	 * @param int $dest_id New product id
5228
	 * @return bool
5229
	 */
5230
	public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
5231
	{
5232
		$tables = array(
5233
			'facturedet'
5234
		);
5235
5236
		return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
5237
	}
5238
5239
	/**
5240
	 * Is the customer invoice delayed?
5241
	 *
5242
	 * @return bool
5243
	 */
5244
	public function hasDelay()
5245
	{
5246
		global $conf;
5247
5248
		$now = dol_now();
5249
5250
		// Paid invoices have status STATUS_CLOSED
5251
		if ($this->statut != Facture::STATUS_VALIDATED) {
5252
			return false;
5253
		}
5254
5255
		$hasDelay = $this->date_lim_reglement < ($now - $conf->facture->client->warning_delay);
5256
		if ($hasDelay && !empty($this->retained_warranty) && !empty($this->retained_warranty_date_limit)) {
5257
			$totalpaid = $this->getSommePaiement();
5258
			$totalpaid = floatval($totalpaid);
5259
			$RetainedWarrantyAmount = $this->getRetainedWarrantyAmount();
5260
			if ($totalpaid >= 0 && $RetainedWarrantyAmount >= 0) {
5261
				if (($totalpaid < $this->total_ttc - $RetainedWarrantyAmount) && $this->date_lim_reglement < ($now - $conf->facture->client->warning_delay)) {
5262
					$hasDelay = 1;
5263
				} elseif ($totalpaid < $this->total_ttc && $this->retained_warranty_date_limit < ($now - $conf->facture->client->warning_delay)) {
5264
					$hasDelay = 1;
5265
				} else {
5266
					$hasDelay = 0;
5267
				}
5268
			}
5269
		}
5270
5271
		return $hasDelay;
5272
	}
5273
5274
	/**
5275
	 * Currently used for documents generation : to know if retained warranty need to be displayed
5276
	 * @return bool
5277
	 */
5278
	public function displayRetainedWarranty()
5279
	{
5280
		global $conf;
5281
5282
		// TODO : add a flag on invoices to store this conf : INVOICE_RETAINED_WARRANTY_LIMITED_TO_FINAL_SITUATION
5283
5284
		// note : we don't need to test INVOICE_USE_RETAINED_WARRANTY because if $this->retained_warranty is not empty it's because it was set when this conf was active
5285
5286
		$displayWarranty = false;
5287
		if (!empty($this->retained_warranty)) {
5288
			$displayWarranty = true;
5289
5290
			if ($this->type == Facture::TYPE_SITUATION && !empty($conf->global->INVOICE_RETAINED_WARRANTY_LIMITED_TO_FINAL_SITUATION)) {
5291
				// Check if this situation invoice is 100% for real
5292
				$displayWarranty = false;
5293
				if (!empty($this->situation_final)) {
5294
					$displayWarranty = true;
5295
				} elseif (!empty($this->lines) && $this->status == Facture::STATUS_DRAFT) {
5296
					// $object->situation_final need validation to be done so this test is need for draft
5297
					$displayWarranty = true;
5298
5299
					foreach ($this->lines as $i => $line) {
5300
						if ($line->product_type < 2 && $line->situation_percent < 100) {
5301
							$displayWarranty = false;
5302
							break;
5303
						}
5304
					}
5305
				}
5306
			}
5307
		}
5308
5309
		return $displayWarranty;
5310
	}
5311
5312
	/**
5313
	 * @param	int			$rounding		Minimum number of decimal to show. If 0, no change, if -1, we use min($conf->global->MAIN_MAX_DECIMALS_UNIT,$conf->global->MAIN_MAX_DECIMALS_TOT)
5314
	 * @return float or -1 if not available
5315
	 */
5316
	public function getRetainedWarrantyAmount($rounding = -1)
5317
	{
5318
		global $conf;
5319
		if (empty($this->retained_warranty)) {
5320
			return -1;
5321
		}
5322
5323
		$retainedWarrantyAmount = 0;
5324
5325
		// Billed - retained warranty
5326
		if ($this->type == Facture::TYPE_SITUATION && !empty($conf->global->INVOICE_RETAINED_WARRANTY_LIMITED_TO_FINAL_SITUATION)) {
5327
			$displayWarranty = true;
5328
			// Check if this situation invoice is 100% for real
5329
			if (!empty($this->lines)) {
5330
				foreach ($this->lines as $i => $line) {
5331
					if ($line->product_type < 2 && $line->situation_percent < 100) {
5332
						$displayWarranty = false;
5333
						break;
5334
					}
5335
				}
5336
			}
5337
5338
			if ($displayWarranty && !empty($this->situation_final)) {
5339
				$this->fetchPreviousNextSituationInvoice();
5340
				$TPreviousIncoice = $this->tab_previous_situation_invoice;
5341
5342
				$total2BillWT = 0;
5343
				foreach ($TPreviousIncoice as &$fac) {
5344
					$total2BillWT += $fac->total_ttc;
5345
				}
5346
				$total2BillWT += $this->total_ttc;
5347
5348
				$retainedWarrantyAmount = $total2BillWT * $this->retained_warranty / 100;
5349
			} else {
5350
				return -1;
5351
			}
5352
		} else {
5353
			// Because one day retained warranty could be used on standard invoices
5354
			$retainedWarrantyAmount = $this->total_ttc * $this->retained_warranty / 100;
5355
		}
5356
5357
		if ($rounding < 0) {
5358
			$rounding = min($conf->global->MAIN_MAX_DECIMALS_UNIT, $conf->global->MAIN_MAX_DECIMALS_TOT);
5359
		}
5360
5361
		if ($rounding > 0) {
5362
			return round($retainedWarrantyAmount, $rounding);
5363
		}
5364
5365
		return $retainedWarrantyAmount;
5366
	}
5367
5368
	/**
5369
	 *  Change the retained warranty
5370
	 *
5371
	 *  @param		float		$value		value of retained warranty
5372
	 *  @return		int				>0 if OK, <0 if KO
5373
	 */
5374
	public function setRetainedWarranty($value)
5375
	{
5376
		dol_syslog(get_class($this).'::setRetainedWarranty('.$value.')');
5377
5378
		if ($this->statut >= 0) {
5379
			$fieldname = 'retained_warranty';
5380
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
5381
			$sql .= " SET ".$fieldname." = ".((float) $value);
5382
			$sql .= ' WHERE rowid='.((int) $this->id);
5383
5384
			if ($this->db->query($sql)) {
5385
				$this->retained_warranty = floatval($value);
5386
				return 1;
5387
			} else {
5388
				dol_syslog(get_class($this).'::setRetainedWarranty Erreur '.$sql.' - '.$this->db->error());
5389
				$this->error = $this->db->error();
5390
				return -1;
5391
			}
5392
		} else {
5393
			dol_syslog(get_class($this).'::setRetainedWarranty, status of the object is incompatible');
5394
			$this->error = 'Status of the object is incompatible '.$this->statut;
5395
			return -2;
5396
		}
5397
	}
5398
5399
5400
	/**
5401
	 *  Change the retained_warranty_date_limit
5402
	 *
5403
	 *  @param		int		$timestamp		date limit of retained warranty in timestamp format
5404
	 *  @param		string	$dateYmd		date limit of retained warranty in Y m d format
5405
	 *  @return		int				>0 if OK, <0 if KO
5406
	 */
5407
	public function setRetainedWarrantyDateLimit($timestamp, $dateYmd = false)
5408
	{
5409
		if (!$timestamp && $dateYmd) {
5410
			$timestamp = $this->db->jdate($dateYmd);
5411
		}
5412
5413
5414
		dol_syslog(get_class($this).'::setRetainedWarrantyDateLimit('.$timestamp.')');
5415
		if ($this->statut >= 0) {
5416
			$fieldname = 'retained_warranty_date_limit';
5417
			$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
5418
			$sql .= " SET ".$fieldname." = ".(strval($timestamp) != '' ? "'".$this->db->idate($timestamp)."'" : 'null');
5419
			$sql .= ' WHERE rowid = '.((int) $this->id);
5420
5421
			if ($this->db->query($sql)) {
5422
				$this->retained_warranty_date_limit = $timestamp;
5423
				return 1;
5424
			} else {
5425
				dol_syslog(get_class($this).'::setRetainedWarrantyDateLimit Erreur '.$sql.' - '.$this->db->error());
5426
				$this->error = $this->db->error();
5427
				return -1;
5428
			}
5429
		} else {
5430
			dol_syslog(get_class($this).'::setRetainedWarrantyDateLimit, status of the object is incompatible');
5431
			$this->error = 'Status of the object is incompatible '.$this->statut;
5432
			return -2;
5433
		}
5434
	}
5435
5436
5437
	/**
5438
	 *  Send reminders by emails for ivoices that are due
5439
	 *  CAN BE A CRON TASK
5440
	 *
5441
	 *  @param	int			$nbdays				Delay after due date (or before if delay is negative)
5442
	 *  @param	string		$paymentmode		'' or 'all' by default (no filter), or 'LIQ', 'CHQ', CB', ...
5443
	 *  @param	int|string	$template			Name (or id) of email template (Must be a template of type 'facture_send')
5444
	 *  @param	string		$forcerecipient		Force email of recipient (for example to send the email to an accountant supervisor instead of the customer)
5445
	 *  @return int         					0 if OK, <>0 if KO (this function is used also by cron so only 0 is OK)
5446
	 */
5447
	public function sendEmailsRemindersOnInvoiceDueDate($nbdays = 0, $paymentmode = 'all', $template = '', $forcerecipient = '')
5448
	{
5449
		global $conf, $langs, $user;
5450
5451
		$error = 0;
5452
		$this->output = '';
5453
		$this->error = '';
5454
		$nbMailSend = 0;
5455
		$errorsMsg = array();
5456
5457
		$langs->load("bills");
5458
5459
		if (!isModEnabled('facture')) {	// Should not happen. If module disabled, cron job should not be visible.
5460
			$this->output .= $langs->trans('ModuleNotEnabled', $langs->transnoentitiesnoconv("Facture"));
5461
			return 0;
5462
		}
5463
		/*if (empty($conf->global->FACTURE_REMINDER_EMAIL)) {
5464
			$langs->load("bills");
5465
			$this->output .= $langs->trans('EventRemindersByEmailNotEnabled', $langs->transnoentitiesnoconv("Facture"));
5466
			return 0;
5467
		}
5468
		*/
5469
5470
		require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
5471
		require_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php';
5472
		require_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
5473
		$formmail = new FormMail($this->db);
5474
5475
		$now = dol_now();
5476
		$tmpidate = dol_get_first_hour(dol_time_plus_duree($now, $nbdays, 'd'), 'gmt');
5477
5478
		$tmpinvoice = new Facture($this->db);
5479
5480
		dol_syslog(__METHOD__, LOG_DEBUG);
5481
5482
		// Select all action comm reminder
5483
		$sql = "SELECT rowid as id FROM ".MAIN_DB_PREFIX."facture as f";
5484
		if (!empty($paymentmode) && $paymentmode != 'all') {
5485
			$sql .= ", ".MAIN_DB_PREFIX."c_paiement as cp";
5486
		}
5487
		$sql .= " WHERE f.paye = 0";
5488
		$sql .= " AND f.fk_statut = ".self::STATUS_VALIDATED;
5489
		$sql .= " AND f.date_lim_reglement = '".$this->db->idate($tmpidate, 'gmt')."'";
5490
		$sql .= " AND f.entity IN (".getEntity('facture', 0).")";	// One batch process only one company (no sharing)
5491
		if (!empty($paymentmode) && $paymentmode != 'all') {
5492
			$sql .= " AND f.fk_mode_reglement = cp.id AND cp.code = '".$this->db->escape($paymentmode)."'";
5493
		}
5494
		// TODO Add a filter to check there is no payment started yet
5495
		$sql .= $this->db->order("date_lim_reglement", "ASC");
5496
5497
		$resql = $this->db->query($sql);
5498
5499
		$stmpidate = dol_print_date($tmpidate, 'day', 'gmt');
5500
		$this->output .= $langs->transnoentitiesnoconv("SearchUnpaidInvoicesWithDueDate", $stmpidate);
5501
		if (!empty($paymentmode) && $paymentmode != 'all') {
5502
			$this->output .= ' ('.$langs->transnoentitiesnoconv("PaymentMode").' '.$paymentmode.')';
5503
		}
5504
		$this->output .= '<br>';
5505
5506
		if ($resql) {
5507
			while ($obj = $this->db->fetch_object($resql)) {
5508
				if (!$error) {
5509
					// Load event
5510
					$res = $tmpinvoice->fetch($obj->id);
5511
					if ($res > 0) {
5512
						$tmpinvoice->fetch_thirdparty();
5513
5514
						$outputlangs = new Translate('', $conf);
5515
						if ($tmpinvoice->thirdparty->default_lang) {
5516
							$outputlangs->setDefaultLang($tmpinvoice->thirdparty->default_lang);
5517
							$outputlangs->loadLangs(array("main", "bills"));
5518
						} else {
5519
							$outputlangs = $langs;
5520
						}
5521
5522
						// Select email template
5523
						$arraymessage = $formmail->getEMailTemplate($this->db, 'facture_send', $user, $outputlangs, (is_numeric($template) ? $template : 0), 1, (is_numeric($template) ? '' : $template));
5524
						if (is_numeric($arraymessage) && $arraymessage <= 0) {
5525
							$langs->load("errors");
5526
							$this->output .= $langs->trans('ErrorFailedToFindEmailTemplate', $template);
5527
							return 0;
5528
						}
5529
5530
						// PREPARE EMAIL
5531
						$errormesg = '';
5532
5533
						// Make substitution in email content
5534
						$substitutionarray = getCommonSubstitutionArray($outputlangs, 0, '', $tmpinvoice);
5535
5536
						complete_substitutions_array($substitutionarray, $outputlangs, $tmpinvoice);
5537
5538
						// Topic
5539
						$sendTopic = make_substitutions(empty($arraymessage->topic) ? $outputlangs->transnoentitiesnoconv('InformationMessage') : $arraymessage->topic, $substitutionarray, $outputlangs, 1);
5540
5541
						// Content
5542
						$content = $outputlangs->transnoentitiesnoconv($arraymessage->content);
5543
5544
						$sendContent = make_substitutions($content, $substitutionarray, $outputlangs, 1);
5545
5546
						// Recipient
5547
						$to = array();
5548
						if ($forcerecipient) {	// If a recipient was forced
5549
							$to = array($forcerecipient);
5550
						} else {
5551
							$res = $tmpinvoice->fetch_thirdparty();
5552
							$recipient = $tmpinvoice->thirdparty;
5553
							if ($res > 0) {
5554
								$tmparraycontact = $tmpinvoice->liste_contact(-1, 'external', 0, 'BILLING');
5555
								if (is_array($tmparraycontact) && count($tmparraycontact) > 0) {
5556
									foreach ($tmparraycontact as $data_email) {
5557
										if (!empty($data_email['email'])) {
5558
											$to[] = $tmpinvoice->thirdparty->contact_get_property($data_email['id'], 'email');
5559
										}
5560
									}
5561
								}
5562
								if (empty($to) && !empty($recipient->email)) {
5563
									$to[] = $recipient->email;
5564
								} else {
5565
									$errormesg = "Failed to send remind to thirdparty id=".$tmpinvoice->socid.". No email defined for user.";
5566
									$error++;
5567
								}
5568
							} else {
5569
								$errormesg = "Failed to load recipient with thirdparty id=".$tmpinvoice->socid;
5570
								$error++;
5571
							}
5572
						}
5573
5574
						// Sender
5575
						$from = getDolGlobalString('MAIN_MAIL_EMAIL_FROM');
5576
						if (!empty($arraymessage->email_from)) {	// If a sender is defined into template, we use it in priority
5577
							$from = $arraymessage->email_from;
5578
						}
5579
						if (empty($from)) {
5580
							$errormesg = "Failed to get sender into global setup MAIN_MAIL_EMAIL_FROM";
5581
							$error++;
5582
						}
5583
5584
						if (!$error && !empty($to)) {
5585
							$this->db->begin();
5586
5587
							$to = implode(',', $to);
5588
							if (!empty($arraymessage->email_to)) {	// If a recipient is defined into template, we add it
5589
								$to = $to.','.$arraymessage->email_to;
5590
							}
5591
5592
							// Errors Recipient
5593
							$errors_to = $conf->global->MAIN_MAIL_ERRORS_TO;
5594
5595
							$trackid = 'inv'.$tmpinvoice->id;
5596
							$sendcontext = 'standard';
5597
5598
							$email_tocc = '';
5599
							if (!empty($arraymessage->email_tocc)) {	// If a CC is defined into template, we use it
5600
								$email_tocc = $arraymessage->email_tocc;
5601
							}
5602
5603
							$email_tobcc = '';
5604
							if (!empty($arraymessage->email_tobcc)) {	// If a BCC is defined into template, we use it
5605
								$email_tobcc = $arraymessage->email_tobcc;
5606
							}
5607
5608
							// Mail Creation
5609
							$cMailFile = new CMailFile($sendTopic, $to, $from, $sendContent, array(), array(), array(), $email_tocc, $email_tobcc, 0, 1, $errors_to, '', $trackid, '', $sendcontext, '');
5610
5611
							// Sending Mail
5612
							if ($cMailFile->sendfile()) {
5613
								$nbMailSend++;
5614
5615
								// Add a line into event table
5616
								require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
5617
5618
								// Insert record of emails sent
5619
								$actioncomm = new ActionComm($this->db);
5620
5621
								$actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
5622
								$actioncomm->socid = $tmpinvoice->thirdparty->id; // To link to a company
5623
								$actioncomm->contact_id = 0;
5624
5625
								$actioncomm->code = 'AC_EMAIL';
5626
								$actioncomm->label = 'sendEmailsRemindersOnInvoiceDueDateOK';
5627
								$actioncomm->note_private = $sendContent;
5628
								$actioncomm->fk_project = $tmpinvoice->fk_project;
5629
								$actioncomm->datep = dol_now();
5630
								$actioncomm->datef = $actioncomm->datep;
5631
								$actioncomm->percentage = -1; // Not applicable
5632
								$actioncomm->authorid = $user->id; // User saving action
5633
								$actioncomm->userownerid = $user->id; // Owner of action
5634
								// Fields when action is an email (content should be added into note)
5635
								$actioncomm->email_msgid = $cMailFile->msgid;
5636
								$actioncomm->email_from = $from;
5637
								$actioncomm->email_sender = '';
5638
								$actioncomm->email_to = $to;
5639
								//$actioncomm->email_tocc = $sendtocc;
5640
								//$actioncomm->email_tobcc = $sendtobcc;
5641
								//$actioncomm->email_subject = $subject;
5642
								$actioncomm->errors_to = $errors_to;
5643
5644
								$actioncomm->elementtype = 'invoice';
5645
								$actioncomm->fk_element = $tmpinvoice->id;
5646
5647
								//$actioncomm->extraparams = $extraparams;
5648
5649
								$actioncomm->create($user);
5650
							} else {
5651
								$errormesg = $cMailFile->error.' : '.$to;
5652
								$error++;
5653
5654
								// Add a line into event table
5655
								require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
5656
5657
								// Insert record of emails sent
5658
								$actioncomm = new ActionComm($this->db);
5659
5660
								$actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
5661
								$actioncomm->socid = $tmpinvoice->thirdparty->id; // To link to a company
5662
								$actioncomm->contact_id = 0;
5663
5664
								$actioncomm->code = 'AC_EMAIL';
5665
								$actioncomm->label = 'sendEmailsRemindersOnInvoiceDueDateKO';
5666
								$actioncomm->note_private = $errormesg;
5667
								$actioncomm->fk_project = $tmpinvoice->fk_project;
5668
								$actioncomm->datep = dol_now();
5669
								$actioncomm->datef = $actioncomm->datep;
5670
								$actioncomm->percentage = -1; // Not applicable
5671
								$actioncomm->authorid = $user->id; // User saving action
5672
								$actioncomm->userownerid = $user->id; // Owner of action
5673
								// Fields when action is an email (content should be added into note)
5674
								$actioncomm->email_msgid = $cMailFile->msgid;
5675
								$actioncomm->email_from = $from;
5676
								$actioncomm->email_sender = '';
5677
								$actioncomm->email_to = $to;
5678
								//$actioncomm->email_tocc = $sendtocc;
5679
								//$actioncomm->email_tobcc = $sendtobcc;
5680
								//$actioncomm->email_subject = $subject;
5681
								$actioncomm->errors_to = $errors_to;
5682
5683
								//$actioncomm->extraparams = $extraparams;
5684
5685
								$actioncomm->create($user);
5686
							}
5687
5688
							$this->db->commit();	// We always commit
5689
						}
5690
5691
						if ($errormesg) {
5692
							$errorsMsg[] = $errormesg;
5693
						}
5694
					} else {
5695
						$errorsMsg[] = 'Failed to fetch record invoice with ID = '.$obj->id;
5696
						$error++;
5697
					}
5698
				}
5699
			}
5700
		} else {
5701
			$error++;
5702
		}
5703
5704
		if (!$error) {
5705
			$this->output .= 'Nb of emails sent : '.$nbMailSend;
5706
			return 0;
5707
		} else {
5708
			$this->error = 'Nb of emails sent : '.$nbMailSend.', '.(!empty($errorsMsg)) ? join(', ', $errorsMsg) : $error;
5709
			return $error;
5710
		}
5711
	}
5712
5713
	/**
5714
	 * See if current invoice date is posterior to the last invoice date among validated invoices of same type.
5715
	 *
5716
	 * @param 	boolean 	$allow_validated_drafts			return true if the invoice has been validated before returning to DRAFT state.
5717
	 * @return 	array										return array
5718
	 */
5719
	public function willBeLastOfSameType($allow_validated_drafts = false)
5720
	{
5721
		// get date of last validated invoices of same type
5722
		$sql  = "SELECT datef";
5723
		$sql .= " FROM ".MAIN_DB_PREFIX."facture";
5724
		$sql .= " WHERE type = " . (int) $this->type ;
5725
		$sql .= " AND date_valid IS NOT NULL";
5726
		$sql .= " ORDER BY datef DESC LIMIT 1";
5727
5728
		$result = $this->db->query($sql);
5729
		if ($result) {
5730
			// compare with current validation date
5731
			if ($this->db->num_rows($result)) {
5732
				$obj = $this->db->fetch_object($result);
5733
				$last_date = $this->db->jdate($obj->datef);
5734
				$invoice_date = $this->date;
5735
5736
				$is_last_of_same_type = $invoice_date >= $last_date;
5737
				if ($allow_validated_drafts) {
5738
					$is_last_of_same_type = $is_last_of_same_type || (!strpos($this->ref, 'PROV') && $this->status == self::STATUS_DRAFT);
5739
				}
5740
5741
				return array($is_last_of_same_type, $last_date);
5742
			} else {
5743
				// element is first of type to be validated
5744
				return array(true);
5745
			}
5746
		} else {
5747
			dol_print_error($this->db);
5748
		}
5749
5750
		return array();
5751
	}
5752
}
5753
5754
/**
5755
 *	Class to manage invoice lines.
5756
 *  Saved into database table llx_facturedet
5757
 */
5758
class FactureLigne extends CommonInvoiceLine
5759
{
5760
	/**
5761
	 * @var string ID to identify managed object
5762
	 */
5763
	public $element = 'facturedet';
5764
5765
	/**
5766
	 * @var string Name of table without prefix where object is stored
5767
	 */
5768
	public $table_element = 'facturedet';
5769
5770
	public $oldline;
5771
5772
	//! From llx_facturedet
5773
	//! Id facture
5774
	public $fk_facture;
5775
	//! Id parent line
5776
	public $fk_parent_line;
5777
5778
	//! Description ligne
5779
	public $desc;
5780
	public $ref_ext; // External reference of the line
5781
5782
	public $localtax1_type; // Local tax 1 type
5783
	public $localtax2_type; // Local tax 2 type
5784
	public $fk_remise_except; // Link to line into llx_remise_except
5785
	public $rang = 0;
5786
5787
	public $fk_fournprice;
5788
	public $pa_ht;
5789
	public $marge_tx;
5790
	public $marque_tx;
5791
5792
	public $remise_percent;
5793
5794
	public $special_code; // Liste d'options non cumulabels:
5795
	// 1: frais de port
5796
	// 2: ecotaxe
5797
	// 3: ??
5798
5799
	public $origin;
5800
	public $origin_id;
5801
5802
	public $fk_code_ventilation = 0;
5803
5804
	public $date_start;
5805
	public $date_end;
5806
5807
	public $skip_update_total; // Skip update price total for special lines
5808
5809
	/**
5810
	 * @var int Situation advance percentage
5811
	 */
5812
	public $situation_percent;
5813
5814
	/**
5815
	 * @var int Previous situation line id reference
5816
	 */
5817
	public $fk_prev_id;
5818
5819
	// Multicurrency
5820
	public $fk_multicurrency;
5821
	public $multicurrency_code;
5822
	public $multicurrency_subprice;
5823
	public $multicurrency_total_ht;
5824
	public $multicurrency_total_tva;
5825
	public $multicurrency_total_ttc;
5826
5827
	/**
5828
	 *	Load invoice line from database
5829
	 *
5830
	 *	@param	int		$rowid      id of invoice line to get
5831
	 *	@return	int					<0 if KO, >0 if OK
5832
	 */
5833
	public function fetch($rowid)
5834
	{
5835
		$sql = 'SELECT fd.rowid, fd.fk_facture, fd.fk_parent_line, fd.fk_product, fd.product_type, fd.label as custom_label, fd.description, fd.price, fd.qty, fd.vat_src_code, fd.tva_tx,';
5836
		$sql .= ' fd.localtax1_tx, fd. localtax2_tx, fd.remise, fd.remise_percent, fd.fk_remise_except, fd.subprice, fd.ref_ext,';
5837
		$sql .= ' fd.date_start as date_start, fd.date_end as date_end, fd.fk_product_fournisseur_price as fk_fournprice, fd.buy_price_ht as pa_ht,';
5838
		$sql .= ' fd.info_bits, fd.special_code, fd.total_ht, fd.total_tva, fd.total_ttc, fd.total_localtax1, fd.total_localtax2, fd.rang,';
5839
		$sql .= ' fd.fk_code_ventilation,';
5840
		$sql .= ' fd.fk_unit, fd.fk_user_author, fd.fk_user_modif,';
5841
		$sql .= ' fd.situation_percent, fd.fk_prev_id,';
5842
		$sql .= ' fd.multicurrency_subprice,';
5843
		$sql .= ' fd.multicurrency_total_ht,';
5844
		$sql .= ' fd.multicurrency_total_tva,';
5845
		$sql .= ' fd.multicurrency_total_ttc,';
5846
		$sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc';
5847
		$sql .= ' FROM '.MAIN_DB_PREFIX.'facturedet as fd';
5848
		$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON fd.fk_product = p.rowid';
5849
		$sql .= ' WHERE fd.rowid = '.((int) $rowid);
5850
5851
		$result = $this->db->query($sql);
5852
		if ($result) {
5853
			$objp = $this->db->fetch_object($result);
5854
5855
			if (!$objp) {
5856
				$this->error = 'InvoiceLine with id '. $rowid .' not found sql='.$sql;
5857
				return 0;
5858
			}
5859
5860
			$this->rowid = $objp->rowid;
5861
			$this->id = $objp->rowid;
5862
			$this->fk_facture = $objp->fk_facture;
5863
			$this->fk_parent_line = $objp->fk_parent_line;
5864
			$this->label				= $objp->custom_label;
5865
			$this->desc					= $objp->description;
5866
			$this->qty = $objp->qty;
5867
			$this->subprice = $objp->subprice;
5868
			$this->ref_ext = $objp->ref_ext;
5869
			$this->vat_src_code = $objp->vat_src_code;
5870
			$this->tva_tx = $objp->tva_tx;
5871
			$this->localtax1_tx			= $objp->localtax1_tx;
5872
			$this->localtax2_tx			= $objp->localtax2_tx;
5873
			$this->remise_percent = $objp->remise_percent;
5874
			$this->fk_remise_except = $objp->fk_remise_except;
5875
			$this->fk_product			= $objp->fk_product;
5876
			$this->product_type = $objp->product_type;
5877
			$this->date_start			= $this->db->jdate($objp->date_start);
5878
			$this->date_end				= $this->db->jdate($objp->date_end);
5879
			$this->info_bits			= $objp->info_bits;
5880
			$this->tva_npr = ($objp->info_bits & 1 == 1) ? 1 : 0;
5881
			$this->special_code = $objp->special_code;
5882
			$this->total_ht				= $objp->total_ht;
5883
			$this->total_tva			= $objp->total_tva;
5884
			$this->total_localtax1		= $objp->total_localtax1;
5885
			$this->total_localtax2		= $objp->total_localtax2;
5886
			$this->total_ttc			= $objp->total_ttc;
5887
			$this->fk_code_ventilation = $objp->fk_code_ventilation;
5888
			$this->rang					= $objp->rang;
5889
			$this->fk_fournprice = $objp->fk_fournprice;
5890
			$marginInfos				= getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
5891
			$this->pa_ht				= $marginInfos[0];
5892
			$this->marge_tx				= $marginInfos[1];
5893
			$this->marque_tx			= $marginInfos[2];
5894
5895
			$this->ref = $objp->product_ref; // deprecated
5896
5897
			$this->product_ref = $objp->product_ref;
5898
			$this->product_label		= $objp->product_label;
5899
			$this->product_desc			= $objp->product_desc;
5900
5901
			$this->fk_unit = $objp->fk_unit;
5902
			$this->fk_user_modif		= $objp->fk_user_modif;
5903
			$this->fk_user_author = $objp->fk_user_author;
5904
5905
			$this->situation_percent    = $objp->situation_percent;
5906
			$this->fk_prev_id           = $objp->fk_prev_id;
5907
5908
			$this->multicurrency_subprice = $objp->multicurrency_subprice;
5909
			$this->multicurrency_total_ht = $objp->multicurrency_total_ht;
5910
			$this->multicurrency_total_tva = $objp->multicurrency_total_tva;
5911
			$this->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
5912
5913
			$this->db->free($result);
5914
5915
			return 1;
5916
		} else {
5917
			$this->error = $this->db->lasterror();
5918
			return -1;
5919
		}
5920
	}
5921
5922
	/**
5923
	 *	Insert line into database
5924
	 *
5925
	 *	@param      int		$notrigger		                 1 no triggers
5926
	 *  @param      int     $noerrorifdiscountalreadylinked  1=Do not make error if lines is linked to a discount and discount already linked to another
5927
	 *	@return		int						                 <0 if KO, >0 if OK
5928
	 */
5929
	public function insert($notrigger = 0, $noerrorifdiscountalreadylinked = 0)
5930
	{
5931
		global $langs, $user, $conf;
5932
5933
		$error = 0;
5934
5935
		$pa_ht_isemptystring = (empty($this->pa_ht) && $this->pa_ht == ''); // If true, we can use a default value. If this->pa_ht = '0', we must use '0'.
5936
5937
		dol_syslog(get_class($this)."::insert rang=".$this->rang, LOG_DEBUG);
5938
5939
		// Clean parameters
5940
		$this->desc = trim($this->desc);
5941
		if (empty($this->tva_tx)) {
5942
			$this->tva_tx = 0;
5943
		}
5944
		if (empty($this->localtax1_tx)) {
5945
			$this->localtax1_tx = 0;
5946
		}
5947
		if (empty($this->localtax2_tx)) {
5948
			$this->localtax2_tx = 0;
5949
		}
5950
		if (empty($this->localtax1_type)) {
5951
			$this->localtax1_type = 0;
5952
		}
5953
		if (empty($this->localtax2_type)) {
5954
			$this->localtax2_type = 0;
5955
		}
5956
		if (empty($this->total_localtax1)) {
5957
			$this->total_localtax1 = 0;
5958
		}
5959
		if (empty($this->total_localtax2)) {
5960
			$this->total_localtax2 = 0;
5961
		}
5962
		if (empty($this->rang)) {
5963
			$this->rang = 0;
5964
		}
5965
		if (empty($this->remise_percent)) {
5966
			$this->remise_percent = 0;
5967
		}
5968
		if (empty($this->info_bits)) {
5969
			$this->info_bits = 0;
5970
		}
5971
		if (empty($this->subprice)) {
5972
			$this->subprice = 0;
5973
		}
5974
		if (empty($this->ref_ext)) {
5975
			$this->ref_ext = '';
5976
		}
5977
		if (empty($this->special_code)) {
5978
			$this->special_code = 0;
5979
		}
5980
		if (empty($this->fk_parent_line)) {
5981
			$this->fk_parent_line = 0;
5982
		}
5983
		if (empty($this->fk_prev_id)) {
5984
			$this->fk_prev_id = 0;
5985
		}
5986
		if (!isset($this->situation_percent) || $this->situation_percent > 100 || (string) $this->situation_percent == '') {
5987
			$this->situation_percent = 100;
5988
		}
5989
5990
		if (empty($this->pa_ht)) {
5991
			$this->pa_ht = 0;
5992
		}
5993
		if (empty($this->multicurrency_subprice)) {
5994
			$this->multicurrency_subprice = 0;
5995
		}
5996
		if (empty($this->multicurrency_total_ht)) {
5997
			$this->multicurrency_total_ht = 0;
5998
		}
5999
		if (empty($this->multicurrency_total_tva)) {
6000
			$this->multicurrency_total_tva = 0;
6001
		}
6002
		if (empty($this->multicurrency_total_ttc)) {
6003
			$this->multicurrency_total_ttc = 0;
6004
		}
6005
6006
		// if buy price not defined, define buyprice as configured in margin admin
6007
		if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
6008
			if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0) {
6009
				return $result;
6010
			} else {
6011
				$this->pa_ht = $result;
6012
			}
6013
		}
6014
6015
		// Check parameters
6016
		if ($this->product_type < 0) {
6017
			$this->error = 'ErrorProductTypeMustBe0orMore';
6018
			return -1;
6019
		}
6020
		if (!empty($this->fk_product) && $this->fk_product > 0) {
6021
			// Check product exists
6022
			$result = Product::isExistingObject('product', $this->fk_product);
6023
			if ($result <= 0) {
6024
				$this->error = 'ErrorProductIdDoesNotExists';
6025
				dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
6026
				return -1;
6027
			}
6028
		}
6029
6030
		$this->db->begin();
6031
6032
		// Update line in database
6033
		$sql = 'INSERT INTO '.MAIN_DB_PREFIX.'facturedet';
6034
		$sql .= ' (fk_facture, fk_parent_line, label, description, qty,';
6035
		$sql .= ' vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
6036
		$sql .= ' fk_product, product_type, remise_percent, subprice, ref_ext, fk_remise_except,';
6037
		$sql .= ' date_start, date_end, fk_code_ventilation, ';
6038
		$sql .= ' rang, special_code, fk_product_fournisseur_price, buy_price_ht,';
6039
		$sql .= ' info_bits, total_ht, total_tva, total_ttc, total_localtax1, total_localtax2,';
6040
		$sql .= ' situation_percent, fk_prev_id,';
6041
		$sql .= ' fk_unit, fk_user_author, fk_user_modif,';
6042
		$sql .= ' fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
6043
		$sql .= ')';
6044
		$sql .= " VALUES (".$this->fk_facture.",";
6045
		$sql .= " ".($this->fk_parent_line > 0 ? $this->fk_parent_line : "null").",";
6046
		$sql .= " ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null").",";
6047
		$sql .= " '".$this->db->escape($this->desc)."',";
6048
		$sql .= " ".price2num($this->qty).",";
6049
		$sql .= " ".(empty($this->vat_src_code) ? "''" : "'".$this->db->escape($this->vat_src_code)."'").",";
6050
		$sql .= " ".price2num($this->tva_tx).",";
6051
		$sql .= " ".price2num($this->localtax1_tx).",";
6052
		$sql .= " ".price2num($this->localtax2_tx).",";
6053
		$sql .= " '".$this->db->escape($this->localtax1_type)."',";
6054
		$sql .= " '".$this->db->escape($this->localtax2_type)."',";
6055
		$sql .= ' '.((!empty($this->fk_product) && $this->fk_product > 0) ? $this->fk_product : "null").',';
6056
		$sql .= " ".((int) $this->product_type).",";
6057
		$sql .= " ".price2num($this->remise_percent).",";
6058
		$sql .= " ".price2num($this->subprice).",";
6059
		$sql .= " '".$this->db->escape($this->ref_ext)."',";
6060
		$sql .= ' '.(!empty($this->fk_remise_except) ? $this->fk_remise_except : "null").',';
6061
		$sql .= " ".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null").",";
6062
		$sql .= " ".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null").",";
6063
		$sql .= ' '.((int) $this->fk_code_ventilation).',';
6064
		$sql .= ' '.((int) $this->rang).',';
6065
		$sql .= ' '.((int) $this->special_code).',';
6066
		$sql .= ' '.(!empty($this->fk_fournprice) ? $this->fk_fournprice : "null").',';
6067
		$sql .= ' '.price2num($this->pa_ht).',';
6068
		$sql .= " '".$this->db->escape($this->info_bits)."',";
6069
		$sql .= " ".price2num($this->total_ht).",";
6070
		$sql .= " ".price2num($this->total_tva).",";
6071
		$sql .= " ".price2num($this->total_ttc).",";
6072
		$sql .= " ".price2num($this->total_localtax1).",";
6073
		$sql .= " ".price2num($this->total_localtax2);
6074
		$sql .= ", ".((float) $this->situation_percent);
6075
		$sql .= ", ".(!empty($this->fk_prev_id) ? $this->fk_prev_id : "null");
6076
		$sql .= ", ".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
6077
		$sql .= ", ".((int) $user->id);
6078
		$sql .= ", ".((int) $user->id);
6079
		$sql .= ", ".(int) $this->fk_multicurrency;
6080
		$sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
6081
		$sql .= ", ".price2num($this->multicurrency_subprice);
6082
		$sql .= ", ".price2num($this->multicurrency_total_ht);
6083
		$sql .= ", ".price2num($this->multicurrency_total_tva);
6084
		$sql .= ", ".price2num($this->multicurrency_total_ttc);
6085
		$sql .= ')';
6086
6087
		dol_syslog(get_class($this)."::insert", LOG_DEBUG);
6088
		$resql = $this->db->query($sql);
6089
		if ($resql) {
6090
			$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'facturedet');
6091
			$this->rowid = $this->id; // For backward compatibility
6092
6093
			if (!$error) {
6094
				$result = $this->insertExtraFields();
6095
				if ($result < 0) {
6096
					$error++;
6097
				}
6098
			}
6099
6100
			// If fk_remise_except is defined, the discount is linked to the invoice
6101
			// which flags it as "consumed".
6102
			if ($this->fk_remise_except) {
6103
				$discount = new DiscountAbsolute($this->db);
6104
				$result = $discount->fetch($this->fk_remise_except);
6105
				if ($result >= 0) {
6106
					// Check if discount was found
6107
					if ($result > 0) {
6108
						// Check if discount not already affected to another invoice
6109
						if ($discount->fk_facture_line > 0) {
6110
							if (empty($noerrorifdiscountalreadylinked)) {
6111
								$this->error = $langs->trans("ErrorDiscountAlreadyUsed", $discount->id);
6112
								dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
6113
								$this->db->rollback();
6114
								return -3;
6115
							}
6116
						} else {
6117
							$result = $discount->link_to_invoice($this->rowid, 0);
6118
							if ($result < 0) {
6119
								$this->error = $discount->error;
6120
								dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
6121
								$this->db->rollback();
6122
								return -3;
6123
							}
6124
						}
6125
					} else {
6126
						$this->error = $langs->trans("ErrorADiscountThatHasBeenRemovedIsIncluded");
6127
						dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
6128
						$this->db->rollback();
6129
						return -3;
6130
					}
6131
				} else {
6132
					$this->error = $discount->error;
6133
					dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
6134
					$this->db->rollback();
6135
					return -3;
6136
				}
6137
			}
6138
6139
			if (!$notrigger) {
6140
				// Call trigger
6141
				$result = $this->call_trigger('LINEBILL_INSERT', $user);
6142
				if ($result < 0) {
6143
					$this->db->rollback();
6144
					return -2;
6145
				}
6146
				// End call triggers
6147
			}
6148
6149
			$this->db->commit();
6150
			return $this->id;
6151
		} else {
6152
			$this->error = $this->db->lasterror();
6153
			$this->db->rollback();
6154
			return -2;
6155
		}
6156
	}
6157
6158
	/**
6159
	 *	Update line into database
6160
	 *
6161
	 *	@param		User	$user		User object
6162
	 *	@param		int		$notrigger	Disable triggers
6163
	 *	@return		int					<0 if KO, >0 if OK
6164
	 */
6165
	public function update($user = '', $notrigger = 0)
6166
	{
6167
		global $user, $conf;
6168
6169
		$error = 0;
6170
6171
		$pa_ht_isemptystring = (empty($this->pa_ht) && $this->pa_ht == ''); // If true, we can use a default value. If this->pa_ht = '0', we must use '0'.
6172
6173
		// Clean parameters
6174
		$this->desc = trim($this->desc);
6175
		if (empty($this->ref_ext)) {
6176
			$this->ref_ext = '';
6177
		}
6178
		if (empty($this->tva_tx)) {
6179
			$this->tva_tx = 0;
6180
		}
6181
		if (empty($this->localtax1_tx)) {
6182
			$this->localtax1_tx = 0;
6183
		}
6184
		if (empty($this->localtax2_tx)) {
6185
			$this->localtax2_tx = 0;
6186
		}
6187
		if (empty($this->localtax1_type)) {
6188
			$this->localtax1_type = 0;
6189
		}
6190
		if (empty($this->localtax2_type)) {
6191
			$this->localtax2_type = 0;
6192
		}
6193
		if (empty($this->total_localtax1)) {
6194
			$this->total_localtax1 = 0;
6195
		}
6196
		if (empty($this->total_localtax2)) {
6197
			$this->total_localtax2 = 0;
6198
		}
6199
		if (empty($this->remise_percent)) {
6200
			$this->remise_percent = 0;
6201
		}
6202
		if (empty($this->info_bits)) {
6203
			$this->info_bits = 0;
6204
		}
6205
		if (empty($this->special_code)) {
6206
			$this->special_code = 0;
6207
		}
6208
		if (empty($this->product_type)) {
6209
			$this->product_type = 0;
6210
		}
6211
		if (empty($this->fk_parent_line)) {
6212
			$this->fk_parent_line = 0;
6213
		}
6214
		if (!isset($this->situation_percent) || $this->situation_percent > 100 || (string) $this->situation_percent == '') {
6215
			$this->situation_percent = 100;
6216
		}
6217
		if (empty($this->pa_ht)) {
6218
			$this->pa_ht = 0;
6219
		}
6220
6221
		if (empty($this->multicurrency_subprice)) {
6222
			$this->multicurrency_subprice = 0;
6223
		}
6224
		if (empty($this->multicurrency_total_ht)) {
6225
			$this->multicurrency_total_ht = 0;
6226
		}
6227
		if (empty($this->multicurrency_total_tva)) {
6228
			$this->multicurrency_total_tva = 0;
6229
		}
6230
		if (empty($this->multicurrency_total_ttc)) {
6231
			$this->multicurrency_total_ttc = 0;
6232
		}
6233
6234
		// Check parameters
6235
		if ($this->product_type < 0) {
6236
			return -1;
6237
		}
6238
6239
		// if buy price not provided, define buyprice as configured in margin admin
6240
		if ($this->pa_ht == 0 && $pa_ht_isemptystring) {
6241
			// We call defineBuyPrice only if data was not provided (if input was '0', we will not go here and value will remaine '0')
6242
			$result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product);
6243
			if ($result < 0) {
6244
				return $result;
6245
			} else {
6246
				$this->pa_ht = $result;
6247
			}
6248
		}
6249
6250
		$this->db->begin();
6251
6252
		// Update line in database
6253
		$sql = "UPDATE ".MAIN_DB_PREFIX."facturedet SET";
6254
		$sql .= " description='".$this->db->escape($this->desc)."'";
6255
		$sql .= ", ref_ext='".$this->db->escape($this->ref_ext)."'";
6256
		$sql .= ", label=".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
6257
		$sql .= ", subprice=".price2num($this->subprice);
6258
		$sql .= ", remise_percent=".price2num($this->remise_percent);
6259
		if ($this->fk_remise_except) {
6260
			$sql .= ", fk_remise_except=".$this->fk_remise_except;
6261
		} else {
6262
			$sql .= ", fk_remise_except=null";
6263
		}
6264
		$sql .= ", vat_src_code = '".(empty($this->vat_src_code) ? '' : $this->db->escape($this->vat_src_code))."'";
6265
		$sql .= ", tva_tx=".price2num($this->tva_tx);
6266
		$sql .= ", localtax1_tx=".price2num($this->localtax1_tx);
6267
		$sql .= ", localtax2_tx=".price2num($this->localtax2_tx);
6268
		$sql .= ", localtax1_type='".$this->db->escape($this->localtax1_type)."'";
6269
		$sql .= ", localtax2_type='".$this->db->escape($this->localtax2_type)."'";
6270
		$sql .= ", qty=".price2num($this->qty);
6271
		$sql .= ", date_start=".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null");
6272
		$sql .= ", date_end=".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
6273
		$sql .= ", product_type=".$this->product_type;
6274
		$sql .= ", info_bits='".$this->db->escape($this->info_bits)."'";
6275
		$sql .= ", special_code='".$this->db->escape($this->special_code)."'";
6276
		if (empty($this->skip_update_total)) {
6277
			$sql .= ", total_ht=".price2num($this->total_ht);
6278
			$sql .= ", total_tva=".price2num($this->total_tva);
6279
			$sql .= ", total_ttc=".price2num($this->total_ttc);
6280
			$sql .= ", total_localtax1=".price2num($this->total_localtax1);
6281
			$sql .= ", total_localtax2=".price2num($this->total_localtax2);
6282
		}
6283
		$sql .= ", fk_product_fournisseur_price=".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null");
6284
		$sql .= ", buy_price_ht=".(($this->pa_ht || (string) $this->pa_ht === '0') ? price2num($this->pa_ht) : "null"); // $this->pa_ht should always be defined (set to 0 or to sell price depending on option)
6285
		$sql .= ", fk_parent_line=".($this->fk_parent_line > 0 ? $this->fk_parent_line : "null");
6286
		if (!empty($this->rang)) {
6287
			$sql .= ", rang=".((int) $this->rang);
6288
		}
6289
		$sql .= ", situation_percent = ".((float) $this->situation_percent);
6290
		$sql .= ", fk_unit = ".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
6291
		$sql .= ", fk_user_modif = ".((int) $user->id);
6292
6293
		// Multicurrency
6294
		$sql .= ", multicurrency_subprice=".price2num($this->multicurrency_subprice);
6295
		$sql .= ", multicurrency_total_ht=".price2num($this->multicurrency_total_ht);
6296
		$sql .= ", multicurrency_total_tva=".price2num($this->multicurrency_total_tva);
6297
		$sql .= ", multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc);
6298
6299
		$sql .= " WHERE rowid = ".((int) $this->rowid);
6300
6301
		dol_syslog(get_class($this)."::update", LOG_DEBUG);
6302
		$resql = $this->db->query($sql);
6303
		if ($resql) {
6304
			if (!$error) {
6305
				$this->id = $this->rowid;
6306
				$result = $this->insertExtraFields();
6307
				if ($result < 0) {
6308
					$error++;
6309
				}
6310
			}
6311
6312
			if (!$error && !$notrigger) {
6313
				// Call trigger
6314
				$result = $this->call_trigger('LINEBILL_MODIFY', $user);
6315
				if ($result < 0) {
6316
					$this->db->rollback();
6317
					return -2;
6318
				}
6319
				// End call triggers
6320
			}
6321
			$this->db->commit();
6322
			return 1;
6323
		} else {
6324
			$this->error = $this->db->error();
6325
			$this->db->rollback();
6326
			return -2;
6327
		}
6328
	}
6329
6330
	/**
6331
	 * Delete line in database
6332
	 *
6333
	 * @param 	User 	$tmpuser    User that deletes
6334
	 * @param 	bool 	$notrigger  false=launch triggers after, true=disable triggers
6335
	 * @return 	int		           	<0 if KO, >0 if OK
6336
	 */
6337
	public function delete($tmpuser = null, $notrigger = false)
6338
	{
6339
		global $user;
6340
6341
		$this->db->begin();
6342
6343
		// Call trigger
6344
		if (empty($notrigger)) {
6345
			$result = $this->call_trigger('LINEBILL_DELETE', $user);
6346
			if ($result < 0) {
6347
				$this->db->rollback();
6348
				return -1;
6349
			}
6350
		}
6351
		// End call triggers
6352
6353
		// extrafields
6354
		$result = $this->deleteExtraFields();
6355
		if ($result < 0) {
6356
			$this->db->rollback();
6357
			return -1;
6358
		}
6359
6360
		$sql = "DELETE FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid = ".((int) $this->rowid);
6361
6362
		if ($this->db->query($sql)) {
6363
			$this->db->commit();
6364
			return 1;
6365
		} else {
6366
			$this->error = $this->db->error()." sql=".$sql;
6367
			$this->db->rollback();
6368
			return -1;
6369
		}
6370
	}
6371
6372
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6373
	/**
6374
	 *	Update DB line fields total_xxx
6375
	 *	Used by migration
6376
	 *
6377
	 *	@return		int		<0 if KO, >0 if OK
6378
	 */
6379
	public function update_total()
6380
	{
6381
		// phpcs:enable
6382
		$this->db->begin();
6383
		dol_syslog(get_class($this)."::update_total", LOG_DEBUG);
6384
6385
		// Clean parameters
6386
		if (empty($this->total_localtax1)) {
6387
			$this->total_localtax1 = 0;
6388
		}
6389
		if (empty($this->total_localtax2)) {
6390
			$this->total_localtax2 = 0;
6391
		}
6392
6393
		// Update line in database
6394
		$sql = "UPDATE ".MAIN_DB_PREFIX."facturedet SET";
6395
		$sql .= " total_ht=".price2num($this->total_ht);
6396
		$sql .= ",total_tva=".price2num($this->total_tva);
6397
		$sql .= ",total_localtax1=".price2num($this->total_localtax1);
6398
		$sql .= ",total_localtax2=".price2num($this->total_localtax2);
6399
		$sql .= ",total_ttc=".price2num($this->total_ttc);
6400
		$sql .= " WHERE rowid = ".((int) $this->rowid);
6401
6402
		dol_syslog(get_class($this)."::update_total", LOG_DEBUG);
6403
6404
		$resql = $this->db->query($sql);
6405
		if ($resql) {
6406
			$this->db->commit();
6407
			return 1;
6408
		} else {
6409
			$this->error = $this->db->error();
6410
			$this->db->rollback();
6411
			return -2;
6412
		}
6413
	}
6414
6415
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
6416
	/**
6417
	 * Returns situation_percent of the previous line.
6418
	 * Warning: If invoice is a replacement invoice, this->fk_prev_id is id of the replaced line.
6419
	 *
6420
	 * @param  int     $invoiceid      Invoice id
6421
	 * @param  bool    $include_credit_note		Include credit note or not
6422
	 * @return int                     >= 0
6423
	 */
6424
	public function get_prev_progress($invoiceid, $include_credit_note = true)
6425
	{
6426
		// phpcs:enable
6427
		global $invoicecache;
6428
		if (is_null($this->fk_prev_id) || empty($this->fk_prev_id) || $this->fk_prev_id == "") {
6429
			return 0;
6430
		} else {
6431
			// If invoice is not a situation invoice, this->fk_prev_id is used for something else
6432
			if (!isset($invoicecache[$invoiceid])) {
6433
				$invoicecache[$invoiceid] = new Facture($this->db);
6434
				$invoicecache[$invoiceid]->fetch($invoiceid);
6435
			}
6436
			if ($invoicecache[$invoiceid]->type != Facture::TYPE_SITUATION) {
6437
				return 0;
6438
			}
6439
6440
			$sql = "SELECT situation_percent FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid = ".((int) $this->fk_prev_id);
6441
			$resql = $this->db->query($sql);
6442
			if ($resql && $this->db->num_rows($resql) > 0) {
6443
				$res = $this->db->fetch_array($resql);
6444
6445
				$returnPercent = floatval($res['situation_percent']);
6446
6447
				if ($include_credit_note) {
6448
					$sql = 'SELECT fd.situation_percent FROM '.MAIN_DB_PREFIX.'facturedet fd';
6449
					$sql .= ' JOIN '.MAIN_DB_PREFIX.'facture f ON (f.rowid = fd.fk_facture) ';
6450
					$sql .= " WHERE fd.fk_prev_id = ".((int) $this->fk_prev_id);
6451
					$sql .= " AND f.situation_cycle_ref = ".((int) $invoicecache[$invoiceid]->situation_cycle_ref); // Prevent cycle outed
6452
					$sql .= " AND f.type = ".Facture::TYPE_CREDIT_NOTE;
6453
6454
					$res = $this->db->query($sql);
6455
					if ($res) {
6456
						while ($obj = $this->db->fetch_object($res)) {
6457
							$returnPercent = $returnPercent + floatval($obj->situation_percent);
6458
						}
6459
					} else {
6460
						dol_print_error($this->db);
6461
					}
6462
				}
6463
6464
				return $returnPercent;
6465
			} else {
6466
				$this->error = $this->db->error();
6467
				dol_syslog(get_class($this)."::select Error ".$this->error, LOG_ERR);
6468
				$this->db->rollback();
6469
				return -1;
6470
			}
6471
		}
6472
	}
6473
}
6474