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

Facture::displayRetainedWarranty()   B

Complexity

Conditions 10

Size

Total Lines 32
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 15
c 0
b 0
f 0
nop 0
dl 0
loc 32
rs 7.6666

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/* Copyright (C) 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