Passed
Branch develop (4d916f)
by
unknown
32:21
created

Facture::updateline()   F

Complexity

Conditions 52

Size

Total Lines 201
Code Lines 133

Duplication

Lines 0
Ratio 0 %

Importance

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