Passed
Branch develop (01f96b)
by
unknown
30:45
created

Facture::getRetainedWarrantyAmount()   C

Complexity

Conditions 13

Size

Total Lines 55
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 28
c 0
b 0
f 0
nop 1
dl 0
loc 55
rs 6.6166

How to fix   Long Method    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-2016 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)) require_once DOL_DOCUMENT_ROOT.'/core/class/html.formaccounting.class.php';
49
if (!empty($conf->accounting->enabled)) require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php';
50
51
/**
52
 *	Class to manage invoices
53
 */
54
class Facture extends CommonInvoice
55
{
56
	/**
57
	 * @var string ID to identify managed object
58
	 */
59
	public $element = 'facture';
60
61
	/**
62
	 * @var string Name of table without prefix where object is stored
63
	 */
64
	public $table_element = 'facture';
65
66
	/**
67
	 * @var int    Name of subtable line
68
	 */
69
	public $table_element_line = 'facturedet';
70
71
	/**
72
	 * @var int Field with ID of parent key if this field has a parent
73
	 */
74
	public $fk_element = 'fk_facture';
75
76
	/**
77
	 * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
78
	 */
79
	public $picto = 'bill';
80
81
	/**
82
	 * 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
83
	 * @var int
84
	 */
85
	public $ismultientitymanaged = 1;
86
87
	/**
88
	 * 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
89
	 * @var integer
90
	 */
91
	public $restrictiononfksoc = 1;
92
93
	/**
94
	 * {@inheritdoc}
95
	 */
96
	protected $table_ref_field = 'ref';
97
98
	public $socid;
99
100
	public $author;
101
102
	/**
103
     * @var int ID
104
     */
105
	public $fk_user_author;
106
107
	/**
108
     * @var int ID
109
     */
110
	public $fk_user_valid;
111
112
	public $date; // Date invoice
113
	public $datem;
114
	public $ref_client;
115
116
	/**
117
	 * @var int Ref Int
118
	 * @deprecated
119
	 */
120
	public $ref_int; // deprecated
121
122
	//Check constants for types
123
	public $type = self::TYPE_STANDARD;
124
125
	//var $amount;
126
	public $remise_absolue;
127
	public $remise_percent;
128
	public $total_ht = 0;
129
	public $total_tva = 0;
130
	public $total_localtax1 = 0;
131
	public $total_localtax2 = 0;
132
	public $total_ttc = 0;
133
	public $revenuestamp;
134
135
	//! Fermeture apres paiement partiel: discount_vat, badcustomer, abandon
136
	//! Fermeture alors que aucun paiement: replaced (si remplace), abandon
137
	public $close_code;
138
	//! Commentaire si mis a paye sans paiement complet
139
	public $close_note;
140
	//! 1 if invoice paid COMPLETELY, 0 otherwise (do not use it anymore, use statut and close_code)
141
	public $paye;
142
	//! key of module source when invoice generated from a dedicated module ('cashdesk', 'takepos', ...)
143
	public $module_source;
144
	//! key of pos source ('0', '1', ...)
145
	public $pos_source;
146
	//! id of template invoice when generated from a template invoice
147
	public $fk_fac_rec_source;
148
	//! id of source invoice if replacement invoice or credit note
149
	public $fk_facture_source;
150
	public $linked_objects = array();
151
	public $date_lim_reglement;
152
	public $cond_reglement_code; // Code in llx_c_paiement
153
	public $mode_reglement_code; // Code in llx_c_paiement
154
155
	/**
156
     * @var int ID Field to store bank id to use when payment mode is withdraw
157
     */
158
	public $fk_bank;
159
160
	/**
161
	 * @deprecated
162
	 */
163
	public $products = array();
164
165
	/**
166
	 * @var FactureLigne[]
167
	 */
168
	public $lines = array();
169
170
	public $line;
171
	public $extraparams = array();
172
	public $specimen;
173
174
	public $fac_rec;
175
176
	// Multicurrency
177
	/**
178
     * @var int ID
179
     */
180
	public $fk_multicurrency;
181
182
	public $multicurrency_code;
183
	public $multicurrency_tx;
184
	public $multicurrency_total_ht;
185
	public $multicurrency_total_tva;
186
	public $multicurrency_total_ttc;
187
188
	/**
189
	 * @var int Situation cycle reference number
190
	 */
191
	public $situation_cycle_ref;
192
193
	/**
194
	 * @var int Situation counter inside the cycle
195
	 */
196
	public $situation_counter;
197
198
	/**
199
	 * @var int Final situation flag
200
	 */
201
	public $situation_final;
202
203
	/**
204
	 * @var array Table of previous situations
205
	 */
206
	public $tab_previous_situation_invoice = array();
207
208
	/**
209
	 * @var array Table of next situations
210
	 */
211
	public $tab_next_situation_invoice = array();
212
213
	public $oldcopy;
214
215
	/**
216
	 * @var double percentage of retainage
217
	 */
218
	public $retained_warranty;
219
220
	/**
221
	 * @var int timestamp of date limit of retainage
222
	 */
223
	public $retained_warranty_date_limit;
224
225
	/**
226
	 * @var int Code in llx_c_paiement
227
	 */
228
	public $retained_warranty_fk_cond_reglement;
229
230
231
	/**
232
	 *  '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')
233
	 *         Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)"
234
	 *  'label' the translation key.
235
	 *  'enabled' is a condition when the field must be managed.
236
	 *  'position' is the sort order of field.
237
	 *  'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
238
	 *  '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)
239
	 *  'noteditable' says if field is not editable (1 or 0)
240
	 *  '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.
241
	 *  'index' if we want an index in database.
242
	 *  'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommanded to name the field fk_...).
243
	 *  'searchall' is 1 if we want to search in this field when making a search from the quick search button.
244
	 *  '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).
245
	 *  'css' is the CSS style to use on field. For example: 'maxwidth200'
246
	 *  'help' is a string visible as a tooltip on field
247
	 *  'showoncombobox' if value of the field must be visible into the label of the combobox that list record
248
	 *  '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.
249
	 *  'arraykeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel")
250
	 *  'comment' is not used. You can store here any text of your choice. It is not used by application.
251
	 *
252
	 *  Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
253
	 */
254
255
	// BEGIN MODULEBUILDER PROPERTIES
256
	/**
257
	 * @var array  Array with all fields and their property. Do not use it as a static var. It may be modified by constructor.
258
	 */
259
	public $fields = array(
260
		'rowid' =>array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>10),
261
		'ref' =>array('type'=>'varchar(30)', 'label'=>'Ref', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'showoncombobox'=>1, 'position'=>15),
262
		'entity' =>array('type'=>'integer', 'label'=>'Entity', 'default'=>1, 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>20, 'index'=>1),
263
		'ref_ext' =>array('type'=>'varchar(255)', 'label'=>'Ref ext', 'enabled'=>1, 'visible'=>0, 'position'=>25),
264
		'ref_int' =>array('type'=>'varchar(255)', 'label'=>'Ref int', 'enabled'=>1, 'visible'=>0, 'position'=>30), // deprecated
265
		'type' =>array('type'=>'smallint(6)', 'label'=>'Type', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>35),
266
		'ref_client' =>array('type'=>'varchar(255)', 'label'=>'Ref client', 'enabled'=>1, 'visible'=>-1, 'position'=>40),
267
		//'increment' =>array('type'=>'varchar(10)', 'label'=>'Increment', 'enabled'=>1, 'visible'=>-1, 'position'=>45),
268
		'fk_soc' =>array('type'=>'integer:Societe:societe/class/societe.class.php', 'label'=>'ThirdParty', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>50),
269
		'datec' =>array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>1, 'visible'=>-1, 'position'=>55),
270
		'datef' =>array('type'=>'date', 'label'=>'DateInvoice', 'enabled'=>1, 'visible'=>-1, 'position'=>60),
271
		'date_valid' =>array('type'=>'date', 'label'=>'DateValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>65),
272
		'date_closing' =>array('type'=>'datetime', 'label'=>'Date closing', 'enabled'=>1, 'visible'=>-1, 'position'=>70),
273
		'tms' =>array('type'=>'timestamp', 'label'=>'DateModification', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>75),
274
		'paye' =>array('type'=>'smallint(6)', 'label'=>'InvoicePaidCompletely', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>80),
275
		//'amount' =>array('type'=>'double(24,8)', 'label'=>'Amount', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>85),
276
		'remise_percent' =>array('type'=>'double', 'label'=>'RelativeDiscount', 'enabled'=>1, 'visible'=>-1, 'position'=>90),
277
		'remise_absolue' =>array('type'=>'double', 'label'=>'CustomerRelativeDiscount', 'enabled'=>1, 'visible'=>-1, 'position'=>95),
278
		//'remise' =>array('type'=>'double', 'label'=>'Remise', 'enabled'=>1, 'visible'=>-1, 'position'=>100),
279
		'close_code' =>array('type'=>'varchar(16)', 'label'=>'EarlyClosingReason', 'enabled'=>1, 'visible'=>-1, 'position'=>105),
280
		'close_note' =>array('type'=>'varchar(128)', 'label'=>'EarlyClosingComment', 'enabled'=>1, 'visible'=>-1, 'position'=>110),
281
		'tva' =>array('type'=>'double(24,8)', 'label'=>'TotalVAT', 'enabled'=>1, 'visible'=>-1, 'position'=>115, 'isameasure'=>1),
282
		'localtax1' =>array('type'=>'double(24,8)', 'label'=>'LT1', 'enabled'=>1, 'visible'=>-1, 'position'=>120, 'isameasure'=>1),
283
		'localtax2' =>array('type'=>'double(24,8)', 'label'=>'LT2', 'enabled'=>1, 'visible'=>-1, 'position'=>125, 'isameasure'=>1),
284
		'revenuestamp' =>array('type'=>'double(24,8)', 'label'=>'RevenueStamp', 'enabled'=>1, 'visible'=>-1, 'position'=>130, 'isameasure'=>1),
285
		'total' =>array('type'=>'double(24,8)', 'label'=>'TotalHT', 'enabled'=>1, 'visible'=>-1, 'position'=>135, 'isameasure'=>1),
286
		'total_ttc' =>array('type'=>'double(24,8)', 'label'=>'TotalTTC', 'enabled'=>1, 'visible'=>-1, 'position'=>140, 'isameasure'=>1),
287
		'fk_user_author' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserAuthor', 'enabled'=>1, 'visible'=>-1, 'position'=>150),
288
		'fk_user_modif' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserModif', 'enabled'=>1, 'visible'=>-2, 'notnull'=>-1, 'position'=>155),
289
		'fk_user_valid' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserValidation', 'enabled'=>1, 'visible'=>-1, 'position'=>160),
290
		'fk_user_closing' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserClosing', 'enabled'=>1, 'visible'=>-1, 'position'=>165),
291
		'fk_facture_source' =>array('type'=>'integer', 'label'=>'SourceInvoice', 'enabled'=>1, 'visible'=>-1, 'position'=>170),
292
		'fk_projet' =>array('type'=>'integer:Project:projet/class/project.class.php:1:fk_statut=1', 'label'=>'Project', 'enabled'=>1, 'visible'=>-1, 'position'=>175),
293
		'fk_account' =>array('type'=>'integer', 'label'=>'Fk account', 'enabled'=>1, 'visible'=>-1, 'position'=>180),
294
		'fk_currency' =>array('type'=>'varchar(3)', 'label'=>'CurrencyCode', 'enabled'=>1, 'visible'=>-1, 'position'=>185),
295
		'fk_cond_reglement' =>array('type'=>'integer', 'label'=>'PaymentTerm', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>190),
296
		'fk_mode_reglement' =>array('type'=>'integer', 'label'=>'PaymentMode', 'enabled'=>1, 'visible'=>-1, 'position'=>195),
297
		'date_lim_reglement' =>array('type'=>'date', 'label'=>'DateDue', 'enabled'=>1, 'visible'=>-1, 'position'=>200),
298
		'note_private' =>array('type'=>'text', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>205),
299
		'note_public' =>array('type'=>'text', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>210),
300
		'model_pdf' =>array('type'=>'varchar(255)', 'label'=>'Model pdf', 'enabled'=>1, 'visible'=>0, 'position'=>215),
301
		'extraparams' =>array('type'=>'varchar(255)', 'label'=>'Extraparams', 'enabled'=>1, 'visible'=>-1, 'position'=>225),
302
		'situation_cycle_ref' =>array('type'=>'smallint(6)', 'label'=>'Situation cycle ref', 'enabled'=>'$conf->global->INVOICE_USE_SITUATION', 'visible'=>-1, 'position'=>230),
303
		'situation_counter' =>array('type'=>'smallint(6)', 'label'=>'Situation counter', 'enabled'=>'$conf->global->INVOICE_USE_SITUATION', 'visible'=>-1, 'position'=>235),
304
		'situation_final' =>array('type'=>'smallint(6)', 'label'=>'Situation final', 'enabled'=>'empty($conf->global->INVOICE_USE_SITUATION) ? 0 : 1', 'visible'=>-1, 'position'=>240),
305
		'retained_warranty' =>array('type'=>'double', 'label'=>'Retained warranty', 'enabled'=>'$conf->global->INVOICE_USE_RETAINED_WARRANTY', 'visible'=>-1, 'position'=>245),
306
		'retained_warranty_date_limit' =>array('type'=>'date', 'label'=>'Retained warranty date limit', 'enabled'=>'$conf->global->INVOICE_USE_RETAINED_WARRANTY', 'visible'=>-1, 'position'=>250),
307
		'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),
308
		'fk_incoterms' =>array('type'=>'integer', 'label'=>'IncotermCode', 'enabled'=>'$conf->incoterm->enabled', 'visible'=>-1, 'position'=>260),
309
		'location_incoterms' =>array('type'=>'varchar(255)', 'label'=>'IncotermLabel', 'enabled'=>'$conf->incoterm->enabled', 'visible'=>-1, 'position'=>265),
310
		'date_pointoftax' =>array('type'=>'date', 'label'=>'DatePointOfTax', 'enabled'=>'$conf->global->INVOICE_POINTOFTAX_DATE', 'visible'=>-1, 'position'=>270),
311
		'fk_multicurrency' =>array('type'=>'integer', 'label'=>'MulticurrencyID', 'enabled'=>'$conf->multicurrency->enabled', 'visible'=>-1, 'position'=>275),
312
		'multicurrency_code' =>array('type'=>'varchar(255)', 'label'=>'MulticurrencyCurrency', 'enabled'=>'$conf->multicurrency->enabled', 'visible'=>-1, 'position'=>280),
313
		'multicurrency_tx' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyRate', 'enabled'=>'$conf->multicurrency->enabled', 'visible'=>-1, 'position'=>285, 'isameasure'=>1),
314
		'multicurrency_total_ht' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountHT', 'enabled'=>'$conf->multicurrency->enabled', 'visible'=>-1, 'position'=>290, 'isameasure'=>1),
315
		'multicurrency_total_tva' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountVAT', 'enabled'=>'$conf->multicurrency->enabled', 'visible'=>-1, 'position'=>295, 'isameasure'=>1),
316
		'multicurrency_total_ttc' =>array('type'=>'double(24,8)', 'label'=>'MulticurrencyAmountTTC', 'enabled'=>'$conf->multicurrency->enabled', 'visible'=>-1, 'position'=>300, 'isameasure'=>1),
317
		'fk_fac_rec_source' =>array('type'=>'integer', 'label'=>'RecurringInvoiceSource', 'enabled'=>1, 'visible'=>-1, 'position'=>305),
318
		'last_main_doc' =>array('type'=>'varchar(255)', 'label'=>'LastMainDoc', 'enabled'=>1, 'visible'=>-1, 'position'=>310),
319
		'module_source' =>array('type'=>'varchar(32)', 'label'=>'POSModule', 'enabled'=>1, 'visible'=>-1, 'position'=>315),
320
		'pos_source' =>array('type'=>'varchar(32)', 'label'=>'POSTerminal', 'enabled'=>1, 'visible'=>-1, 'position'=>320),
321
		'fk_statut' =>array('type'=>'smallint(6)', 'label'=>'Status', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>500, 'arrayofkeyval'=>array(0=>'Draft', 1=>'Validated', 2=>'Paid', 3=>'Abandonned')),
322
		'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'position'=>900),
323
	);
324
	// END MODULEBUILDER PROPERTIES
325
326
    /**
327
     * Standard invoice
328
     */
329
    const TYPE_STANDARD = 0;
330
331
    /**
332
     * Replacement invoice
333
     */
334
    const TYPE_REPLACEMENT = 1;
335
336
    /**
337
     * Credit note invoice
338
     */
339
    const TYPE_CREDIT_NOTE = 2;
340
341
    /**
342
     * Deposit invoice
343
     */
344
    const TYPE_DEPOSIT = 3;
345
346
    /**
347
     * Proforma invoice (should not be used. a proforma is an order)
348
     */
349
    const TYPE_PROFORMA = 4;
350
351
	/**
352
	 * Situation invoice
353
	 */
354
	const TYPE_SITUATION = 5;
355
356
	/**
357
	 * Draft status
358
	 */
359
	const STATUS_DRAFT = 0;
360
361
	/**
362
	 * Validated (need to be paid)
363
	 */
364
	const STATUS_VALIDATED = 1;
365
366
	/**
367
	 * Classified paid.
368
	 * If paid partially, $this->close_code can be:
369
	 * - CLOSECODE_DISCOUNTVAT
370
	 * - CLOSECODE_BADDEBT
371
	 * If paid completely, this->close_code will be null
372
	 */
373
	const STATUS_CLOSED = 2;
374
375
	/**
376
	 * Classified abandoned and no payment done.
377
	 * $this->close_code can be:
378
	 * - CLOSECODE_BADDEBT
379
	 * - CLOSECODE_ABANDONED
380
	 * - CLOSECODE_REPLACED
381
	 */
382
	const STATUS_ABANDONED = 3;
383
384
	const CLOSECODE_DISCOUNTVAT = 'discount_vat'; // Abandonned remain - escompte
385
	const CLOSECODE_BADDEBT = 'badcustomer'; // Abandonned - bad
386
	const CLOSECODE_ABANDONED = 'abandon'; // Abandonned - other
387
	const CLOSECODE_REPLACED = 'replaced'; // Closed after doing a replacement invoice
388
389
390
	/**
391
	 * 	Constructor
392
	 *
393
	 * 	@param	DoliDB		$db			Database handler
394
	 */
395
	public function __construct($db)
396
	{
397
		$this->db = $db;
398
	}
399
400
	/**
401
	 *	Create invoice in database.
402
	 *  Note: this->ref can be set or empty. If empty, we will use "(PROV999)"
403
	 *  Note: this->fac_rec must be set to create invoice from a recurring invoice
404
	 *
405
	 *	@param	User	$user      		Object user that create
406
	 *	@param  int		$notrigger		1=Does not execute triggers, 0 otherwise
407
	 * 	@param	int		$forceduedate	If set, do not recalculate due date from payment condition but force it with value
408
	 *	@return	int						<0 if KO, >0 if OK
409
	 */
410
    public function create(User $user, $notrigger = 0, $forceduedate = 0)
411
	{
412
		global $langs, $conf, $mysoc, $hookmanager;
413
		$error = 0;
414
415
		// Clean parameters
416
		if (empty($this->type)) $this->type = self::TYPE_STANDARD;
417
		$this->ref_client = trim($this->ref_client);
418
		$this->note = (isset($this->note) ? trim($this->note) : trim($this->note_private)); // deprecated
419
		$this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note_private));
420
		$this->note_public = trim($this->note_public);
421
		if (!$this->cond_reglement_id) $this->cond_reglement_id = 0;
422
		if (!$this->mode_reglement_id) $this->mode_reglement_id = 0;
423
		$this->brouillon = 1;
424
425
		// Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
426
		if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code);
427
		else $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
428
		if (empty($this->fk_multicurrency))
429
		{
430
			$this->multicurrency_code = $conf->currency;
431
			$this->fk_multicurrency = 0;
432
			$this->multicurrency_tx = 1;
433
		}
434
435
		dol_syslog(get_class($this)."::create user=".$user->id." date=".$this->date);
436
437
		// Check parameters
438
		if (empty($this->date))
439
		{
440
			$this->error = "Try to create an invoice with an empty parameter (date)";
441
			dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
442
			return -3;
443
		}
444
		$soc = new Societe($this->db);
445
		$result = $soc->fetch($this->socid);
446
		if ($result < 0)
447
		{
448
			$this->error = "Failed to fetch company: ".$soc->error;
449
			dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
450
			return -2;
451
		}
452
453
		$now = dol_now();
454
455
		$this->db->begin();
456
457
		$originaldatewhen = null;
458
		$nextdatewhen = null;
459
		$previousdaynextdatewhen = null;
460
461
		// Create invoice from a template invoice
462
		if ($this->fac_rec > 0)
463
		{
464
		    $this->fk_fac_rec_source = $this->fac_rec;
465
466
			require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture-rec.class.php';
467
			$_facrec = new FactureRec($this->db);
468
			$result = $_facrec->fetch($this->fac_rec);
469
			$result = $_facrec->fetchObjectLinked(null, '', null, '', 'OR', 1, 'sourcetype', 0); // This load $_facrec->linkedObjectsIds
470
471
			// Define some dates
472
			$originaldatewhen = $_facrec->date_when;
473
			$nextdatewhen = dol_time_plus_duree($originaldatewhen, $_facrec->frequency, $_facrec->unit_frequency);
474
			$previousdaynextdatewhen = dol_time_plus_duree($nextdatewhen, -1, 'd');
475
476
			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.
477
			{
478
				$this->socid = $_facrec->socid;
479
			}
480
			$this->entity            = $_facrec->entity; // Invoice created in same entity than template
481
482
			// 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
483
			$this->fk_project        = GETPOST('projectid', 'int') > 0 ? ((int) GETPOST('projectid', 'int')) : $_facrec->fk_project;
484
			$this->note_public       = GETPOST('note_public', 'none') ? GETPOST('note_public', 'none') : $_facrec->note_public;
485
			$this->note_private      = GETPOST('note_private', 'none') ? GETPOST('note_private', 'none') : $_facrec->note_private;
486
			$this->modelpdf          = GETPOST('model', 'alpha') ? GETPOST('model', 'alpha') : $_facrec->modelpdf;
487
			$this->cond_reglement_id = GETPOST('cond_reglement_id', 'int') > 0 ? ((int) GETPOST('cond_reglement_id', 'int')) : $_facrec->cond_reglement_id;
488
			$this->mode_reglement_id = GETPOST('mode_reglement_id', 'int') > 0 ? ((int) GETPOST('mode_reglement_id', 'int')) : $_facrec->mode_reglement_id;
489
			$this->fk_account        = GETPOST('fk_account') > 0 ? ((int) GETPOST('fk_account')) : $_facrec->fk_account;
490
491
			// Set here to have this defined for substitution into notes, should be recalculated after adding lines to get same result
492
			$this->total_ht          = $_facrec->total_ht;
493
			$this->total_ttc         = $_facrec->total_ttc;
494
495
			// Fields always coming from template
496
			$this->remise_absolue    = $_facrec->remise_absolue;
497
			$this->remise_percent    = $_facrec->remise_percent;
498
			$this->fk_incoterms = $_facrec->fk_incoterms;
499
			$this->location_incoterms = $_facrec->location_incoterms;
500
501
			// Clean parameters
502
			if (!$this->type) $this->type = self::TYPE_STANDARD;
503
			$this->ref_client = trim($this->ref_client);
504
			$this->note_public = trim($this->note_public);
505
			$this->note_private = trim($this->note_private);
506
		    $this->note_private = dol_concatdesc($this->note_private, $langs->trans("GeneratedFromRecurringInvoice", $_facrec->ref));
507
508
		    $this->array_options = $_facrec->array_options;
509
510
			//if (! $this->remise) $this->remise = 0;
511
			if (!$this->mode_reglement_id) $this->mode_reglement_id = 0;
512
			$this->brouillon = 1;
513
514
			$this->linked_objects = $_facrec->linkedObjectsIds;
515
			// We do not add link to template invoice or next invoice will be linked to all generated invoices
516
			//$this->linked_objects['facturerec'][0] = $this->fac_rec;
517
518
			$forceduedate = $this->calculate_date_lim_reglement();
519
520
			// For recurring invoices, update date and number of last generation of recurring template invoice, before inserting new invoice
521
			if ($_facrec->frequency > 0)
522
			{
523
			    dol_syslog("This is a recurring invoice so we set date_last_gen and next date_when");
524
			    if (empty($_facrec->date_when)) $_facrec->date_when = $now;
525
                $next_date = $_facrec->getNextDate(); // Calculate next date
526
                $result = $_facrec->setValueFrom('date_last_gen', $now, '', null, 'date', '', $user, '');
527
                //$_facrec->setValueFrom('nb_gen_done', $_facrec->nb_gen_done + 1);		// Not required, +1 already included into setNextDate when second param is 1.
528
                $result = $_facrec->setNextDate($next_date, 1);
529
			}
530
531
			// Define lang of customer
532
			$outputlangs = $langs;
533
			$newlang = '';
534
535
			if ($conf->global->MAIN_MULTILANGS && empty($newlang) && isset($this->thirdparty->default_lang)) $newlang = $this->thirdparty->default_lang; // for proposal, order, invoice, ...
536
			if ($conf->global->MAIN_MULTILANGS && empty($newlang) && isset($this->default_lang)) $newlang = $this->default_lang; // for thirdparty
537
			if (!empty($newlang))
538
			{
539
			    $outputlangs = new Translate("", $conf);
540
			    $outputlangs->setDefaultLang($newlang);
541
			}
542
543
			// Array of possible substitutions (See also file mailing-send.php that should manage same substitutions)
544
			$substitutionarray = getCommonSubstitutionArray($outputlangs, 0, null, $this);
545
			$substitutionarray['__INVOICE_PREVIOUS_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%m');
546
			$substitutionarray['__INVOICE_MONTH__'] = dol_print_date($this->date, '%m');
547
			$substitutionarray['__INVOICE_NEXT_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%m');
548
			$substitutionarray['__INVOICE_PREVIOUS_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%B');
549
			$substitutionarray['__INVOICE_MONTH_TEXT__'] = dol_print_date($this->date, '%B');
550
			$substitutionarray['__INVOICE_NEXT_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%B');
551
			$substitutionarray['__INVOICE_PREVIOUS_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'y'), '%Y');
552
			$substitutionarray['__INVOICE_YEAR__'] = dol_print_date($this->date, '%Y');
553
			$substitutionarray['__INVOICE_NEXT_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'y'), '%Y');
554
			// Only for template invoice
555
			$substitutionarray['__INVOICE_DATE_NEXT_INVOICE_BEFORE_GEN__'] = dol_print_date($originaldatewhen, 'dayhour');
556
			$substitutionarray['__INVOICE_DATE_NEXT_INVOICE_AFTER_GEN__'] = dol_print_date($nextdatewhen, 'dayhour');
557
			$substitutionarray['__INVOICE_PREVIOUS_DATE_NEXT_INVOICE_AFTER_GEN__'] = dol_print_date($previousdaynextdatewhen, 'dayhour');
558
			$substitutionarray['__INVOICE_COUNTER_CURRENT__'] = $_facrec->nb_gen_done;
559
			$substitutionarray['__INVOICE_COUNTER_MAX__'] = $_facrec->nb_gen_max;
560
561
			//var_dump($substitutionarray);exit;
562
563
			complete_substitutions_array($substitutionarray, $outputlangs);
564
565
			$this->note_public = make_substitutions($this->note_public, $substitutionarray);
566
			$this->note_private = make_substitutions($this->note_private, $substitutionarray);
567
		}
568
569
		// Define due date if not already defined
570
        if (empty($forceduedate)) {
571
            $duedate = $this->calculate_date_lim_reglement();
572
            /*if ($duedate < 0) {	Regression, a date can be negative if before 1970.
573
                dol_syslog(__METHOD__ . ' Error in calculate_date_lim_reglement. We got ' . $duedate, LOG_ERR);
574
                return -1;
575
            }*/
576
            $this->date_lim_reglement = $duedate;
577
        } else {
578
            $this->date_lim_reglement = $forceduedate;
579
        }
580
581
		// Insert into database
582
		$socid = $this->socid;
583
584
		$sql = "INSERT INTO ".MAIN_DB_PREFIX."facture (";
585
		$sql .= " ref";
586
		$sql .= ", entity";
587
		$sql .= ", ref_ext";
588
		$sql .= ", type";
589
		$sql .= ", fk_soc";
590
		$sql .= ", datec";
591
		$sql .= ", remise_absolue";
592
		$sql .= ", remise_percent";
593
		$sql .= ", datef";
594
		$sql .= ", date_pointoftax";
595
		$sql .= ", note_private";
596
		$sql .= ", note_public";
597
		$sql .= ", ref_client, ref_int";
598
        $sql .= ", fk_account";
599
		$sql .= ", module_source, pos_source, fk_fac_rec_source, fk_facture_source, fk_user_author, fk_projet";
600
		$sql .= ", fk_cond_reglement, fk_mode_reglement, date_lim_reglement, model_pdf";
601
		$sql .= ", situation_cycle_ref, situation_counter, situation_final";
602
		$sql .= ", fk_incoterms, location_incoterms";
603
        $sql .= ", fk_multicurrency";
604
        $sql .= ", multicurrency_code";
605
        $sql .= ", multicurrency_tx";
606
        $sql .= ", retained_warranty";
607
        $sql .= ", retained_warranty_date_limit";
608
        $sql .= ", retained_warranty_fk_cond_reglement";
609
		$sql .= ")";
610
		$sql .= " VALUES (";
611
		$sql .= "'(PROV)'";
612
		$sql .= ", ".setEntity($this);
613
		$sql .= ", ".($this->ref_ext ? "'".$this->db->escape($this->ref_ext)."'" : "null");
614
		$sql .= ", '".$this->db->escape($this->type)."'";
615
		$sql .= ", '".$socid."'";
616
		$sql .= ", '".$this->db->idate($now)."'";
617
		$sql .= ", ".($this->remise_absolue > 0 ? $this->remise_absolue : 'NULL');
618
		$sql .= ", ".($this->remise_percent > 0 ? $this->remise_percent : 'NULL');
619
		$sql .= ", '".$this->db->idate($this->date)."'";
620
		$sql .= ", ".(strval($this->date_pointoftax) != '' ? "'".$this->db->idate($this->date_pointoftax)."'" : 'null');
621
		$sql .= ", ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : "null");
622
		$sql .= ", ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : "null");
623
		$sql .= ", ".($this->ref_client ? "'".$this->db->escape($this->ref_client)."'" : "null");
624
		$sql .= ", ".($this->ref_int ? "'".$this->db->escape($this->ref_int)."'" : "null");
625
		$sql .= ", ".($this->fk_account > 0 ? $this->fk_account : 'NULL');
626
		$sql .= ", ".($this->module_source ? "'".$this->db->escape($this->module_source)."'" : "null");
627
		$sql .= ", ".($this->pos_source != '' ? "'".$this->db->escape($this->pos_source)."'" : "null");
628
		$sql .= ", ".($this->fk_fac_rec_source ? "'".$this->db->escape($this->fk_fac_rec_source)."'" : "null");
629
		$sql .= ", ".($this->fk_facture_source ? "'".$this->db->escape($this->fk_facture_source)."'" : "null");
630
		$sql .= ", ".($user->id > 0 ? "'".$user->id."'" : "null");
631
		$sql .= ", ".($this->fk_project ? $this->fk_project : "null");
632
		$sql .= ", ".$this->cond_reglement_id;
633
		$sql .= ", ".$this->mode_reglement_id;
634
		$sql .= ", '".$this->db->idate($this->date_lim_reglement)."', '".$this->db->escape($this->modelpdf)."'";
635
		$sql .= ", ".($this->situation_cycle_ref ? "'".$this->db->escape($this->situation_cycle_ref)."'" : "null");
636
		$sql .= ", ".($this->situation_counter ? "'".$this->db->escape($this->situation_counter)."'" : "null");
637
		$sql .= ", ".($this->situation_final ? $this->situation_final : 0);
638
		$sql .= ", ".(int) $this->fk_incoterms;
639
        $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
640
		$sql .= ", ".(int) $this->fk_multicurrency;
641
		$sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
642
		$sql .= ", ".(double) $this->multicurrency_tx;
643
		$sql .= ", ".(empty($this->retained_warranty) ? "0" : $this->db->escape($this->retained_warranty));
644
		$sql .= ", ".(!empty($this->retained_warranty_date_limit) ? "'".$this->db->idate($this->retained_warranty_date_limit)."'" : 'NULL');
645
		$sql .= ", ".(int) $this->retained_warranty_fk_cond_reglement;
646
		$sql .= ")";
647
648
		$resql = $this->db->query($sql);
649
		if ($resql)
650
		{
651
			$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'facture');
652
653
			// Update ref with new one
654
			$this->ref = '(PROV'.$this->id.')';
655
			$sql = 'UPDATE '.MAIN_DB_PREFIX."facture SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".$this->id;
656
657
			$resql = $this->db->query($sql);
658
			if (!$resql) $error++;
659
660
			if (!empty($this->linkedObjectsIds) && empty($this->linked_objects))	// To use new linkedObjectsIds instead of old linked_objects
661
			{
662
				$this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
663
			}
664
665
			// Add object linked
666
			if (!$error && $this->id && is_array($this->linked_objects) && !empty($this->linked_objects))
667
			{
668
				foreach ($this->linked_objects as $origin => $tmp_origin_id)
669
				{
670
				    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, ...))
671
				    {
672
				        foreach ($tmp_origin_id as $origin_id)
673
				        {
674
				            $ret = $this->add_object_linked($origin, $origin_id);
675
				            if (!$ret)
676
				            {
677
				                $this->error = $this->db->lasterror();
678
				                $error++;
679
				            }
680
				        }
681
				    }
682
				    else                                // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
683
				    {
684
				        $origin_id = $tmp_origin_id;
685
    					$ret = $this->add_object_linked($origin, $origin_id);
686
    					if (!$ret)
687
    					{
688
    						$this->error = $this->db->lasterror();
689
    						$error++;
690
    					}
691
				    }
692
				}
693
			}
694
695
			// Propagate contacts
696
			if (!$error && $this->id && !empty($conf->global->MAIN_PROPAGATE_CONTACTS_FROM_ORIGIN) && !empty($this->origin) && !empty($this->origin_id))   // Get contact from origin object
697
			{
698
				$originforcontact = $this->origin;
699
				$originidforcontact = $this->origin_id;
700
				if ($originforcontact == 'shipping')     // shipment and order share the same contacts. If creating from shipment we take data of order
701
				{
702
				    require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
703
				    $exp = new Expedition($this->db);
704
				    $exp->fetch($this->origin_id);
705
				    $exp->fetchObjectLinked(null, '', null, '', 'OR', 1, 'sourcetype', 0);
706
				    if (count($exp->linkedObjectsIds['commande']) > 0)
707
				    {
708
				        foreach ($exp->linkedObjectsIds['commande'] as $key => $value)
709
				        {
710
				            $originforcontact = 'commande';
711
				            if (is_object($value)) $originidforcontact = $value->id;
712
				            else $originidforcontact = $value;
713
				            break; // We take first one
714
				        }
715
				    }
716
				}
717
718
				$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";
719
				$sqlcontact .= " WHERE element_id = ".$originidforcontact." AND ec.fk_c_type_contact = ctc.rowid AND ctc.element = '".$originforcontact."'";
720
721
				$resqlcontact = $this->db->query($sqlcontact);
722
				if ($resqlcontact)
723
				{
724
				    while ($objcontact = $this->db->fetch_object($resqlcontact))
725
				    {
726
				        //print $objcontact->code.'-'.$objcontact->source.'-'.$objcontact->fk_socpeople."\n";
727
				        $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
728
				    }
729
				}
730
				else dol_print_error($resqlcontact);
731
			}
732
733
			/*
734
			 *  Insert lines of invoices, if not from template invoice, into database
735
			 */
736
			if (!$error && empty($this->fac_rec) && count($this->lines) && is_object($this->lines[0]))	// If this->lines is array of InvoiceLines (preferred mode)
737
			{
738
				$fk_parent_line = 0;
739
740
				dol_syslog("There is ".count($this->lines)." lines that are invoice lines objects");
741
				foreach ($this->lines as $i => $val)
742
				{
743
					$newinvoiceline = $this->lines[$i];
744
					$newinvoiceline->fk_facture = $this->id;
745
746
					$newinvoiceline->origin = $this->lines[$i]->element;
747
					$newinvoiceline->origin_id = $this->lines[$i]->id;
748
749
					// Auto set date of service ?
750
					if ($this->lines[$i]->date_start_fill == 1 && $originaldatewhen)			// $originaldatewhen is defined when generating from recurring invoice only
0 ignored issues
show
Bug introduced by
The property date_start_fill does not exist on FactureLigne. Did you mean date_start?
Loading history...
751
					{
752
						$newinvoiceline->date_start = $originaldatewhen;
753
					}
754
					if ($this->lines[$i]->date_end_fill == 1 && $previousdaynextdatewhen)	// $previousdaynextdatewhen is defined when generating from recurring invoice only
0 ignored issues
show
Bug introduced by
The property date_end_fill does not exist on FactureLigne. Did you mean date_end?
Loading history...
755
					{
756
						$newinvoiceline->date_end = $previousdaynextdatewhen;
757
					}
758
759
					if ($result >= 0)
760
					{
761
						// Reset fk_parent_line for no child products and special product
762
						if (($newinvoiceline->product_type != 9 && empty($newinvoiceline->fk_parent_line)) || $newinvoiceline->product_type == 9) {
763
							$fk_parent_line = 0;
764
						}
765
766
						$newinvoiceline->fk_parent_line = $fk_parent_line;
767
768
						if ($this->type === Facture::TYPE_REPLACEMENT && $newinvoiceline->fk_remise_except) {
769
                            $discount = new DiscountAbsolute($this->db);
770
                            $discount->fetch($newinvoiceline->fk_remise_except);
771
772
						    $discountId = $soc->set_remise_except($discount->amount_ht, $user, $discount->description, $discount->tva_tx);
773
						    $newinvoiceline->fk_remise_except = $discountId;
774
                        }
775
776
						$result = $newinvoiceline->insert();
777
778
						// Defined the new fk_parent_line
779
						if ($result > 0 && $newinvoiceline->product_type == 9) {
780
							$fk_parent_line = $result;
781
						}
782
					}
783
					if ($result < 0)
784
					{
785
						$this->error = $newinvoiceline->error;
786
						$error++;
787
						break;
788
					}
789
				}
790
			}
791
			elseif (!$error && empty($this->fac_rec)) 		// If this->lines is an array of invoice line arrays
792
			{
793
				$fk_parent_line = 0;
794
795
				dol_syslog("There is ".count($this->lines)." lines that are array lines");
796
797
				foreach ($this->lines as $i => $val)
798
				{
799
                	$line = $this->lines[$i];
800
801
                	// Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
802
				    //if (! is_object($line)) $line=json_decode(json_encode($line), false);  // convert recursively array into object.
803
                	if (!is_object($line)) $line = (object) $line;
804
805
				    if ($result >= 0)
806
					{
807
						// Reset fk_parent_line for no child products and special product
808
						if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
809
							$fk_parent_line = 0;
810
						}
811
812
						// Complete vat rate with code
813
						$vatrate = $line->tva_tx;
814
						if ($line->vat_src_code && !preg_match('/\(.*\)/', $vatrate)) $vatrate .= ' ('.$line->vat_src_code.')';
815
816
						if(!empty($conf->global->MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION)) {
817
							$originid=$line->origin_id;
818
							$origintype=$line->origin;
819
						} else {
820
							$originid=$line->id;
821
							$origintype=$this->element;
822
						}
823
824
                        $result = $this->addline(
825
							$line->desc,
826
							$line->subprice,
827
							$line->qty,
828
							$vatrate,
829
							$line->localtax1_tx,
830
							$line->localtax2_tx,
831
							$line->fk_product,
832
							$line->remise_percent,
833
							$line->date_start,
834
							$line->date_end,
835
							$line->fk_code_ventilation,
836
							$line->info_bits,
837
							$line->fk_remise_except,
838
							'HT',
839
							0,
840
							$line->product_type,
841
							$line->rang,
842
							$line->special_code,
843
	                        $origintype,
844
	                        $originid,
845
							$fk_parent_line,
846
							$line->fk_fournprice,
847
							$line->pa_ht,
848
							$line->label,
849
							$line->array_options,
850
							$line->situation_percent,
851
							$line->fk_prev_id,
852
							$line->fk_unit,
853
							$line->pu_ht_devise
0 ignored issues
show
Bug introduced by
The property pu_ht_devise does not seem to exist on FactureLigne.
Loading history...
854
						);
855
						if ($result < 0)
856
						{
857
							$this->error = $this->db->lasterror();
858
							dol_print_error($this->db);
859
							$this->db->rollback();
860
							return -1;
861
						}
862
863
						// Defined the new fk_parent_line
864
						if ($result > 0 && $line->product_type == 9) {
865
							$fk_parent_line = $result;
866
						}
867
					}
868
				}
869
			}
870
871
			/*
872
			 * Insert lines of template invoices
873
			 */
874
			if (!$error && $this->fac_rec > 0)
875
			{
876
				foreach ($_facrec->lines as $i => $val)
877
				{
878
					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...
879
					{
880
						$prod = new Product($this->db);
881
						$res = $prod->fetch($_facrec->lines[$i]->fk_product);
882
					}
883
884
					// For line from template invoice, we use data from template invoice
885
					/*
886
					$tva_tx = get_default_tva($mysoc,$soc,$prod->id);
887
					$tva_npr = get_default_npr($mysoc,$soc,$prod->id);
888
					if (empty($tva_tx)) $tva_npr=0;
889
					$localtax1_tx=get_localtax($tva_tx,1,$soc,$mysoc,$tva_npr);
890
					$localtax2_tx=get_localtax($tva_tx,2,$soc,$mysoc,$tva_npr);
891
					*/
892
					$tva_tx = $_facrec->lines[$i]->tva_tx.($_facrec->lines[$i]->vat_src_code ? '('.$_facrec->lines[$i]->vat_src_code.')' : '');
893
					$tva_npr = $_facrec->lines[$i]->info_bits;
894
					if (empty($tva_tx)) $tva_npr = 0;
895
					$localtax1_tx = $_facrec->lines[$i]->localtax1_tx;
896
					$localtax2_tx = $_facrec->lines[$i]->localtax2_tx;
897
898
					$fk_product_fournisseur_price = empty($_facrec->lines[$i]->fk_product_fournisseur_price) ?null:$_facrec->lines[$i]->fk_product_fournisseur_price;
899
					$buyprice = empty($_facrec->lines[$i]->buyprice) ? 0 : $_facrec->lines[$i]->buyprice;
900
					// If buyprice not defined from template invoice, we try to guess the best value
901
					if (!$buyprice && $_facrec->lines[$i]->fk_product > 0)
902
                    {
903
                        require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
904
                        $producttmp = new ProductFournisseur($this->db);
905
                        $producttmp->fetch($_facrec->lines[$i]->fk_product);
906
907
                        // If margin module defined on costprice, we try the costprice
908
                        // If not defined or if module margin defined and pmp and stock module enabled, we try pmp price
909
                        // else we get the best supplier price
910
                        if ($conf->global->MARGIN_TYPE == 'costprice' && !empty($producttmp->cost_price)) $buyprice = $producttmp->cost_price;
911
                        elseif (!empty($conf->stock->enabled) && ($conf->global->MARGIN_TYPE == 'costprice' || $conf->global->MARGIN_TYPE == 'pmp') && !empty($producttmp->pmp)) $buyprice = $producttmp->pmp;
912
                        else {
913
                            if ($producttmp->find_min_price_product_fournisseur($_facrec->lines[$i]->fk_product) > 0)
914
                            {
915
                                if ($producttmp->product_fourn_price_id > 0)
916
                                {
917
                                    $buyprice = price2num($producttmp->fourn_unitprice * (1 - $producttmp->fourn_remise_percent / 100) + $producttmp->fourn_remise, 'MU');
918
                                }
919
                            }
920
                        }
921
                    }
922
923
					$result_insert = $this->addline(
924
						$_facrec->lines[$i]->desc,
925
						$_facrec->lines[$i]->subprice,
926
						$_facrec->lines[$i]->qty,
927
						$tva_tx,
928
						$localtax1_tx,
929
						$localtax2_tx,
930
						$_facrec->lines[$i]->fk_product,
931
						$_facrec->lines[$i]->remise_percent,
932
						($_facrec->lines[$i]->date_start_fill == 1 && $originaldatewhen) ? $originaldatewhen : '',
933
						($_facrec->lines[$i]->date_end_fill == 1 && $previousdaynextdatewhen) ? $previousdaynextdatewhen : '',
934
						0,
935
						$tva_npr,
936
						'',
937
						'HT',
938
						0,
939
						$_facrec->lines[$i]->product_type,
940
						$_facrec->lines[$i]->rang,
941
						$_facrec->lines[$i]->special_code,
942
						'',
943
						0,
944
						0,
945
					    $fk_product_fournisseur_price,
946
						$buyprice,
947
						$_facrec->lines[$i]->label,
948
						empty($_facrec->lines[$i]->array_options) ?null:$_facrec->lines[$i]->array_options,
949
						$_facrec->lines[$i]->situation_percent,
950
						'',
951
						$_facrec->lines[$i]->fk_unit,
952
						$_facrec->lines[$i]->pu_ht_devise
953
					);
954
955
					if ($result_insert < 0)
956
					{
957
						$error++;
958
						$this->error = $this->db->error();
959
						break;
960
					}
961
				}
962
			}
963
964
			if (!$error)
965
			{
966
				$result = $this->update_price(1);
967
				if ($result > 0)
968
				{
969
					$action = 'create';
970
971
					// Actions on extra fields
972
					if (!$error)
973
					{
974
					    $result = $this->insertExtraFields();
975
					    if ($result < 0) $error++;
976
					}
977
978
			        if (!$error && !$notrigger)
979
			        {
980
			            // Call trigger
981
			            $result = $this->call_trigger('BILL_CREATE', $user);
982
			            if ($result < 0) $error++;
983
			            // End call triggers
984
			        }
985
986
					if (!$error)
987
					{
988
						$this->db->commit();
989
						return $this->id;
990
					}
991
					else
992
					{
993
						$this->db->rollback();
994
						return -4;
995
					}
996
				}
997
				else
998
				{
999
					$this->error = $langs->trans('FailedToUpdatePrice');
1000
					$this->db->rollback();
1001
					return -3;
1002
				}
1003
			}
1004
			else
1005
			{
1006
				dol_syslog(get_class($this)."::create error ".$this->error, LOG_ERR);
1007
				$this->db->rollback();
1008
				return -2;
1009
			}
1010
		}
1011
		else
1012
		{
1013
			$this->error = $this->db->error();
1014
			$this->db->rollback();
1015
			return -1;
1016
		}
1017
	}
1018
1019
1020
	/**
1021
	 *	Create a new invoice in database from current invoice
1022
	 *
1023
	 *	@param      User	$user    		Object user that ask creation
1024
	 *	@param		int		$invertdetail	Reverse sign of amounts for lines
1025
	 *	@return		int						<0 if KO, >0 if OK
1026
	 */
1027
    public function createFromCurrent(User $user, $invertdetail = 0)
1028
	{
1029
		global $conf;
1030
1031
		// Charge facture source
1032
		$facture = new Facture($this->db);
1033
1034
		// Retreive all extrafield
1035
		// fetch optionals attributes and labels
1036
		$this->fetch_optionals();
1037
1038
        if (!empty($this->array_options)) {
1039
                    $facture->array_options = $this->array_options;
1040
        }
1041
1042
        foreach ($this->lines as &$line) {
1043
                    $line->fetch_optionals(); //fetch extrafields
1044
        }
1045
1046
		$facture->fk_facture_source = $this->fk_facture_source;
1047
		$facture->type 			    = $this->type;
1048
		$facture->socid 		    = $this->socid;
1049
		$facture->date              = $this->date;
1050
		$facture->date_pointoftax   = $this->date_pointoftax;
1051
		$facture->note_public       = $this->note_public;
1052
		$facture->note_private      = $this->note_private;
1053
		$facture->ref_client        = $this->ref_client;
1054
		$facture->modelpdf          = $this->modelpdf;
1055
		$facture->fk_project        = $this->fk_project;
1056
		$facture->cond_reglement_id = $this->cond_reglement_id;
1057
		$facture->mode_reglement_id = $this->mode_reglement_id;
1058
		$facture->remise_absolue    = $this->remise_absolue;
1059
		$facture->remise_percent    = $this->remise_percent;
1060
1061
		$facture->origin            = $this->origin;
1062
		$facture->origin_id         = $this->origin_id;
1063
1064
		$facture->lines = $this->lines; // Array of lines of invoice
1065
		$facture->products = $this->lines; // Tant que products encore utilise
1 ignored issue
show
Deprecated Code introduced by
The property Facture::$products has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1065
		/** @scrutinizer ignore-deprecated */ $facture->products = $this->lines; // Tant que products encore utilise
Loading history...
1066
		$facture->situation_counter = $this->situation_counter;
1067
		$facture->situation_cycle_ref = $this->situation_cycle_ref;
1068
		$facture->situation_final = $this->situation_final;
1069
1070
        $facture->retained_warranty = $this->retained_warranty;
1071
        $facture->retained_warranty_fk_cond_reglement = $this->retained_warranty_fk_cond_reglement;
1072
        $facture->retained_warranty_date_limit = $this->retained_warranty_date_limit;
1073
1074
1075
		// Loop on each line of new invoice
1076
		foreach ($facture->lines as $i => $tmpline)
1077
		{
1078
			$facture->lines[$i]->fk_prev_id = $this->lines[$i]->rowid;
1 ignored issue
show
Deprecated Code introduced by
The property CommonObjectLine::$rowid has been deprecated: Try to use id property as possible (even if field into database is still rowid) ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1078
			$facture->lines[$i]->fk_prev_id = /** @scrutinizer ignore-deprecated */ $this->lines[$i]->rowid;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
1079
			if ($invertdetail)
1080
			{
1081
				$facture->lines[$i]->subprice  = -$facture->lines[$i]->subprice;
1082
				$facture->lines[$i]->total_ht  = -$facture->lines[$i]->total_ht;
1083
				$facture->lines[$i]->total_tva = -$facture->lines[$i]->total_tva;
1084
				$facture->lines[$i]->total_localtax1 = -$facture->lines[$i]->total_localtax1;
1085
				$facture->lines[$i]->total_localtax2 = -$facture->lines[$i]->total_localtax2;
1086
				$facture->lines[$i]->total_ttc = -$facture->lines[$i]->total_ttc;
1087
			}
1088
		}
1089
1090
		dol_syslog(get_class($this)."::createFromCurrent invertdetail=".$invertdetail." socid=".$this->socid." nboflines=".count($facture->lines));
1091
1092
		$facid = $facture->create($user);
1093
		if ($facid <= 0)
1094
		{
1095
			$this->error = $facture->error;
1096
			$this->errors = $facture->errors;
1097
		}
1098
		elseif ($this->type == self::TYPE_SITUATION && !empty($conf->global->INVOICE_USE_SITUATION))
1099
		{
1100
			$this->fetchObjectLinked('', '', $this->id, 'facture');
1101
1102
			foreach ($this->linkedObjectsIds as $typeObject => $Tfk_object)
1103
			{
1104
				foreach ($Tfk_object as $fk_object)
1105
				{
1106
					$facture->add_object_linked($typeObject, $fk_object);
1107
				}
1108
			}
1109
1110
			$facture->add_object_linked('facture', $this->fk_facture_source);
1111
		}
1112
1113
		return $facid;
1114
	}
1115
1116
1117
	/**
1118
	 *	Load an object from its id and create a new one in database
1119
	 *
1120
     *	@param      User	$user        	User that clone
1121
	 *  @param  	int 	$fromid         Id of object to clone
1122
	 * 	@return		int					    New id of clone
1123
	 */
1124
	public function createFromClone(User $user, $fromid = 0)
1125
	{
1126
		global $conf, $hookmanager;
1127
1128
		$error = 0;
1129
1130
		$object = new Facture($this->db);
1131
1132
		$this->db->begin();
1133
1134
		$object->fetch($fromid);
1135
1136
		// Change socid if needed
1137
		if (!empty($this->socid) && $this->socid != $object->socid)
1138
		{
1139
			$objsoc = new Societe($this->db);
1140
1141
			if ($objsoc->fetch($this->socid) > 0)
1142
			{
1143
			    $object->socid = $objsoc->id;
1144
			    $object->cond_reglement_id	= (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1145
			    $object->mode_reglement_id	= (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1146
			    $object->fk_project = '';
1147
			    $object->fk_delivery_address = '';
1 ignored issue
show
Deprecated Code introduced by
The property CommonObject::$fk_delivery_address has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1147
			    /** @scrutinizer ignore-deprecated */ $object->fk_delivery_address = '';
Loading history...
1148
			}
1149
1150
			// TODO Change product price if multi-prices
1151
		}
1152
1153
		$object->id = 0;
1154
		$object->statut = self::STATUS_DRAFT;
1155
1156
		// Clear fields
1157
		$object->date               = (empty($this->date) ? dol_now() : $this->date);
1158
		$object->user_author        = $user->id;
1159
		$object->user_valid         = '';
1160
		$object->fk_facture_source  = 0;
1161
		$object->date_creation      = '';
1162
		$object->date_modification = '';
1163
		$object->date_validation    = '';
1164
		$object->ref_client         = '';
1165
		$object->close_code         = '';
1166
		$object->close_note         = '';
1167
		$object->products = $object->lines; // For backward compatibility
1 ignored issue
show
Deprecated Code introduced by
The property Facture::$products has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1167
		/** @scrutinizer ignore-deprecated */ $object->products = $object->lines; // For backward compatibility
Loading history...
1168
		if ($conf->global->MAIN_DONT_KEEP_NOTE_ON_CLONING == 1) {
1169
			$object->note_private = '';
1170
			$object->note_public = '';
1171
		}
1172
1173
		// Loop on each line of new invoice
1174
		foreach ($object->lines as $i => $line)
1175
		{
1176
		    if (($object->lines[$i]->info_bits & 0x02) == 0x02)	// We do not clone line of discounts
1177
			{
1178
			    unset($object->lines[$i]);
1179
			    unset($object->products[$i]); // Tant que products encore utilise
1 ignored issue
show
Deprecated Code introduced by
The property Facture::$products has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1179
			    unset(/** @scrutinizer ignore-deprecated */ $object->products[$i]); // Tant que products encore utilise
Loading history...
1180
			}
1181
		}
1182
1183
		// Create clone
1184
		$object->context['createfromclone'] = 'createfromclone';
1185
		$result = $object->create($user);
1186
		if ($result < 0) $error++;
1187
		else {
1188
			// copy internal contacts
1189
		    if ($object->copy_linked_contact($this, 'internal') < 0)
1190
				$error++;
1191
1192
			// copy external contacts if same company
1193
			elseif ($this->socid == $object->socid)
1194
			{
1195
			    if ($object->copy_linked_contact($this, 'external') < 0)
1196
					$error++;
1197
			}
1198
		}
1199
1200
		if (!$error)
1201
		{
1202
			// Hook of thirdparty module
1203
			if (is_object($hookmanager))
1204
			{
1205
				$parameters = array('objFrom'=>$this);
1206
				$action = '';
1207
				$reshook = $hookmanager->executeHooks('createFrom', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
1208
				if ($reshook < 0) $error++;
1209
			}
1210
		}
1211
1212
		unset($object->context['createfromclone']);
1213
1214
		// End
1215
		if (!$error)
1216
		{
1217
			$this->db->commit();
1218
			return $object->id;
1219
		}
1220
		else
1221
		{
1222
			$this->db->rollback();
1223
			return -1;
1224
		}
1225
	}
1226
1227
	/**
1228
	 *  Load an object from an order and create a new invoice into database
1229
	 *
1230
	 *  @param      Object			$object         	Object source
1231
	 *  @param		User			$user				Object user
1232
	 *  @return     int             					<0 if KO, 0 if nothing done, 1 if OK
1233
	 */
1234
    public function createFromOrder($object, User $user)
1235
	{
1236
		global $conf, $hookmanager;
1237
1238
		$error = 0;
1239
1240
		// Closed order
1241
		$this->date = dol_now();
1242
		$this->source = 0;
1243
1244
		$num = count($object->lines);
1245
		for ($i = 0; $i < $num; $i++)
1246
		{
1247
			$line = new FactureLigne($this->db);
1248
1249
			$line->libelle = $object->lines[$i]->libelle; // deprecated
1 ignored issue
show
Deprecated Code introduced by
The property FactureLigne::$libelle has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1249
			/** @scrutinizer ignore-deprecated */ $line->libelle = $object->lines[$i]->libelle; // deprecated
Loading history...
1250
			$line->label			= $object->lines[$i]->label;
1 ignored issue
show
Deprecated Code introduced by
The property FactureLigne::$label has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1250
			/** @scrutinizer ignore-deprecated */ $line->label			= $object->lines[$i]->label;
Loading history...
1251
			$line->desc				= $object->lines[$i]->desc;
1252
			$line->subprice			= $object->lines[$i]->subprice;
1253
			$line->total_ht			= $object->lines[$i]->total_ht;
1254
			$line->total_tva		= $object->lines[$i]->total_tva;
1255
			$line->total_localtax1	= $object->lines[$i]->total_localtax1;
1256
			$line->total_localtax2	= $object->lines[$i]->total_localtax2;
1257
			$line->total_ttc		= $object->lines[$i]->total_ttc;
1258
			$line->vat_src_code = $object->lines[$i]->vat_src_code;
1259
			$line->tva_tx = $object->lines[$i]->tva_tx;
1260
			$line->localtax1_tx		= $object->lines[$i]->localtax1_tx;
1261
			$line->localtax2_tx		= $object->lines[$i]->localtax2_tx;
1262
			$line->qty = $object->lines[$i]->qty;
1263
			$line->fk_remise_except = $object->lines[$i]->fk_remise_except;
1264
			$line->remise_percent = $object->lines[$i]->remise_percent;
1265
			$line->fk_product = $object->lines[$i]->fk_product;
1266
			$line->info_bits = $object->lines[$i]->info_bits;
1267
			$line->product_type		= $object->lines[$i]->product_type;
1268
			$line->rang = $object->lines[$i]->rang;
1269
			$line->special_code		= $object->lines[$i]->special_code;
1270
			$line->fk_parent_line = $object->lines[$i]->fk_parent_line;
1271
			$line->fk_unit = $object->lines[$i]->fk_unit;
1272
			$line->date_start = $object->lines[$i]->date_start;
1273
			$line->date_end = $object->lines[$i]->date_end;
1274
1275
			// Multicurrency
1276
			$line->fk_multicurrency = $object->lines[$i]->fk_multicurrency;
1277
			$line->multicurrency_code = $object->lines[$i]->multicurrency_code;
1278
			$line->multicurrency_subprice = $object->lines[$i]->multicurrency_subprice;
1279
			$line->multicurrency_total_ht = $object->lines[$i]->multicurrency_total_ht;
1280
			$line->multicurrency_total_tva = $object->lines[$i]->multicurrency_total_tva;
1281
			$line->multicurrency_total_ttc = $object->lines[$i]->multicurrency_total_ttc;
1282
1283
			$line->fk_fournprice = $object->lines[$i]->fk_fournprice;
1284
			$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);
1285
			$line->pa_ht			= $marginInfos[0];
1286
1287
            // get extrafields from original line
1288
			$object->lines[$i]->fetch_optionals();
1289
			foreach ($object->lines[$i]->array_options as $options_key => $value)
1290
				$line->array_options[$options_key] = $value;
1291
1292
			$this->lines[$i] = $line;
1293
		}
1294
1295
		$this->socid                = $object->socid;
1296
		$this->fk_project           = $object->fk_project;
1297
		$this->fk_account = $object->fk_account;
1298
		$this->cond_reglement_id    = $object->cond_reglement_id;
1299
		$this->mode_reglement_id    = $object->mode_reglement_id;
1300
		$this->availability_id      = $object->availability_id;
1301
		$this->demand_reason_id     = $object->demand_reason_id;
1302
		$this->date_livraison       = $object->date_livraison;
1303
		$this->fk_delivery_address  = $object->fk_delivery_address; // deprecated
1 ignored issue
show
Deprecated Code introduced by
The property CommonObject::$fk_delivery_address has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1303
		/** @scrutinizer ignore-deprecated */ $this->fk_delivery_address  = $object->fk_delivery_address; // deprecated
Loading history...
1304
		$this->contact_id           = $object->contactid;
1305
		$this->ref_client           = $object->ref_client;
1306
1307
		if (empty($conf->global->MAIN_DISABLE_PROPAGATE_NOTES_FROM_ORIGIN))
1308
		{
1309
		    $this->note_private = $object->note_private;
1310
            $this->note_public = $object->note_public;
1311
		}
1312
1313
        $this->module_source = $object->module_source;
1314
		$this->pos_source = $object->pos_source;
1315
1316
		$this->origin = $object->element;
1317
		$this->origin_id = $object->id;
1318
1319
        // get extrafields from original line
1320
		$object->fetch_optionals();
1321
		foreach ($object->array_options as $options_key => $value)
1322
			$this->array_options[$options_key] = $value;
1323
1324
		// Possibility to add external linked objects with hooks
1325
		$this->linked_objects[$this->origin] = $this->origin_id;
1326
		if (!empty($object->other_linked_objects) && is_array($object->other_linked_objects))
1327
		{
1328
			$this->linked_objects = array_merge($this->linked_objects, $object->other_linked_objects);
1329
		}
1330
1331
		$ret = $this->create($user);
1332
1333
		if ($ret > 0)
1334
		{
1335
			// Actions hooked (by external module)
1336
			$hookmanager->initHooks(array('invoicedao'));
1337
1338
			$parameters = array('objFrom'=>$object);
1339
			$action = '';
1340
			$reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1341
			if ($reshook < 0) $error++;
1342
1343
			if (!$error)
1344
			{
1345
				return 1;
1346
			}
1347
			else return -1;
1348
		}
1349
		else return -1;
1350
	}
1351
1352
	/**
1353
	 *  Return clicable link of object (with eventually picto)
1354
	 *
1355
	 *  @param	int		$withpicto       			Add picto into link
1356
	 *  @param  string	$option          			Where point the link
1357
	 *  @param  int		$max             			Maxlength of ref
1358
	 *  @param  int		$short           			1=Return just URL
1359
	 *  @param  string  $moretitle       			Add more text to title tooltip
1360
     *  @param	int  	$notooltip		 			1=Disable tooltip
1361
     *  @param  int     $addlinktonotes  			1=Add link to notes
1362
     *  @param  int     $save_lastsearch_value		-1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
1363
     *  @param  string  $target                     Target of link ('', '_self', '_blank', '_parent', '_backoffice', ...)
1364
	 *  @return string 			         			String with URL
1365
	 */
1366
    public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $moretitle = '', $notooltip = 0, $addlinktonotes = 0, $save_lastsearch_value = -1, $target = '')
1367
	{
1368
		global $langs, $conf, $user, $mysoc;
1369
1370
		if (!empty($conf->dol_no_mouse_hover)) $notooltip = 1; // Force disable tooltips
1371
1372
		$result = '';
1373
1374
		if ($option == 'withdraw') $url = DOL_URL_ROOT.'/compta/facture/prelevement.php?facid='.$this->id;
1375
		else $url = DOL_URL_ROOT.'/compta/facture/card.php?facid='.$this->id;
1376
1377
        if (!$user->rights->facture->lire)
1378
            $option = 'nolink';
1379
1380
		if ($option !== 'nolink')
1381
		{
1382
			// Add param to save lastsearch_values or not
1383
			$add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1384
			if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) $add_save_lastsearch_values = 1;
1385
			if ($add_save_lastsearch_values) $url .= '&save_lastsearch_values=1';
1386
		}
1387
1388
		if ($short) return $url;
1389
1390
		$picto = $this->picto;
1391
		if ($this->type == self::TYPE_REPLACEMENT) $picto .= 'r'; // Replacement invoice
1392
		if ($this->type == self::TYPE_CREDIT_NOTE) $picto .= 'a'; // Credit note
1393
		if ($this->type == self::TYPE_DEPOSIT) $picto .= 'd'; // Deposit invoice
1394
        $label = '';
1395
1396
        if ($user->rights->facture->lire) {
1397
            $label = '<u>'.$langs->trans("ShowInvoice").'</u>';
1398
            if ($this->type == self::TYPE_REPLACEMENT) $label = '<u>'.$langs->transnoentitiesnoconv("ShowInvoiceReplace").'</u>';
1399
            if ($this->type == self::TYPE_CREDIT_NOTE) $label = '<u>'.$langs->transnoentitiesnoconv("ShowInvoiceAvoir").'</u>';
1400
            if ($this->type == self::TYPE_DEPOSIT)     $label = '<u>'.$langs->transnoentitiesnoconv("ShowInvoiceDeposit").'</u>';
1401
            if ($this->type == self::TYPE_SITUATION)   $label = '<u>'.$langs->transnoentitiesnoconv("ShowInvoiceSituation").'</u>';
1402
            if (!empty($this->ref))
1403
                $label .= '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1404
            if (!empty($this->ref_client))
1405
                $label .= '<br><b>'.$langs->trans('RefCustomer').':</b> '.$this->ref_client;
1406
            if (!empty($this->date))
1407
              	$label .= '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
1408
            if (!empty($this->total_ht))
1409
                $label .= '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
1410
            if (!empty($this->total_tva))
1411
                $label .= '<br><b>'.$langs->trans('AmountVAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
1412
            if (!empty($this->total_localtax1) && $this->total_localtax1 != 0)		// We keep test != 0 because $this->total_localtax1 can be '0.00000000'
1413
            	$label .= '<br><b>'.$langs->transcountry('AmountLT1', $mysoc->country_code).':</b> '.price($this->total_localtax1, 0, $langs, 0, -1, -1, $conf->currency);
1414
            if (!empty($this->total_localtax2) && $this->total_localtax2 != 0)
1415
            	$label .= '<br><b>'.$langs->transcountry('AmountLT2', $mysoc->country_code).':</b> '.price($this->total_localtax2, 0, $langs, 0, -1, -1, $conf->currency);
1416
            if (!empty($this->total_ttc))
1417
                $label .= '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
1418
    		if ($moretitle) $label .= ' - '.$moretitle;
1419
    		if (isset($this->statut) && isset($this->alreadypaid)) {
1420
    			$label .= '<br><b>'.$langs->trans("Status").":</b> ".$this->getLibStatut(5, $this->alreadypaid);
1421
    		}
1422
        }
1423
1424
		$linkclose = ($target ? ' target="'.$target.'"' : '');
1425
		if (empty($notooltip) && $user->rights->facture->lire)
1426
		{
1427
		    if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER))
1428
		    {
1429
		        $label = $langs->trans("ShowInvoice");
1430
		        $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1431
		    }
1432
		    $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
1433
		    $linkclose .= ' class="classfortooltip"';
1434
		}
1435
1436
        $linkstart = '<a href="'.$url.'"';
1437
        $linkstart .= $linkclose.'>';
1438
		$linkend = '</a>';
1439
1440
        if ($option == 'nolink') {
1441
            $linkstart = '';
1442
            $linkend = '';
1443
        }
1444
1445
		$result .= $linkstart;
1446
		if ($withpicto) $result .= img_object(($notooltip ? '' : $label), $picto, ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
1447
		if ($withpicto != 2) $result .= ($max ?dol_trunc($this->ref, $max) : $this->ref);
1448
		$result .= $linkend;
1449
1450
		if ($addlinktonotes)
1451
		{
1452
		    $txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
1453
		    if ($txttoshow)
1454
		    {
1455
                $notetoshow = $langs->trans("ViewPrivateNote").':<br>'.dol_string_nohtmltag($txttoshow, 1);
1456
    		    $result .= ' <span class="note inline-block">';
1457
    		    $result .= '<a href="'.DOL_URL_ROOT.'/compta/facture/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($notetoshow).'">';
1458
    		    $result .= img_picto('', 'note');
1459
    		    $result .= '</a>';
1460
    		    //$result.=img_picto($langs->trans("ViewNote"),'object_generic');
1461
    		    //$result.='</a>';
1462
    		    $result .= '</span>';
1463
		    }
1464
		}
1465
1466
		return $result;
1467
	}
1468
1469
	/**
1470
	 *	Get object from database. Get also lines.
1471
	 *
1472
	 *	@param      int		$rowid       		Id of object to load
1473
	 * 	@param		string	$ref				Reference of invoice
1474
	 * 	@param		string	$ref_ext			External reference of invoice
1475
	 * 	@param		int		$notused			Not used
1476
	 *  @param		bool	$fetch_situation	Fetch the previous and next situation in $tab_previous_situation_invoice and $tab_next_situation_invoice
1477
	 *	@return     int         				>0 if OK, <0 if KO, 0 if not found
1478
	 */
1479
	public function fetch($rowid, $ref = '', $ref_ext = '', $notused = '', $fetch_situation = false)
1480
	{
1481
		global $conf;
1482
1483
		if (empty($rowid) && empty($ref) && empty($ref_ext)) return -1;
1484
1485
		$sql = 'SELECT f.rowid,f.entity,f.ref,f.ref_client,f.ref_ext,f.ref_int,f.type,f.fk_soc';
1486
		$sql .= ', f.tva, f.localtax1, f.localtax2, f.total, f.total_ttc, f.revenuestamp';
1487
		$sql .= ', f.remise_percent, f.remise_absolue, f.remise';
1488
		$sql .= ', f.datef as df, f.date_pointoftax';
1489
		$sql .= ', f.date_lim_reglement as dlr';
1490
		$sql .= ', f.datec as datec';
1491
		$sql .= ', f.date_valid as datev';
1492
		$sql .= ', f.tms as datem';
1493
		$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';
1494
		$sql .= ', f.fk_facture_source, f.fk_fac_rec_source';
1495
		$sql .= ', f.fk_mode_reglement, f.fk_cond_reglement, f.fk_projet as fk_project, f.extraparams';
1496
		$sql .= ', f.situation_cycle_ref, f.situation_counter, f.situation_final';
1497
		$sql .= ', f.fk_account';
1498
		$sql .= ", f.fk_multicurrency, f.multicurrency_code, f.multicurrency_tx, f.multicurrency_total_ht, f.multicurrency_total_tva, f.multicurrency_total_ttc";
1499
		$sql .= ', p.code as mode_reglement_code, p.libelle as mode_reglement_libelle';
1500
		$sql .= ', c.code as cond_reglement_code, c.libelle as cond_reglement_libelle, c.libelle_facture as cond_reglement_libelle_doc';
1501
        $sql .= ', f.fk_incoterms, f.location_incoterms';
1502
        $sql .= ', f.module_source, f.pos_source';
1503
        $sql .= ", i.libelle as label_incoterms";
1504
        $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";
1505
		$sql .= ' FROM '.MAIN_DB_PREFIX.'facture as f';
1506
		$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_payment_term as c ON f.fk_cond_reglement = c.rowid';
1507
		$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as p ON f.fk_mode_reglement = p.id';
1508
		$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON f.fk_incoterms = i.rowid';
1509
1510
		if ($rowid) $sql .= " WHERE f.rowid=".$rowid;
1511
		else {
1512
			$sql .= ' WHERE f.entity IN ('.getEntity('invoice').')'; // Dont't use entity if you use rowid
1513
			if ($ref)     $sql .= " AND f.ref='".$this->db->escape($ref)."'";
1514
			if ($ref_ext) $sql .= " AND f.ref_ext='".$this->db->escape($ref_ext)."'";
1515
			if ($notused) $sql .= " AND f.ref_int='".$this->db->escape($notused)."'"; // deprecated
1516
		}
1517
1518
		dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
1519
		$result = $this->db->query($sql);
1520
		if ($result)
1521
		{
1522
			if ($this->db->num_rows($result))
1523
			{
1524
				$obj = $this->db->fetch_object($result);
1525
1526
				$this->id = $obj->rowid;
1527
				$this->entity = $obj->entity;
1528
1529
				$this->ref = $obj->ref;
1530
				$this->ref_client = $obj->ref_client;
1531
				$this->ref_ext				= $obj->ref_ext;
1532
				$this->ref_int				= $obj->ref_int;
1533
				$this->type					= $obj->type;
1534
				$this->date					= $this->db->jdate($obj->df);
1535
				$this->date_pointoftax		= $this->db->jdate($obj->date_pointoftax);
1536
				$this->date_creation = $this->db->jdate($obj->datec);
1537
				$this->date_validation		= $this->db->jdate($obj->datev);
1538
				$this->date_modification = $this->db->jdate($obj->datem);
1539
				$this->datem = $this->db->jdate($obj->datem);
1540
				$this->remise_percent		= $obj->remise_percent;
1541
				$this->remise_absolue		= $obj->remise_absolue;
1542
				$this->total_ht				= $obj->total;
1543
				$this->total_tva			= $obj->tva;
1544
				$this->total_localtax1		= $obj->localtax1;
1545
				$this->total_localtax2		= $obj->localtax2;
1546
				$this->total_ttc			= $obj->total_ttc;
1547
				$this->revenuestamp = $obj->revenuestamp;
1548
				$this->paye = $obj->paye;
1549
				$this->close_code			= $obj->close_code;
1550
				$this->close_note			= $obj->close_note;
1551
1552
				$this->socid = $obj->fk_soc;
1553
				$this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
1554
1555
				$this->fk_project = $obj->fk_project;
1556
				$this->project = null; // Clear if another value was already set by fetch_projet
1557
1558
				$this->statut = $obj->fk_statut;
1559
				$this->date_lim_reglement = $this->db->jdate($obj->dlr);
1560
				$this->mode_reglement_id	= $obj->fk_mode_reglement;
1561
				$this->mode_reglement_code	= $obj->mode_reglement_code;
1562
				$this->mode_reglement		= $obj->mode_reglement_libelle;
1563
				$this->cond_reglement_id	= $obj->fk_cond_reglement;
1564
				$this->cond_reglement_code	= $obj->cond_reglement_code;
1565
				$this->cond_reglement		= $obj->cond_reglement_libelle;
1566
				$this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
1567
				$this->fk_account = ($obj->fk_account > 0) ? $obj->fk_account : null;
1568
				$this->fk_facture_source	= $obj->fk_facture_source;
1569
				$this->fk_fac_rec_source	= $obj->fk_fac_rec_source;
1570
				$this->note = $obj->note_private; // deprecated
1571
				$this->note_private = $obj->note_private;
1572
				$this->note_public			= $obj->note_public;
1573
				$this->user_author			= $obj->fk_user_author;
1574
				$this->user_valid = $obj->fk_user_valid;
1575
				$this->modelpdf = $obj->model_pdf;
1576
				$this->last_main_doc = $obj->last_main_doc;
1577
				$this->situation_cycle_ref  = $obj->situation_cycle_ref;
1578
				$this->situation_counter    = $obj->situation_counter;
1579
				$this->situation_final      = $obj->situation_final;
1580
				$this->retained_warranty    = $obj->retained_warranty;
1581
				$this->retained_warranty_date_limit         = $this->db->jdate($obj->retained_warranty_date_limit);
1582
				$this->retained_warranty_fk_cond_reglement  = $obj->retained_warranty_fk_cond_reglement;
1583
1584
				$this->extraparams = (array) json_decode($obj->extraparams, true);
1585
1586
				//Incoterms
1587
				$this->fk_incoterms         = $obj->fk_incoterms;
1588
				$this->location_incoterms   = $obj->location_incoterms;
1589
				$this->label_incoterms = $obj->label_incoterms;
1590
1591
  				$this->module_source = $obj->module_source;
1592
				$this->pos_source = $obj->pos_source;
1593
1594
				// Multicurrency
1595
				$this->fk_multicurrency 		= $obj->fk_multicurrency;
1596
				$this->multicurrency_code = $obj->multicurrency_code;
1597
				$this->multicurrency_tx 		= $obj->multicurrency_tx;
1598
				$this->multicurrency_total_ht = $obj->multicurrency_total_ht;
1599
				$this->multicurrency_total_tva 	= $obj->multicurrency_total_tva;
1600
				$this->multicurrency_total_ttc 	= $obj->multicurrency_total_ttc;
1601
1602
				if (($this->type == self::TYPE_SITUATION || ($this->type == self::TYPE_CREDIT_NOTE && $this->situation_cycle_ref > 0)) && $fetch_situation)
1603
				{
1604
					$this->fetchPreviousNextSituationInvoice();
1605
				}
1606
1607
				if ($this->statut == self::STATUS_DRAFT)	$this->brouillon = 1;
1608
1609
				// Retreive all extrafield
1610
				// fetch optionals attributes and labels
1611
				$this->fetch_optionals();
1612
1613
				// Lines
1614
				$this->lines = array();
1615
1616
				$result = $this->fetch_lines();
1617
				if ($result < 0)
1618
				{
1619
					$this->error = $this->db->error();
1620
					return -3;
1621
				}
1622
				return 1;
1623
			}
1624
			else
1625
			{
1626
				$this->error = 'Invoice with id='.$rowid.' or ref='.$ref.' or ref_ext='.$ref_ext.' not found';
1627
				dol_syslog(get_class($this)."::fetch Error ".$this->error, LOG_ERR);
1628
				return 0;
1629
			}
1630
		}
1631
		else
1632
		{
1633
			$this->error = $this->db->error();
1634
			return -1;
1635
		}
1636
	}
1637
1638
1639
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1640
	/**
1641
	 *	Load all detailed lines into this->lines
1642
	 *
1643
	 *	@param		int		$only_product	Return only physical products
1644
	 *	@param		int		$loadalsotranslation	Return translation for products
1645
	 *
1646
	 *	@return     int         1 if OK, < 0 if KO
1647
	 */
1648
	public function fetch_lines($only_product = 0, $loadalsotranslation = 0)
1649
	{
1650
		global $langs, $conf;
1651
        // phpcs:enable
1652
		$this->lines = array();
1653
1654
		$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,';
1655
		$sql .= ' l.localtax1_tx, l.localtax2_tx, l.localtax1_type, l.localtax2_type, l.remise_percent, l.fk_remise_except, l.subprice,';
1656
		$sql .= ' l.situation_percent, l.fk_prev_id,';
1657
		$sql .= ' l.rang, l.special_code,';
1658
		$sql .= ' l.date_start as date_start, l.date_end as date_end,';
1659
		$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,';
1660
		$sql .= ' l.fk_unit,';
1661
		$sql .= ' l.fk_multicurrency, l.multicurrency_code, l.multicurrency_subprice, l.multicurrency_total_ht, l.multicurrency_total_tva, l.multicurrency_total_ttc,';
1662
		$sql .= ' p.ref as product_ref, p.fk_product_type as fk_product_type, p.label as product_label, p.description as product_desc';
1663
		$sql .= ' FROM '.MAIN_DB_PREFIX.'facturedet as l';
1664
		$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON l.fk_product = p.rowid';
1665
		$sql .= ' WHERE l.fk_facture = '.$this->id;
1666
		$sql .= ' ORDER BY l.rang, l.rowid';
1667
1668
		dol_syslog(get_class($this).'::fetch_lines', LOG_DEBUG);
1669
		$result = $this->db->query($sql);
1670
		if ($result)
1671
		{
1672
			$num = $this->db->num_rows($result);
1673
			$i = 0;
1674
			while ($i < $num)
1675
			{
1676
				$objp = $this->db->fetch_object($result);
1677
				$line = new FactureLigne($this->db);
1678
1679
				$line->id               = $objp->rowid;
1680
				$line->rowid = $objp->rowid; // deprecated
1681
				$line->fk_facture       = $objp->fk_facture;
1682
				$line->label            = $objp->custom_label; // deprecated
1683
				$line->desc             = $objp->description; // Description line
1684
				$line->description      = $objp->description; // Description line
1685
				$line->product_type     = $objp->product_type; // Type of line
1686
				$line->ref              = $objp->product_ref; // Ref product
1687
				$line->product_ref      = $objp->product_ref; // Ref product
1688
				$line->libelle          = $objp->product_label; // deprecated
1689
				$line->product_label = $objp->product_label; // Label product
1690
				$line->product_desc     = $objp->product_desc; // Description product
1691
				$line->fk_product_type  = $objp->fk_product_type; // Type of product
1692
				$line->qty              = $objp->qty;
1693
				$line->subprice         = $objp->subprice;
1694
1695
                $line->vat_src_code = $objp->vat_src_code;
1696
				$line->tva_tx           = $objp->tva_tx;
1697
				$line->localtax1_tx     = $objp->localtax1_tx;
1698
				$line->localtax2_tx     = $objp->localtax2_tx;
1699
				$line->localtax1_type   = $objp->localtax1_type;
1700
				$line->localtax2_type   = $objp->localtax2_type;
1701
				$line->remise_percent   = $objp->remise_percent;
1702
				$line->fk_remise_except = $objp->fk_remise_except;
1703
				$line->fk_product       = $objp->fk_product;
1704
				$line->date_start       = $this->db->jdate($objp->date_start);
1705
				$line->date_end         = $this->db->jdate($objp->date_end);
1706
				$line->date_start       = $this->db->jdate($objp->date_start);
1707
				$line->date_end         = $this->db->jdate($objp->date_end);
1708
				$line->info_bits        = $objp->info_bits;
1709
				$line->total_ht         = $objp->total_ht;
1710
				$line->total_tva        = $objp->total_tva;
1711
				$line->total_localtax1  = $objp->total_localtax1;
1712
				$line->total_localtax2  = $objp->total_localtax2;
1713
				$line->total_ttc        = $objp->total_ttc;
1714
				$line->code_ventilation = $objp->fk_code_ventilation;
1715
				$line->fk_fournprice = $objp->fk_fournprice;
1716
				$marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
1717
				$line->pa_ht = $marginInfos[0];
1718
				$line->marge_tx			= $marginInfos[1];
1719
				$line->marque_tx		= $marginInfos[2];
1720
				$line->rang = $objp->rang;
1721
				$line->special_code = $objp->special_code;
1722
				$line->fk_parent_line = $objp->fk_parent_line;
1723
				$line->situation_percent = $objp->situation_percent;
1724
				$line->fk_prev_id = $objp->fk_prev_id;
1725
				$line->fk_unit = $objp->fk_unit;
1726
1727
				// Accountancy
1728
				$line->fk_accounting_account = $objp->fk_code_ventilation;
1729
1730
				// Multicurrency
1731
				$line->fk_multicurrency = $objp->fk_multicurrency;
1732
				$line->multicurrency_code = $objp->multicurrency_code;
1733
				$line->multicurrency_subprice 	= $objp->multicurrency_subprice;
1734
				$line->multicurrency_total_ht 	= $objp->multicurrency_total_ht;
1735
				$line->multicurrency_total_tva 	= $objp->multicurrency_total_tva;
1736
				$line->multicurrency_total_ttc 	= $objp->multicurrency_total_ttc;
1737
1738
                $line->fetch_optionals();
1739
1740
				// multilangs
1741
        		if (!empty($conf->global->MAIN_MULTILANGS) && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
1742
        		    $line = new Product($this->db);
1743
        		    $line->fetch($objp->fk_product);
1744
        		    $line->getMultiLangs();
1745
        		}
1746
1747
				$this->lines[$i] = $line;
1748
1749
				$i++;
1750
			}
1751
			$this->db->free($result);
1752
			return 1;
1753
		}
1754
		else
1755
		{
1756
			$this->error = $this->db->error();
1757
			return -3;
1758
		}
1759
	}
1760
1761
	/**
1762
	 * Fetch previous and next situations invoices
1763
	 *
1764
	 * @return	void
1765
	 */
1766
    public function fetchPreviousNextSituationInvoice()
1767
	{
1768
		global $conf;
1769
1770
		$this->tab_previous_situation_invoice = array();
1771
		$this->tab_next_situation_invoice = array();
1772
1773
		$sql = 'SELECT rowid, situation_counter FROM '.MAIN_DB_PREFIX.'facture';
1774
		$sql .= ' WHERE rowid <> '.$this->id;
1775
		$sql .= ' AND entity = '.$this->entity;
1776
		$sql .= ' AND situation_cycle_ref = '.(int) $this->situation_cycle_ref;
1777
		$sql .= ' ORDER BY situation_counter ASC';
1778
1779
		dol_syslog(get_class($this).'::fetchPreviousNextSituationInvoice ', LOG_DEBUG);
1780
		$result = $this->db->query($sql);
1781
		if ($result && $this->db->num_rows($result) > 0)
1782
		{
1783
			while ($objp = $this->db->fetch_object($result))
1784
			{
1785
				$invoice = new Facture($this->db);
1786
				if ($invoice->fetch($objp->rowid) > 0)
1787
				{
1788
				    if ($objp->situation_counter < $this->situation_counter
1789
				        || ($objp->situation_counter == $this->situation_counter && $objp->rowid < $this->id) // This case appear when there are credit notes
1790
				       )
1791
					{
1792
					    $this->tab_previous_situation_invoice[] = $invoice;
1793
					}
1794
					else
1795
					{
1796
					    $this->tab_next_situation_invoice[] = $invoice;
1797
					}
1798
				}
1799
			}
1800
		}
1801
	}
1802
1803
	/**
1804
	 *      Update database
1805
	 *
1806
	 *      @param      User	$user        	User that modify
1807
	 *      @param      int		$notrigger	    0=launch triggers after, 1=disable triggers
1808
	 *      @return     int      			   	<0 if KO, >0 if OK
1809
	 */
1810
    public function update(User $user, $notrigger = 0)
1811
	{
1812
		global $conf;
1813
1814
		$error = 0;
1815
1816
		// Clean parameters
1817
		if (empty($this->type)) $this->type = self::TYPE_STANDARD;
1818
		if (isset($this->ref)) $this->ref = trim($this->ref);
1819
		if (isset($this->ref_client)) $this->ref_client = trim($this->ref_client);
1820
		if (isset($this->increment)) $this->increment = trim($this->increment);
1821
		if (isset($this->close_code)) $this->close_code = trim($this->close_code);
1822
		if (isset($this->close_note)) $this->close_note = trim($this->close_note);
1823
		if (isset($this->note) || isset($this->note_private)) $this->note = (isset($this->note) ? trim($this->note) : trim($this->note_private)); // deprecated
1824
		if (isset($this->note) || isset($this->note_private)) $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
1825
		if (isset($this->note_public)) $this->note_public = trim($this->note_public);
1826
		if (isset($this->modelpdf)) $this->modelpdf = trim($this->modelpdf);
1827
		if (isset($this->import_key)) $this->import_key = trim($this->import_key);
1828
		if (isset($this->retained_warranty)) $this->retained_warranty = floatval($this->retained_warranty);
1829
1830
1831
		// Check parameters
1832
		// Put here code to add control on parameters values
1833
1834
		// Update request
1835
		$sql = "UPDATE ".MAIN_DB_PREFIX."facture SET";
1836
		$sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1837
		$sql .= " type=".(isset($this->type) ? $this->db->escape($this->type) : "null").",";
1838
		$sql .= " ref_client=".(isset($this->ref_client) ? "'".$this->db->escape($this->ref_client)."'" : "null").",";
1839
		$sql .= " increment=".(isset($this->increment) ? "'".$this->db->escape($this->increment)."'" : "null").",";
1840
		$sql .= " fk_soc=".(isset($this->socid) ? $this->db->escape($this->socid) : "null").",";
1841
		$sql .= " datec=".(strval($this->date_creation) != '' ? "'".$this->db->idate($this->date_creation)."'" : 'null').",";
1842
		$sql .= " datef=".(strval($this->date) != '' ? "'".$this->db->idate($this->date)."'" : 'null').",";
1843
		$sql .= " date_pointoftax=".(strval($this->date_pointoftax) != '' ? "'".$this->db->idate($this->date_pointoftax)."'" : 'null').",";
1844
		$sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
1845
		$sql .= " paye=".(isset($this->paye) ? $this->db->escape($this->paye) : 0).",";
1846
		$sql .= " remise_percent=".(isset($this->remise_percent) ? $this->db->escape($this->remise_percent) : "null").",";
1847
		$sql .= " remise_absolue=".(isset($this->remise_absolue) ? $this->db->escape($this->remise_absolue) : "null").",";
1848
		$sql .= " close_code=".(isset($this->close_code) ? "'".$this->db->escape($this->close_code)."'" : "null").",";
1849
		$sql .= " close_note=".(isset($this->close_note) ? "'".$this->db->escape($this->close_note)."'" : "null").",";
1850
		$sql .= " tva=".(isset($this->total_tva) ? $this->total_tva : "null").",";
1851
		$sql .= " localtax1=".(isset($this->total_localtax1) ? $this->total_localtax1 : "null").",";
1852
		$sql .= " localtax2=".(isset($this->total_localtax2) ? $this->total_localtax2 : "null").",";
1853
		$sql .= " total=".(isset($this->total_ht) ? $this->total_ht : "null").",";
1854
		$sql .= " total_ttc=".(isset($this->total_ttc) ? $this->total_ttc : "null").",";
1855
		$sql .= " revenuestamp=".((isset($this->revenuestamp) && $this->revenuestamp != '') ? $this->db->escape($this->revenuestamp) : "null").",";
1856
		$sql .= " fk_statut=".(isset($this->statut) ? $this->db->escape($this->statut) : "null").",";
1857
		$sql .= " fk_user_author=".(isset($this->user_author) ? $this->db->escape($this->user_author) : "null").",";
1858
		$sql .= " fk_user_valid=".(isset($this->fk_user_valid) ? $this->db->escape($this->fk_user_valid) : "null").",";
1859
		$sql .= " fk_facture_source=".(isset($this->fk_facture_source) ? $this->db->escape($this->fk_facture_source) : "null").",";
1860
		$sql .= " fk_projet=".(isset($this->fk_project) ? $this->db->escape($this->fk_project) : "null").",";
1861
		$sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? $this->db->escape($this->cond_reglement_id) : "null").",";
1862
		$sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? $this->db->escape($this->mode_reglement_id) : "null").",";
1863
		$sql .= " date_lim_reglement=".(strval($this->date_lim_reglement) != '' ? "'".$this->db->idate($this->date_lim_reglement)."'" : 'null').",";
1864
		$sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1865
		$sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1866
		$sql .= " model_pdf=".(isset($this->modelpdf) ? "'".$this->db->escape($this->modelpdf)."'" : "null").",";
1867
		$sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null").",";
1868
		$sql .= " situation_cycle_ref=".(empty($this->situation_cycle_ref) ? "null" : $this->db->escape($this->situation_cycle_ref)).",";
1869
		$sql .= " situation_counter=".(empty($this->situation_counter) ? "null" : $this->db->escape($this->situation_counter)).",";
1870
		$sql .= " situation_final=".(empty($this->situation_final) ? "0" : $this->db->escape($this->situation_final)).",";
1871
		$sql .= " retained_warranty=".(empty($this->retained_warranty) ? "0" : $this->db->escape($this->retained_warranty)).",";
1872
		$sql .= " retained_warranty_date_limit=".(strval($this->retained_warranty_date_limit) != '' ? "'".$this->db->idate($this->retained_warranty_date_limit)."'" : 'null').",";
1873
		$sql .= " retained_warranty_fk_cond_reglement=".(isset($this->retained_warranty_fk_cond_reglement) ?intval($this->retained_warranty_fk_cond_reglement) : "null");
1874
		$sql .= " WHERE rowid=".$this->id;
1875
1876
		$this->db->begin();
1877
1878
		dol_syslog(get_class($this)."::update", LOG_DEBUG);
1879
		$resql = $this->db->query($sql);
1880
		if (!$resql) {
1881
			$error++; $this->errors[] = "Error ".$this->db->lasterror();
1882
		}
1883
1884
		if (!$error && empty($conf->global->MAIN_EXTRAFIELDS_DISABLED) && is_array($this->array_options) && count($this->array_options) > 0)
1885
		{
1886
			$result = $this->insertExtraFields();
1887
			if ($result < 0)
1888
			{
1889
				$error++;
1890
			}
1891
		}
1892
1893
		if (!$error && !$notrigger)
1894
		{
1895
			// Call trigger
1896
			$result = $this->call_trigger('BILL_MODIFY', $user);
1897
			if ($result < 0) $error++;
1898
			// End call triggers
1899
		}
1900
1901
		// Commit or rollback
1902
		if ($error)
1903
		{
1904
			foreach ($this->errors as $errmsg)
1905
			{
1906
				dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1907
				$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1908
			}
1909
			$this->db->rollback();
1910
			return -1 * $error;
1911
		}
1912
		else
1913
		{
1914
			$this->db->commit();
1915
			return 1;
1916
		}
1917
	}
1918
1919
1920
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1921
	/**
1922
	 *    Add a discount line into an invoice (as an invoice line) using an existing absolute discount (Consume the discount)
1923
	 *
1924
	 *    @param     int	$idremise	Id of absolute discount
1925
	 *    @return    int          		>0 if OK, <0 if KO
1926
	 */
1927
    public function insert_discount($idremise)
1928
	{
1929
        // phpcs:enable
1930
		global $langs;
1931
1932
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1933
		include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
1934
1935
		$this->db->begin();
1936
1937
		$remise = new DiscountAbsolute($this->db);
1938
		$result = $remise->fetch($idremise);
1939
1940
		if ($result > 0)
1941
		{
1942
			if ($remise->fk_facture)	// Protection against multiple submission
1943
			{
1944
				$this->error = $langs->trans("ErrorDiscountAlreadyUsed");
1945
				$this->db->rollback();
1946
				return -5;
1947
			}
1948
1949
			$facligne = new FactureLigne($this->db);
1950
			$facligne->fk_facture = $this->id;
1951
			$facligne->fk_remise_except = $remise->id;
1952
			$facligne->desc = $remise->description; // Description ligne
1953
			$facligne->vat_src_code = $remise->vat_src_code;
1954
			$facligne->tva_tx = $remise->tva_tx;
1955
			$facligne->subprice = -$remise->amount_ht;
1956
			$facligne->fk_product = 0; // Id produit predefini
1957
			$facligne->qty = 1;
1958
			$facligne->remise_percent = 0;
1959
			$facligne->rang = -1;
1960
			$facligne->info_bits = 2;
1961
1962
			// Get buy/cost price of invoice that is source of discount
1963
			if ($remise->fk_facture_source > 0)
1964
			{
1965
    			$srcinvoice = new Facture($this->db);
1966
    			$srcinvoice->fetch($remise->fk_facture_source);
1967
    			include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmargin.class.php'; // TODO Move this into commonobject
1968
    			$formmargin = new FormMargin($this->db);
1969
    			$arraytmp = $formmargin->getMarginInfosArray($srcinvoice, false);
1970
        		$facligne->pa_ht = $arraytmp['pa_total'];
1971
			}
1972
1973
			$facligne->total_ht  = -$remise->amount_ht;
1974
			$facligne->total_tva = -$remise->amount_tva;
1975
			$facligne->total_ttc = -$remise->amount_ttc;
1976
1977
			$facligne->multicurrency_subprice = -$remise->multicurrency_subprice;
1978
			$facligne->multicurrency_total_ht = -$remise->multicurrency_amount_ht;
1979
			$facligne->multicurrency_total_tva = -$remise->multicurrency_amount_tva;
1980
			$facligne->multicurrency_total_ttc = -$remise->multicurrency_amount_ttc;
1981
1982
			$lineid = $facligne->insert();
1983
			if ($lineid > 0)
1984
			{
1985
				$result = $this->update_price(1);
1986
				if ($result > 0)
1987
				{
1988
					// Create link between discount and invoice line
1989
					$result = $remise->link_to_invoice($lineid, 0);
1990
					if ($result < 0)
1991
					{
1992
						$this->error = $remise->error;
1993
						$this->db->rollback();
1994
						return -4;
1995
					}
1996
1997
					$this->db->commit();
1998
					return 1;
1999
				}
2000
				else
2001
				{
2002
					$this->error = $facligne->error;
2003
					$this->db->rollback();
2004
					return -1;
2005
				}
2006
			}
2007
			else
2008
			{
2009
				$this->error = $facligne->error;
2010
				$this->db->rollback();
2011
				return -2;
2012
			}
2013
		}
2014
		else
2015
		{
2016
			$this->db->rollback();
2017
			return -3;
2018
		}
2019
	}
2020
2021
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2022
	/**
2023
	 *	Set customer ref
2024
	 *
2025
	 *	@param     	string	$ref_client		Customer ref
2026
	 *  @param     	int		$notrigger		1=Does not execute triggers, 0= execute triggers
2027
	 *	@return		int						<0 if KO, >0 if OK
2028
	 */
2029
    public function set_ref_client($ref_client, $notrigger = 0)
2030
	{
2031
        // phpcs:enable
2032
	    global $user;
2033
2034
		$error = 0;
2035
2036
		$this->db->begin();
2037
2038
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
2039
		if (empty($ref_client))
2040
			$sql .= ' SET ref_client = NULL';
2041
		else
2042
			$sql .= ' SET ref_client = \''.$this->db->escape($ref_client).'\'';
2043
		$sql .= ' WHERE rowid = '.$this->id;
2044
2045
		dol_syslog(__METHOD__.' this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
2046
		$resql = $this->db->query($sql);
2047
		if (!$resql)
2048
		{
2049
			$this->errors[] = $this->db->error();
2050
			$error++;
2051
		}
2052
2053
		if (!$error)
2054
		{
2055
			$this->ref_client = $ref_client;
2056
		}
2057
2058
		if (!$notrigger && empty($error))
2059
		{
2060
			// Call trigger
2061
			$result = $this->call_trigger('BILL_MODIFY', $user);
2062
			if ($result < 0) $error++;
2063
			// End call triggers
2064
		}
2065
2066
		if (!$error)
2067
		{
2068
			$this->ref_client = $ref_client;
2069
2070
			$this->db->commit();
2071
			return 1;
2072
		}
2073
		else
2074
		{
2075
			foreach ($this->errors as $errmsg)
2076
			{
2077
				dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
2078
				$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2079
			}
2080
			$this->db->rollback();
2081
			return -1 * $error;
2082
		}
2083
	}
2084
2085
	/**
2086
	 *	Delete invoice
2087
	 *
2088
	 *	@param     	User	$user      	    User making the deletion.
2089
	 *	@param		int		$notrigger		1=Does not execute triggers, 0= execute triggers
2090
	 *	@param		int		$idwarehouse	Id warehouse to use for stock change.
2091
	 *	@return		int						<0 if KO, 0=Refused, >0 if OK
2092
	 */
2093
    public function delete($user, $notrigger = 0, $idwarehouse = -1)
2094
	{
2095
		global $langs, $conf;
2096
		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2097
2098
		$rowid = $this->id;
2099
2100
		dol_syslog(get_class($this)."::delete rowid=".$rowid.", ref=".$this->ref.", thirdparty=".$this->thirdparty->name, LOG_DEBUG);
2101
2102
		// Test to avoid invoice deletion (allowed if draft)
2103
		$result = $this->is_erasable();
2104
2105
		if ($result <= 0) return 0;
2106
2107
		$error = 0;
2108
2109
		$this->db->begin();
2110
2111
		if (!$error && !$notrigger)
2112
		{
2113
            // Call trigger
2114
            $result = $this->call_trigger('BILL_DELETE', $user);
2115
            if ($result < 0) $error++;
2116
            // End call triggers
2117
		}
2118
2119
		// Removed extrafields
2120
		if (!$error) {
2121
			$result = $this->deleteExtraFields();
2122
			if ($result < 0)
2123
			{
2124
				$error++;
2125
				dol_syslog(get_class($this)."::delete error deleteExtraFields ".$this->error, LOG_ERR);
2126
			}
2127
		}
2128
2129
		if (!$error)
2130
		{
2131
			// Delete linked object
2132
			$res = $this->deleteObjectLinked();
2133
			if ($res < 0) $error++;
2134
		}
2135
2136
		if (!$error)
2137
		{
2138
			// If invoice was converted into a discount not yet consumed, we remove discount
2139
			$sql = 'DELETE FROM '.MAIN_DB_PREFIX.'societe_remise_except';
2140
			$sql .= ' WHERE fk_facture_source = '.$rowid;
2141
			$sql .= ' AND fk_facture_line IS NULL';
2142
			$resql = $this->db->query($sql);
2143
2144
			// If invoice has consumned discounts
2145
			$this->fetch_lines();
2146
			$list_rowid_det = array();
2147
			foreach ($this->lines as $key => $invoiceline)
2148
			{
2149
				$list_rowid_det[] = $invoiceline->id;
2150
			}
2151
2152
			// Consumned discounts are freed
2153
			if (count($list_rowid_det))
2154
			{
2155
				$sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
2156
				$sql .= ' SET fk_facture = NULL, fk_facture_line = NULL';
2157
				$sql .= ' WHERE fk_facture_line IN ('.join(',', $list_rowid_det).')';
2158
2159
				dol_syslog(get_class($this)."::delete", LOG_DEBUG);
2160
				if (!$this->db->query($sql))
2161
				{
2162
					$this->error = $this->db->error()." sql=".$sql;
2163
					$this->db->rollback();
2164
					return -5;
2165
				}
2166
			}
2167
2168
			// If we decrease stock on invoice validation, we increase back if a warehouse id was provided
2169
			if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && !empty($conf->stock->enabled) && !empty($conf->global->STOCK_CALCULATE_ON_BILL) && $idwarehouse != -1)
2170
			{
2171
				require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2172
				$langs->load("agenda");
2173
2174
				$num = count($this->lines);
2175
				for ($i = 0; $i < $num; $i++)
2176
				{
2177
					if ($this->lines[$i]->fk_product > 0)
2178
					{
2179
						$mouvP = new MouvementStock($this->db);
2180
						$mouvP->origin = &$this;
2181
						// We decrease stock for product
2182
						if ($this->type == self::TYPE_CREDIT_NOTE) $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceDeleteDolibarr", $this->ref));
2183
						else $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
2184
					}
2185
				}
2186
			}
2187
2188
2189
			// Delete invoice line
2190
			$sql = 'DELETE FROM '.MAIN_DB_PREFIX.'facturedet WHERE fk_facture = '.$rowid;
2191
2192
			dol_syslog(get_class($this)."::delete", LOG_DEBUG);
2193
2194
			if ($this->db->query($sql) && $this->delete_linked_contact())
2195
			{
2196
				$sql = 'DELETE FROM '.MAIN_DB_PREFIX.'facture WHERE rowid = '.$rowid;
2197
2198
				dol_syslog(get_class($this)."::delete", LOG_DEBUG);
2199
2200
				$resql = $this->db->query($sql);
2201
				if ($resql)
2202
				{
2203
					// On efface le repertoire de pdf provisoire
2204
					$ref = dol_sanitizeFileName($this->ref);
2205
					if ($conf->facture->dir_output && !empty($this->ref))
2206
					{
2207
						$dir = $conf->facture->dir_output."/".$ref;
2208
						$file = $conf->facture->dir_output."/".$ref."/".$ref.".pdf";
2209
						if (file_exists($file))	// We must delete all files before deleting directory
2210
						{
2211
							$ret = dol_delete_preview($this);
2212
2213
							if (!dol_delete_file($file, 0, 0, 0, $this)) // For triggers
2214
							{
2215
								$langs->load("errors");
2216
								$this->error = $langs->trans("ErrorFailToDeleteFile", $file);
2217
								$this->db->rollback();
2218
								return 0;
2219
							}
2220
						}
2221
						if (file_exists($dir))
2222
						{
2223
							if (!dol_delete_dir_recursive($dir)) // For remove dir and meta
2224
							{
2225
								$langs->load("errors");
2226
								$this->error = $langs->trans("ErrorFailToDeleteDir", $dir);
2227
								$this->db->rollback();
2228
								return 0;
2229
							}
2230
						}
2231
					}
2232
2233
					$this->db->commit();
2234
					return 1;
2235
				}
2236
				else
2237
				{
2238
					$this->error = $this->db->lasterror()." sql=".$sql;
2239
					$this->db->rollback();
2240
					return -6;
2241
				}
2242
			}
2243
			else
2244
			{
2245
				$this->error = $this->db->lasterror()." sql=".$sql;
2246
				$this->db->rollback();
2247
				return -4;
2248
			}
2249
		}
2250
		else
2251
		{
2252
			$this->db->rollback();
2253
			return -2;
2254
		}
2255
	}
2256
2257
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2258
	/**
2259
	 *  Tag the invoice as paid completely (if close_code is filled) => this->fk_statut=2, this->paye=1
2260
	 *  or partialy (if close_code filled) + appel trigger BILL_PAYED => this->fk_statut=2, this->paye stay 0
2261
	 *
2262
	 *  @param	User	$user      	Object user that modify
2263
	 *	@param  string	$close_code	Code renseigne si on classe a payee completement alors que paiement incomplet (cas escompte par exemple)
2264
	 *	@param  string	$close_note	Commentaire renseigne si on classe a payee alors que paiement incomplet (cas escompte par exemple)
2265
	 *  @return int         		<0 if KO, >0 if OK
2266
	 */
2267
    public function set_paid($user, $close_code = '', $close_note = '')
2268
	{
2269
        // phpcs:enable
2270
		$error = 0;
2271
2272
		if ($this->paye != 1)
2273
		{
2274
			$this->db->begin();
2275
2276
			$now = dol_now();
2277
2278
			dol_syslog(get_class($this)."::set_paid rowid=".$this->id, LOG_DEBUG);
2279
2280
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture SET';
2281
			$sql .= ' fk_statut='.self::STATUS_CLOSED;
2282
			if (!$close_code) $sql .= ', paye=1';
2283
			if ($close_code) $sql .= ", close_code='".$this->db->escape($close_code)."'";
2284
			if ($close_note) $sql .= ", close_note='".$this->db->escape($close_note)."'";
2285
			$sql .= ', fk_user_closing = '.$user->id;
2286
			$sql .= ", date_closing = '".$this->db->idate($now)."'";
2287
			$sql .= ' WHERE rowid = '.$this->id;
2288
2289
			$resql = $this->db->query($sql);
2290
			if ($resql)
2291
			{
2292
	            // Call trigger
2293
	            $result = $this->call_trigger('BILL_PAYED', $user);
2294
	            if ($result < 0) $error++;
2295
	            // End call triggers
2296
			}
2297
			else
2298
			{
2299
				$error++;
2300
				$this->error = $this->db->lasterror();
2301
			}
2302
2303
			if (!$error)
2304
			{
2305
				$this->db->commit();
2306
				return 1;
2307
			}
2308
			else
2309
			{
2310
				$this->db->rollback();
2311
				return -1;
2312
			}
2313
		}
2314
		else
2315
		{
2316
			return 0;
2317
		}
2318
	}
2319
2320
2321
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2322
	/**
2323
	 *  Tag la facture comme non payee completement + appel trigger BILL_UNPAYED
2324
	 *	Fonction utilisee quand un paiement prelevement est refuse,
2325
	 * 	ou quand une facture annulee et reouverte.
2326
	 *
2327
	 *  @param	User	$user       Object user that change status
2328
	 *  @return int         		<0 if KO, >0 if OK
2329
	 */
2330
    public function set_unpaid($user)
2331
	{
2332
        // phpcs:enable
2333
		$error = 0;
2334
2335
		$this->db->begin();
2336
2337
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
2338
		$sql .= ' SET paye=0, fk_statut='.self::STATUS_VALIDATED.', close_code=null, close_note=null,';
2339
		$sql .= ' date_closing=null,';
2340
		$sql .= ' fk_user_closing=null';
2341
		$sql .= ' WHERE rowid = '.$this->id;
2342
2343
		dol_syslog(get_class($this)."::set_unpaid", LOG_DEBUG);
2344
		$resql = $this->db->query($sql);
2345
		if ($resql)
2346
		{
2347
            // Call trigger
2348
            $result = $this->call_trigger('BILL_UNPAYED', $user);
2349
            if ($result < 0) $error++;
2350
            // End call triggers
2351
		}
2352
		else
2353
		{
2354
			$error++;
2355
			$this->error = $this->db->error();
2356
			dol_print_error($this->db);
2357
		}
2358
2359
		if (!$error)
2360
		{
2361
			$this->db->commit();
2362
			return 1;
2363
		}
2364
		else
2365
		{
2366
			$this->db->rollback();
2367
			return -1;
2368
		}
2369
	}
2370
2371
2372
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2373
	/**
2374
	 *	Tag invoice as canceled, with no payment on it (example for replacement invoice or payment never received) + call trigger BILL_CANCEL
2375
	 *	Warning, if option to decrease stock on invoice was set, this function does not change stock (it might be a cancel because
2376
	 *  of no payment even if merchandises were sent).
2377
	 *
2378
	 *	@param	User	$user        	Object user making change
2379
	 *	@param	string	$close_code		Code of closing invoice (CLOSECODE_REPLACED, CLOSECODE_...)
2380
	 *	@param	string	$close_note		Comment
2381
	 *	@return int         			<0 if KO, >0 if OK
2382
	 */
2383
    public function set_canceled($user, $close_code = '', $close_note = '')
2384
	{
2385
        // phpcs:enable
2386
2387
		dol_syslog(get_class($this)."::set_canceled rowid=".$this->id, LOG_DEBUG);
2388
2389
		$this->db->begin();
2390
2391
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture SET';
2392
		$sql .= ' fk_statut='.self::STATUS_ABANDONED;
2393
		if ($close_code) $sql .= ", close_code='".$this->db->escape($close_code)."'";
2394
		if ($close_note) $sql .= ", close_note='".$this->db->escape($close_note)."'";
2395
		$sql .= ' WHERE rowid = '.$this->id;
2396
2397
		$resql = $this->db->query($sql);
2398
		if ($resql)
2399
		{
2400
			// On desaffecte de la facture les remises liees
2401
			// car elles n'ont pas ete utilisees vu que la facture est abandonnee.
2402
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
2403
			$sql .= ' SET fk_facture = NULL';
2404
			$sql .= ' WHERE fk_facture = '.$this->id;
2405
2406
			$resql = $this->db->query($sql);
2407
			if ($resql)
2408
			{
2409
	            // Call trigger
2410
	            $result = $this->call_trigger('BILL_CANCEL', $user);
2411
	            if ($result < 0)
2412
	            {
2413
					$this->db->rollback();
2414
					return -1;
2415
				}
2416
	            // End call triggers
2417
2418
				$this->db->commit();
2419
				return 1;
2420
			}
2421
			else
2422
			{
2423
				$this->error = $this->db->error()." sql=".$sql;
2424
				$this->db->rollback();
2425
				return -1;
2426
			}
2427
		}
2428
		else
2429
		{
2430
			$this->error = $this->db->error()." sql=".$sql;
2431
			$this->db->rollback();
2432
			return -2;
2433
		}
2434
	}
2435
2436
	/**
2437
	 * Tag invoice as validated + call trigger BILL_VALIDATE
2438
	 * Object must have lines loaded with fetch_lines
2439
	 *
2440
	 * @param	User	$user           Object user that validate
2441
	 * @param   string	$force_number	Reference to force on invoice
2442
	 * @param	int		$idwarehouse	Id of warehouse to use for stock decrease if option to decreasenon stock is on (0=no decrease)
2443
	 * @param	int		$notrigger		1=Does not execute triggers, 0= execute triggers
2444
	 * @param	int		$batch_rule		[=0] 0 not decrement batch, else batch rule to use
2445
	 *                                 	1=take in batches ordered by sellby and eatby dates
2446
     * @return	int						<0 if KO, 0=Nothing done because invoice is not a draft, >0 if OK
2447
	 */
2448
    public function validate($user, $force_number = '', $idwarehouse = 0, $notrigger = 0, $batch_rule = 0)
2449
	{
2450
		global $conf, $langs;
2451
		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2452
		$productStatic = null;
2453
		$warehouseStatic = null;
2454
		if ($batch_rule > 0) {
2455
			require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
2456
			require_once DOL_DOCUMENT_ROOT.'/product/class/productbatch.class.php';
2457
			require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
2458
			$productStatic = new Product($this->db);
2459
			$warehouseStatic = new Entrepot($this->db);
2460
		}
2461
2462
		$now = dol_now();
2463
2464
		$error = 0;
2465
		dol_syslog(get_class($this).'::validate user='.$user->id.', force_number='.$force_number.', idwarehouse='.$idwarehouse);
2466
2467
		// Force to have object complete for checks
2468
		$this->fetch_thirdparty();
2469
		$this->fetch_lines();
2470
2471
		// Check parameters
2472
		if (!$this->brouillon)
2473
		{
2474
			dol_syslog(get_class($this)."::validate no draft status", LOG_WARNING);
2475
			return 0;
2476
		}
2477
		if (count($this->lines) <= 0)
2478
		{
2479
        	$langs->load("errors");
2480
			$this->error = $langs->trans("ErrorObjectMustHaveLinesToBeValidated", $this->ref);
2481
			return -1;
2482
		}
2483
		if ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && empty($user->rights->facture->creer))
2484
       	|| (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && empty($user->rights->facture->invoice_advance->validate)))
2485
		{
2486
			$this->error = 'Permission denied';
2487
			dol_syslog(get_class($this)."::validate ".$this->error.' MAIN_USE_ADVANCED_PERMS='.$conf->global->MAIN_USE_ADVANCED_PERMS, LOG_ERR);
2488
			return -1;
2489
		}
2490
2491
		$this->db->begin();
2492
2493
		// Check parameters
2494
		if ($this->type == self::TYPE_REPLACEMENT)		// si facture de remplacement
2495
		{
2496
			// Controle que facture source connue
2497
			if ($this->fk_facture_source <= 0)
2498
			{
2499
				$this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("InvoiceReplacement"));
2500
				$this->db->rollback();
2501
				return -10;
2502
			}
2503
2504
			// Charge la facture source a remplacer
2505
			$facreplaced = new Facture($this->db);
2506
			$result = $facreplaced->fetch($this->fk_facture_source);
2507
			if ($result <= 0)
2508
			{
2509
				$this->error = $langs->trans("ErrorBadInvoice");
2510
				$this->db->rollback();
2511
				return -11;
2512
			}
2513
2514
			// Controle que facture source non deja remplacee par une autre
2515
			$idreplacement = $facreplaced->getIdReplacingInvoice('validated');
2516
			if ($idreplacement && $idreplacement != $this->id)
2517
			{
2518
				$facreplacement = new Facture($this->db);
2519
				$facreplacement->fetch($idreplacement);
2520
				$this->error = $langs->trans("ErrorInvoiceAlreadyReplaced", $facreplaced->ref, $facreplacement->ref);
2521
				$this->db->rollback();
2522
				return -12;
2523
			}
2524
2525
			$result = $facreplaced->set_canceled($user, self::CLOSECODE_REPLACED, '');
2526
			if ($result < 0)
2527
			{
2528
				$this->error = $facreplaced->error;
2529
				$this->db->rollback();
2530
				return -13;
2531
			}
2532
		}
2533
2534
		// Define new ref
2535
		if ($force_number)
2536
		{
2537
			$num = $force_number;
2538
		}
2539
		elseif (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref)) // empty should not happened, but when it occurs, the test save life
2540
		{
2541
			if (!empty($conf->global->FAC_FORCE_DATE_VALIDATION))	// If option enabled, we force invoice date
2542
			{
2543
				$this->date = dol_now();
2544
				$this->date_lim_reglement = $this->calculate_date_lim_reglement();
2545
			}
2546
			$num = $this->getNextNumRef($this->thirdparty);
2547
		}
2548
		else
2549
		{
2550
			$num = $this->ref;
2551
		}
2552
		$this->newref = dol_sanitizeFileName($num);
2553
2554
		if ($num)
2555
		{
2556
			$this->update_price(1);
2557
2558
			// Validate
2559
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
2560
			$sql .= " SET ref='".$num."', fk_statut = ".self::STATUS_VALIDATED.", fk_user_valid = ".($user->id > 0 ? $user->id : "null").", date_valid = '".$this->db->idate($now)."'";
2561
			if (!empty($conf->global->FAC_FORCE_DATE_VALIDATION))	// If option enabled, we force invoice date
2562
			{
2563
				$sql .= ", datef='".$this->db->idate($this->date)."'";
2564
				$sql .= ", date_lim_reglement='".$this->db->idate($this->date_lim_reglement)."'";
2565
			}
2566
			$sql .= ' WHERE rowid = '.$this->id;
2567
2568
			dol_syslog(get_class($this)."::validate", LOG_DEBUG);
2569
			$resql = $this->db->query($sql);
2570
			if (!$resql)
2571
			{
2572
				dol_print_error($this->db);
2573
				$error++;
2574
			}
2575
2576
			// On verifie si la facture etait une provisoire
2577
			if (!$error && (preg_match('/^[\(]?PROV/i', $this->ref)))
2578
			{
2579
				// La verif qu'une remise n'est pas utilisee 2 fois est faite au moment de l'insertion de ligne
2580
			}
2581
2582
			if (!$error)
2583
			{
2584
				// Define third party as a customer
2585
				$result = $this->thirdparty->set_as_client();
2586
2587
				// Si active on decremente le produit principal et ses composants a la validation de facture
2588
				if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && !empty($conf->stock->enabled) && !empty($conf->global->STOCK_CALCULATE_ON_BILL) && $idwarehouse > 0)
2589
				{
2590
					require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2591
					$langs->load("agenda");
2592
2593
					// Loop on each line
2594
					$cpt = count($this->lines);
2595
					for ($i = 0; $i < $cpt; $i++)
2596
					{
2597
						if ($this->lines[$i]->fk_product > 0)
2598
						{
2599
							$mouvP = new MouvementStock($this->db);
2600
							$mouvP->origin = &$this;
2601
							// We decrease stock for product
2602
							if ($this->type == self::TYPE_CREDIT_NOTE) {
2603
								$result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, 0, $langs->trans("InvoiceValidatedInDolibarr", $num));
2604
								if ($result < 0) {
2605
									$error++;
2606
									$this->error = $mouvP->error;
2607
								}
2608
							} else {
2609
								$is_batch_line = false;
2610
								if ($batch_rule > 0) {
2611
									$productStatic->fetch($this->lines[$i]->fk_product);
2612
									if ($productStatic->hasbatch()) {
2613
										$is_batch_line = true;
2614
										$product_qty_remain = $this->lines[$i]->qty;
2615
2616
										$sortfield = null;
2617
										$sortorder = null;
2618
										// find all batch order by sellby (DLC) and eatby dates (DLUO) first
2619
										if ($batch_rule == Productbatch::BATCH_RULE_SELLBY_EATBY_DATES_FIRST) {
2620
											$sortfield = 'pl.sellby,pl.eatby,pb.qty,pl.rowid';
2621
											$sortorder = 'ASC,ASC,ASC,ASC';
2622
										}
2623
2624
										$resBatchList = Productbatch::findAllForProduct($this->db, $productStatic->id, $idwarehouse, (!empty($conf->global->STOCK_ALLOW_NEGATIVE_TRANSFER) ? null : 0), $sortfield, $sortorder);
2625
										if (!is_array($resBatchList)) {
2626
											$error++;
2627
											$this->error = $this->db->lasterror();
2628
										}
2629
2630
										if (!$error) {
2631
											$batchList = $resBatchList;
2632
											if (empty($batchList)) {
2633
												$error++;
2634
												$langs->load('errors');
2635
												$warehouseStatic->fetch($idwarehouse);
2636
												$this->error = $langs->trans('ErrorBatchNoFoundForProductInWarehouse', $productStatic->label, $warehouseStatic->ref);
2637
												dol_syslog(__METHOD__.' Error: '.$langs->transnoentitiesnoconv('ErrorBatchNoFoundForProductInWarehouse', $productStatic->label, $warehouseStatic->ref), LOG_ERR);
2638
											}
2639
2640
											foreach ($batchList as $batch) {
2641
												if ($batch->qty <= 0) continue; // try to decrement only batches have positive quantity first
2642
2643
												// enough quantity in this batch
2644
												if ($batch->qty >= $product_qty_remain) {
2645
													$product_batch_qty = $product_qty_remain;
2646
												} // not enough (take all in batch)
2647
												else {
2648
													$product_batch_qty = $batch->qty;
2649
												}
2650
												$result = $mouvP->livraison($user, $productStatic->id, $idwarehouse, $product_batch_qty, $this->lines[$i]->subprice, $langs->trans('InvoiceValidatedInDolibarr', $num), '', '', '', $batch->batch);
2651
												if ($result < 0) {
2652
													$error++;
2653
													$this->error = $mouvP->error;
2654
													break;
2655
												}
2656
2657
												$product_qty_remain -= $product_batch_qty;
2658
												// all product quantity was decremented
2659
												if ($product_qty_remain <= 0) break;
2660
											}
2661
2662
											if (!$error && $product_qty_remain > 0) {
2663
												if ($conf->global->STOCK_ALLOW_NEGATIVE_TRANSFER) {
2664
													// take in the first batch
2665
													$batch = $batchList[0];
2666
													$result = $mouvP->livraison($user, $productStatic->id, $idwarehouse, $product_qty_remain, $this->lines[$i]->subprice, $langs->trans('InvoiceValidatedInDolibarr', $num), '', '', '', $batch->batch);
2667
													if ($result < 0) {
2668
														$error++;
2669
														$this->error = $mouvP->error;
2670
													}
2671
												} else {
2672
													$error++;
2673
													$langs->load('errors');
2674
													$warehouseStatic->fetch($idwarehouse);
2675
													$this->error = $langs->trans('ErrorBatchNoFoundEnoughQuantityForProductInWarehouse', $productStatic->label, $warehouseStatic->ref);
2676
													dol_syslog(__METHOD__.' Error: '.$langs->transnoentitiesnoconv('ErrorBatchNoFoundEnoughQuantityForProductInWarehouse', $productStatic->label, $warehouseStatic->ref), LOG_ERR);
2677
												}
2678
											}
2679
										}
2680
									}
2681
								}
2682
2683
								if (!$is_batch_line) {
2684
									$result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceValidatedInDolibarr", $num));
2685
									if ($result < 0) {
2686
										$error++;
2687
										$this->error = $mouvP->error;
2688
									}
2689
								}
2690
							}
2691
						}
2692
					}
2693
				}
2694
			}
2695
2696
			// Trigger calls
2697
			if (!$error && !$notrigger)
2698
			{
2699
	            // Call trigger
2700
	            $result = $this->call_trigger('BILL_VALIDATE', $user);
2701
	            if ($result < 0) $error++;
2702
	            // End call triggers
2703
			}
2704
2705
			if (!$error)
2706
			{
2707
				$this->oldref = $this->ref;
2708
2709
				// Rename directory if dir was a temporary ref
2710
				if (preg_match('/^[\(]?PROV/i', $this->ref))
2711
				{
2712
					// Now we rename also files into index
2713
					$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)."'";
2714
					$sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'facture/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
2715
					$resql = $this->db->query($sql);
2716
					if (!$resql) { $error++; $this->error = $this->db->lasterror(); }
2717
2718
					// We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
2719
					$oldref = dol_sanitizeFileName($this->ref);
2720
					$newref = dol_sanitizeFileName($num);
2721
					$dirsource = $conf->facture->dir_output.'/'.$oldref;
2722
					$dirdest = $conf->facture->dir_output.'/'.$newref;
2723
					if (!$error && file_exists($dirsource))
2724
					{
2725
						dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
2726
2727
						if (@rename($dirsource, $dirdest))
2728
						{
2729
							dol_syslog("Rename ok");
2730
	                        // Rename docs starting with $oldref with $newref
2731
	                        $listoffiles = dol_dir_list($conf->facture->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
2732
	                        foreach ($listoffiles as $fileentry)
2733
	                        {
2734
	                        	$dirsource = $fileentry['name'];
2735
	                        	$dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
2736
	                        	$dirsource = $fileentry['path'].'/'.$dirsource;
2737
	                        	$dirdest = $fileentry['path'].'/'.$dirdest;
2738
	                        	@rename($dirsource, $dirdest);
2739
	                        }
2740
						}
2741
					}
2742
				}
2743
			}
2744
2745
			if (!$error && !$this->is_last_in_cycle())
2746
			{
2747
				if (!$this->updatePriceNextInvoice($langs))
2748
				{
2749
					$error++;
2750
				}
2751
			}
2752
2753
			// Set new ref and define current status
2754
			if (!$error)
2755
			{
2756
				$this->ref = $num;
2757
				$this->ref = $num;
2758
				$this->statut = self::STATUS_VALIDATED;
2759
				$this->brouillon = 0;
2760
				$this->date_validation = $now;
2761
				$i = 0;
2762
2763
                if (!empty($conf->global->INVOICE_USE_SITUATION))
2764
                {
2765
                	$final = true;
2766
    				$nboflines = count($this->lines);
2767
    				while (($i < $nboflines) && $final) {
2768
    					$final = ($this->lines[$i]->situation_percent == 100);
2769
    					$i++;
2770
    				}
2771
2772
    				if (empty($final)) $this->situation_final = 0;
2773
    				else $this->situation_final = 1;
2774
2775
				    $this->setFinal($user);
2776
                }
2777
			}
2778
		}
2779
		else
2780
		{
2781
			$error++;
2782
		}
2783
2784
		if (!$error)
2785
		{
2786
			$this->db->commit();
2787
			return 1;
2788
		}
2789
		else
2790
		{
2791
			$this->db->rollback();
2792
			return -1;
2793
		}
2794
	}
2795
2796
	/**
2797
	 * Update price of next invoice
2798
	 *
2799
	 * @param	Translate	$langs	Translate object
2800
	 * @return bool		false if KO, true if OK
2801
	 */
2802
    public function updatePriceNextInvoice(&$langs)
2803
	{
2804
		foreach ($this->tab_next_situation_invoice as $next_invoice)
2805
		{
2806
			$is_last = $next_invoice->is_last_in_cycle();
2807
2808
			if ($next_invoice->statut == self::STATUS_DRAFT && $is_last != 1)
2809
			{
2810
				$this->error = $langs->trans('updatePriceNextInvoiceErrorUpdateline', $next_invoice->ref);
2811
				return false;
2812
			}
2813
2814
			$next_invoice->brouillon = 1;
2815
			foreach ($next_invoice->lines as $line)
2816
			{
2817
				$result = $next_invoice->updateline(
2818
					$line->id, $line->desc, $line->subprice, $line->qty, $line->remise_percent,
2819
					$line->date_start, $line->date_end, $line->tva_tx, $line->localtax1_tx, $line->localtax2_tx, 'HT', $line->info_bits, $line->product_type,
2820
					$line->fk_parent_line, 0, $line->fk_fournprice, $line->pa_ht, $line->label, $line->special_code, $line->array_options, $line->situation_percent,
2821
					$line->fk_unit
2822
				);
2823
2824
				if ($result < 0)
2825
				{
2826
					$this->error = $langs->trans('updatePriceNextInvoiceErrorUpdateline', $next_invoice->ref);
2827
					return false;
2828
				}
2829
			}
2830
2831
			break; // Only the next invoice and not each next invoice
2832
		}
2833
2834
		return true;
2835
	}
2836
2837
	/**
2838
	 *	Set draft status
2839
	 *
2840
	 *	@param	User	$user			Object user that modify
2841
	 *	@param	int		$idwarehouse	Id warehouse to use for stock change.
2842
	 *	@return	int						<0 if KO, >0 if OK
2843
	 */
2844
	public function setDraft($user, $idwarehouse = -1)
2845
	{
2846
        // phpcs:enable
2847
		global $conf, $langs;
2848
2849
		$error = 0;
2850
2851
		if ($this->statut == self::STATUS_DRAFT)
2852
		{
2853
			dol_syslog(__METHOD__." already draft status", LOG_WARNING);
2854
			return 0;
2855
		}
2856
2857
		dol_syslog(__METHOD__, LOG_DEBUG);
2858
2859
		$this->db->begin();
2860
2861
		$sql = "UPDATE ".MAIN_DB_PREFIX."facture";
2862
		$sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2863
		$sql .= " WHERE rowid = ".$this->id;
2864
2865
		$result = $this->db->query($sql);
2866
		if ($result)
2867
		{
2868
			if (!$error)
2869
			{
2870
				$this->oldcopy = clone $this;
2871
			}
2872
2873
			// If we decrease stock on invoice validation, we increase back
2874
			if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && !empty($conf->stock->enabled) && !empty($conf->global->STOCK_CALCULATE_ON_BILL))
2875
			{
2876
				require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2877
				$langs->load("agenda");
2878
2879
				$num = count($this->lines);
2880
				for ($i = 0; $i < $num; $i++)
2881
				{
2882
					if ($this->lines[$i]->fk_product > 0)
2883
					{
2884
						$mouvP = new MouvementStock($this->db);
2885
						$mouvP->origin = &$this;
2886
						// We decrease stock for product
2887
						if ($this->type == self::TYPE_CREDIT_NOTE) $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceBackToDraftInDolibarr", $this->ref));
2888
						else $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
2889
					}
2890
				}
2891
			}
2892
2893
			if ($error == 0)
2894
			{
2895
				$old_statut = $this->statut;
2896
				$this->brouillon = 1;
2897
				$this->statut = self::STATUS_DRAFT;
2898
2899
				// Call trigger
2900
				$result = $this->call_trigger('BILL_UNVALIDATE', $user);
2901
				if ($result < 0)
2902
				{
2903
					$error++;
2904
					$this->statut = $old_statut;
2905
					$this->brouillon = 0;
2906
				}
2907
				// End call triggers
2908
			} else {
2909
				$this->db->rollback();
2910
				return -1;
2911
			}
2912
2913
			if ($error == 0)
2914
			{
2915
				$this->db->commit();
2916
				return 1;
2917
			}
2918
			else
2919
			{
2920
				$this->db->rollback();
2921
				return -1;
2922
			}
2923
		}
2924
		else
2925
		{
2926
			$this->error = $this->db->error();
2927
			$this->db->rollback();
2928
			return -1;
2929
		}
2930
	}
2931
2932
2933
	/**
2934
	 *  Add an invoice line into database (linked to product/service or not).
2935
	 *  Les parametres sont deja cense etre juste et avec valeurs finales a l'appel
2936
	 *  de cette methode. Aussi, pour le taux tva, il doit deja avoir ete defini
2937
	 *  par l'appelant par la methode get_default_tva(societe_vendeuse,societe_acheteuse,produit)
2938
	 *  et le desc doit deja avoir la bonne valeur (a l'appelant de gerer le multilangue)
2939
	 *
2940
	 *  @param    	string		$desc            	Description of line
2941
	 *  @param    	double		$pu_ht              Unit price without tax (> 0 even for credit note)
2942
	 *  @param    	double		$qty             	Quantity
2943
	 *  @param    	double		$txtva           	Force Vat rate, -1 for auto (Can contain the vat_src_code too with syntax '9.9 (CODE)')
2944
	 *  @param		double		$txlocaltax1		Local tax 1 rate (deprecated, use instead txtva with code inside)
2945
	 *  @param		double		$txlocaltax2		Local tax 2 rate (deprecated, use instead txtva with code inside)
2946
	 *  @param    	int			$fk_product      	Id of predefined product/service
2947
	 *  @param    	double		$remise_percent  	Percent of discount on line
2948
	 *  @param    	int			$date_start      	Date start of service
2949
	 *  @param    	int			$date_end        	Date end of service
2950
	 *  @param    	int			$ventil          	Code of dispatching into accountancy
2951
	 *  @param    	int			$info_bits			Bits of type of lines
2952
	 *  @param    	int			$fk_remise_except	Id discount used
2953
	 *  @param		string		$price_base_type	'HT' or 'TTC'
2954
	 *  @param    	double		$pu_ttc             Unit price with tax (> 0 even for credit note)
2955
	 *  @param		int			$type				Type of line (0=product, 1=service). Not used if fk_product is defined, the type of product is used.
2956
	 *  @param      int			$rang               Position of line
2957
	 *  @param		int			$special_code		Special code (also used by externals modules!)
2958
	 *  @param		string		$origin				Depend on global conf MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION can be 'orderdet', 'propaldet'..., else 'order','propal,'....
2959
	 *  @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
2960
	 *  @param		int			$fk_parent_line		Id of parent line
2961
	 *  @param		int			$fk_fournprice		Supplier price id (to calculate margin) or ''
2962
	 *  @param		int			$pa_ht				Buying price of line (to calculate margin) or ''
2963
	 *  @param		string		$label				Label of the line (deprecated, do not use)
2964
	 *  @param		array		$array_options		extrafields array
2965
	 *  @param      int         $situation_percent  Situation advance percentage
2966
	 *  @param      int         $fk_prev_id         Previous situation line id reference
2967
	 *  @param 		string		$fk_unit 			Code of the unit to use. Null to use the default one
2968
	 *  @param		double		$pu_ht_devise		Unit price in currency
2969
	 *  @return    	int             				<0 if KO, Id of line if OK
2970
	 */
2971
    public function addline(
2972
        $desc,
2973
        $pu_ht,
2974
        $qty,
2975
        $txtva,
2976
        $txlocaltax1 = 0,
2977
        $txlocaltax2 = 0,
2978
        $fk_product = 0,
2979
        $remise_percent = 0,
2980
        $date_start = '',
2981
        $date_end = '',
2982
        $ventil = 0,
2983
        $info_bits = 0,
2984
        $fk_remise_except = '',
2985
        $price_base_type = 'HT',
2986
        $pu_ttc = 0,
2987
        $type = self::TYPE_STANDARD,
2988
        $rang = -1,
2989
        $special_code = 0,
2990
        $origin = '',
2991
        $origin_id = 0,
2992
        $fk_parent_line = 0,
2993
        $fk_fournprice = null,
2994
        $pa_ht = 0,
2995
        $label = '',
2996
        $array_options = 0,
2997
        $situation_percent = 100,
2998
        $fk_prev_id = 0,
2999
        $fk_unit = null,
3000
        $pu_ht_devise = 0
3001
    ) {
3002
		// Deprecation warning
3003
		if ($label) {
3004
			dol_syslog(__METHOD__.": using line label is deprecated", LOG_WARNING);
3005
			//var_dump(debug_backtrace(false));exit;
3006
		}
3007
3008
		global $mysoc, $conf, $langs;
3009
3010
		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);
3011
3012
		if ($this->statut == self::STATUS_DRAFT)
3013
		{
3014
			include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
3015
3016
			// Clean parameters
3017
			if (empty($remise_percent)) $remise_percent = 0;
3018
			if (empty($qty)) $qty = 0;
3019
			if (empty($info_bits)) $info_bits = 0;
3020
			if (empty($rang)) $rang = 0;
3021
			if (empty($ventil)) $ventil = 0;
3022
			if (empty($txtva)) $txtva = 0;
3023
			if (empty($txlocaltax1)) $txlocaltax1 = 0;
3024
			if (empty($txlocaltax2)) $txlocaltax2 = 0;
3025
			if (empty($fk_parent_line) || $fk_parent_line < 0) $fk_parent_line = 0;
3026
			if (empty($fk_prev_id)) $fk_prev_id = 'null';
3027
			if (!isset($situation_percent) || $situation_percent > 100 || (string) $situation_percent == '') $situation_percent = 100;
3028
3029
			$remise_percent = price2num($remise_percent);
3030
			$qty = price2num($qty);
3031
			$pu_ht = price2num($pu_ht);
3032
			$pu_ht_devise = price2num($pu_ht_devise);
3033
			$pu_ttc = price2num($pu_ttc);
3034
			$pa_ht = price2num($pa_ht);
3035
			if (!preg_match('/\((.*)\)/', $txtva)) {
3036
				$txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
3037
			}
3038
			$txlocaltax1 = price2num($txlocaltax1);
3039
			$txlocaltax2 = price2num($txlocaltax2);
3040
3041
			if ($price_base_type == 'HT')
3042
			{
3043
				$pu = $pu_ht;
3044
			}
3045
			else
3046
			{
3047
				$pu = $pu_ttc;
3048
			}
3049
3050
			// Check parameters
3051
			if ($type < 0) return -1;
3052
3053
			if ($date_start && $date_end && $date_start > $date_end) {
3054
				$langs->load("errors");
3055
				$this->error = $langs->trans('ErrorStartDateGreaterEnd');
3056
				return -1;
3057
			}
3058
3059
			$this->db->begin();
3060
3061
			$product_type = $type;
3062
			if (!empty($fk_product))
3063
			{
3064
				$product = new Product($this->db);
3065
				$result = $product->fetch($fk_product);
3066
				$product_type = $product->type;
3067
3068
				if (!empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_INVOICE) && $product_type == 0 && $product->stock_reel < $qty) {
3069
                    $langs->load("errors");
3070
				    $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnInvoice', $product->ref);
3071
					$this->db->rollback();
3072
					return -3;
3073
				}
3074
			}
3075
3076
			$localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
3077
3078
			// Clean vat code
3079
			$reg = array();
3080
			$vat_src_code = '';
3081
			if (preg_match('/\((.*)\)/', $txtva, $reg))
3082
			{
3083
				$vat_src_code = $reg[1];
3084
				$txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
3085
			}
3086
3087
			// Calcul du total TTC et de la TVA pour la ligne a partir de
3088
			// qty, pu, remise_percent et txtva
3089
			// TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
3090
			// la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
3091
3092
			$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);
3093
3094
			$total_ht  = $tabprice[0];
3095
			$total_tva = $tabprice[1];
3096
			$total_ttc = $tabprice[2];
3097
			$total_localtax1 = $tabprice[9];
3098
			$total_localtax2 = $tabprice[10];
3099
			$pu_ht = $tabprice[3];
3100
3101
			// MultiCurrency
3102
			$multicurrency_total_ht = $tabprice[16];
3103
            $multicurrency_total_tva = $tabprice[17];
3104
            $multicurrency_total_ttc = $tabprice[18];
3105
			$pu_ht_devise = $tabprice[19];
3106
3107
			// Rank to use
3108
			$ranktouse = $rang;
3109
			if ($ranktouse == -1)
3110
			{
3111
				$rangmax = $this->line_max($fk_parent_line);
3112
				$ranktouse = $rangmax + 1;
3113
			}
3114
3115
			// Insert line
3116
			$this->line = new FactureLigne($this->db);
3117
3118
			$this->line->context = $this->context;
3119
3120
			$this->line->fk_facture = $this->id;
3121
			$this->line->label = $label; // deprecated
1 ignored issue
show
Deprecated Code introduced by
The property FactureLigne::$label has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

3121
			/** @scrutinizer ignore-deprecated */ $this->line->label = $label; // deprecated
Loading history...
3122
			$this->line->desc = $desc;
3123
3124
			$this->line->qty = ($this->type == self::TYPE_CREDIT_NOTE ?abs($qty) : $qty); // For credit note, quantity is always positive and unit price negative
3125
			$this->line->subprice = ($this->type == self::TYPE_CREDIT_NOTE ?-abs($pu_ht) : $pu_ht); // For credit note, unit price always negative, always positive otherwise
3126
3127
			$this->line->vat_src_code = $vat_src_code;
3128
			$this->line->tva_tx = $txtva;
3129
			$this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
3130
			$this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
3131
			$this->line->localtax1_type = $localtaxes_type[0];
3132
			$this->line->localtax2_type = $localtaxes_type[2];
3133
3134
			$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
3135
			$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
3136
			$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
3137
			$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
3138
			$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
3139
3140
			$this->line->fk_product = $fk_product;
3141
			$this->line->product_type = $product_type;
3142
			$this->line->remise_percent = $remise_percent;
3143
			$this->line->date_start = $date_start;
3144
			$this->line->date_end = $date_end;
3145
			$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...
3146
			$this->line->rang = $ranktouse;
3147
			$this->line->info_bits = $info_bits;
3148
			$this->line->fk_remise_except = $fk_remise_except;
3149
3150
			$this->line->special_code = $special_code;
3151
			$this->line->fk_parent_line = $fk_parent_line;
3152
			$this->line->origin = $origin;
3153
			$this->line->origin_id = $origin_id;
3154
			$this->line->situation_percent = $situation_percent;
3155
			$this->line->fk_prev_id = $fk_prev_id;
3156
			$this->line->fk_unit = $fk_unit;
3157
3158
			// infos marge
3159
			$this->line->fk_fournprice = $fk_fournprice;
3160
			$this->line->pa_ht = $pa_ht;
3161
3162
			// Multicurrency
3163
			$this->line->fk_multicurrency = $this->fk_multicurrency;
3164
			$this->line->multicurrency_code = $this->multicurrency_code;
3165
			$this->line->multicurrency_subprice		= $pu_ht_devise;
3166
			$this->line->multicurrency_total_ht 	= $multicurrency_total_ht;
3167
            $this->line->multicurrency_total_tva 	= $multicurrency_total_tva;
3168
            $this->line->multicurrency_total_ttc 	= $multicurrency_total_ttc;
3169
3170
			if (is_array($array_options) && count($array_options) > 0) {
3171
				$this->line->array_options = $array_options;
3172
			}
3173
3174
			$result = $this->line->insert();
3175
			if ($result > 0)
3176
			{
3177
				// Reorder if child line
3178
				if (!empty($fk_parent_line)) $this->line_order(true, 'DESC');
3179
3180
				// Mise a jour informations denormalisees au niveau de la facture meme
3181
				$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.
3182
3183
				if ($result > 0)
3184
				{
3185
					$this->db->commit();
3186
					return $this->line->id;
3187
				}
3188
				else
3189
				{
3190
					$this->error = $this->db->lasterror();
3191
					$this->db->rollback();
3192
					return -1;
3193
				}
3194
			}
3195
			else
3196
			{
3197
				$this->error = $this->line->error;
3198
				$this->db->rollback();
3199
				return -2;
3200
			}
3201
		}
3202
		else
3203
		{
3204
			dol_syslog(get_class($this)."::addline status of order must be Draft to allow use of ->addline()", LOG_ERR);
3205
			return -3;
3206
		}
3207
	}
3208
3209
	/**
3210
	 *  Update a detail line
3211
	 *
3212
	 *  @param     	int			$rowid           	Id of line to update
3213
	 *  @param     	string		$desc            	Description of line
3214
	 *  @param     	double		$pu              	Prix unitaire (HT ou TTC selon price_base_type) (> 0 even for credit note lines)
3215
	 *  @param     	double		$qty             	Quantity
3216
	 *  @param     	double		$remise_percent  	Percentage discount of the line
3217
	 *  @param     	int		    $date_start      	Date de debut de validite du service
3218
	 *  @param     	int		    $date_end        	Date de fin de validite du service
3219
	 *  @param     	double		$txtva          	VAT Rate (Can be '8.5', '8.5 (ABC)')
3220
	 * 	@param		double		$txlocaltax1		Local tax 1 rate
3221
	 *  @param		double		$txlocaltax2		Local tax 2 rate
3222
	 * 	@param     	string		$price_base_type 	HT or TTC
3223
	 * 	@param     	int			$info_bits 		    Miscellaneous informations
3224
	 * 	@param		int			$type				Type of line (0=product, 1=service)
3225
	 * 	@param		int			$fk_parent_line		Id of parent line (0 in most cases, used by modules adding sublevels into lines).
3226
	 * 	@param		int			$skip_update_total	Keep fields total_xxx to 0 (used for special lines by some modules)
3227
	 * 	@param		int			$fk_fournprice		Id of origin supplier price
3228
	 * 	@param		int			$pa_ht				Price (without tax) of product when it was bought
3229
	 * 	@param		string		$label				Label of the line (deprecated, do not use)
3230
	 * 	@param		int			$special_code		Special code (also used by externals modules!)
3231
     *  @param		array		$array_options		extrafields array
3232
	 * 	@param      int         $situation_percent  Situation advance percentage
3233
	 * 	@param 		string		$fk_unit 			Code of the unit to use. Null to use the default one
3234
	 * 	@param		double		$pu_ht_devise		Unit price in currency
3235
	 * 	@param		int			$notrigger			disable line update trigger
3236
	 *  @return    	int             				< 0 if KO, > 0 if OK
3237
	 */
3238
    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)
3239
	{
3240
		global $conf, $user;
3241
		// Deprecation warning
3242
		if ($label) {
3243
			dol_syslog(__METHOD__.": using line label is deprecated", LOG_WARNING);
3244
		}
3245
3246
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
3247
3248
		global $mysoc, $langs;
3249
3250
		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);
3251
3252
		if ($this->brouillon)
3253
		{
3254
			if (!$this->is_last_in_cycle() && empty($this->error))
3255
			{
3256
				if (!$this->checkProgressLine($rowid, $situation_percent))
3257
				{
3258
					if (!$this->error) $this->error = $langs->trans('invoiceLineProgressError');
3259
					return -3;
3260
				}
3261
			}
3262
3263
			if ($date_start && $date_end && $date_start > $date_end) {
3264
				$langs->load("errors");
3265
				$this->error = $langs->trans('ErrorStartDateGreaterEnd');
3266
				return -1;
3267
			}
3268
3269
			$this->db->begin();
3270
3271
			// Clean parameters
3272
			if (empty($qty)) $qty = 0;
3273
			if (empty($fk_parent_line) || $fk_parent_line < 0) $fk_parent_line = 0;
3274
			if (empty($special_code) || $special_code == 3) $special_code = 0;
3275
			if (!isset($situation_percent) || $situation_percent > 100 || (string) $situation_percent == '') $situation_percent = 100;
3276
3277
			$remise_percent = price2num($remise_percent);
3278
			$qty			= price2num($qty);
3279
			$pu 			= price2num($pu);
3280
        	$pu_ht_devise = price2num($pu_ht_devise);
3281
			$pa_ht			= price2num($pa_ht);
3282
			$txtva			= price2num($txtva);
3283
			$txlocaltax1	= price2num($txlocaltax1);
3284
			$txlocaltax2	= price2num($txlocaltax2);
3285
3286
			// Check parameters
3287
			if ($type < 0) return -1;
3288
3289
			// Calculate total with, without tax and tax from qty, pu, remise_percent and txtva
3290
			// TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
3291
			// la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
3292
3293
			$localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
3294
3295
			// Clean vat code
3296
    		$vat_src_code = '';
3297
    		if (preg_match('/\((.*)\)/', $txtva, $reg))
3298
    		{
3299
    		    $vat_src_code = $reg[1];
3300
    		    $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
3301
    		}
3302
3303
			$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);
3304
3305
			$total_ht  = $tabprice[0];
3306
			$total_tva = $tabprice[1];
3307
			$total_ttc = $tabprice[2];
3308
			$total_localtax1 = $tabprice[9];
3309
			$total_localtax2 = $tabprice[10];
3310
			$pu_ht  = $tabprice[3];
3311
			$pu_tva = $tabprice[4];
3312
			$pu_ttc = $tabprice[5];
3313
3314
			// MultiCurrency
3315
			$multicurrency_total_ht = $tabprice[16];
3316
            $multicurrency_total_tva = $tabprice[17];
3317
            $multicurrency_total_ttc = $tabprice[18];
3318
			$pu_ht_devise = $tabprice[19];
3319
3320
			// Old properties: $price, $remise (deprecated)
3321
			$price = $pu;
3322
			$remise = 0;
3323
			if ($remise_percent > 0)
3324
			{
3325
				$remise = round(($pu * $remise_percent / 100), 2);
3326
				$price = ($pu - $remise);
3327
			}
3328
			$price = price2num($price);
3329
3330
			//Fetch current line from the database and then clone the object and set it in $oldline property
3331
			$line = new FactureLigne($this->db);
3332
			$line->fetch($rowid);
3333
			$line->fetch_optionals();
3334
3335
			if (!empty($line->fk_product))
3336
			{
3337
				$product = new Product($this->db);
3338
				$result = $product->fetch($line->fk_product);
3339
				$product_type = $product->type;
3340
3341
				if (!empty($conf->global->STOCK_MUST_BE_ENOUGH_FOR_INVOICE) && $product_type == 0 && $product->stock_reel < $qty) {
3342
                    $langs->load("errors");
3343
				    $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnInvoice', $product->ref);
3344
					$this->db->rollback();
3345
					return -3;
3346
				}
3347
			}
3348
3349
			$staticline = clone $line;
3350
3351
			$line->oldline = $staticline;
3352
			$this->line = $line;
3353
            $this->line->context = $this->context;
3354
3355
			// Reorder if fk_parent_line change
3356
			if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line)
3357
			{
3358
				$rangmax = $this->line_max($fk_parent_line);
3359
				$this->line->rang = $rangmax + 1;
3360
			}
3361
3362
			$this->line->id = $rowid;
3363
			$this->line->rowid				= $rowid;
1 ignored issue
show
Deprecated Code introduced by
The property CommonObjectLine::$rowid has been deprecated: Try to use id property as possible (even if field into database is still rowid) ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

3363
			/** @scrutinizer ignore-deprecated */ $this->line->rowid				= $rowid;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
3364
			$this->line->label				= $label;
1 ignored issue
show
Deprecated Code introduced by
The property FactureLigne::$label has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

3364
			/** @scrutinizer ignore-deprecated */ $this->line->label				= $label;
Loading history...
3365
			$this->line->desc = $desc;
3366
			$this->line->qty = ($this->type == self::TYPE_CREDIT_NOTE ?abs($qty) : $qty); // For credit note, quantity is always positive and unit price negative
3367
3368
			$this->line->vat_src_code = $vat_src_code;
3369
			$this->line->tva_tx = $txtva;
3370
			$this->line->localtax1_tx		= $txlocaltax1;
3371
			$this->line->localtax2_tx		= $txlocaltax2;
3372
			$this->line->localtax1_type		= $localtaxes_type[0];
3373
			$this->line->localtax2_type		= $localtaxes_type[2];
3374
3375
			$this->line->remise_percent		= $remise_percent;
3376
			$this->line->subprice			= ($this->type == 2 ?-abs($pu_ht) : $pu_ht); // For credit note, unit price always negative, always positive otherwise
3377
			$this->line->date_start = $date_start;
3378
			$this->line->date_end			= $date_end;
3379
			$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
3380
			$this->line->total_tva			= (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ?-abs($total_tva) : $total_tva);
3381
			$this->line->total_localtax1	= $total_localtax1;
3382
			$this->line->total_localtax2	= $total_localtax2;
3383
			$this->line->total_ttc			= (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ?-abs($total_ttc) : $total_ttc);
3384
			$this->line->info_bits			= $info_bits;
3385
			$this->line->special_code		= $special_code;
3386
			$this->line->product_type		= $type;
3387
			$this->line->fk_parent_line = $fk_parent_line;
3388
			$this->line->skip_update_total = $skip_update_total;
3389
			$this->line->situation_percent = $situation_percent;
3390
			$this->line->fk_unit = $fk_unit;
3391
3392
			$this->line->fk_fournprice = $fk_fournprice;
3393
			$this->line->pa_ht = $pa_ht;
3394
3395
			// Multicurrency
3396
			$this->line->multicurrency_subprice		= $pu_ht_devise;
3397
			$this->line->multicurrency_total_ht 	= $multicurrency_total_ht;
3398
            $this->line->multicurrency_total_tva 	= $multicurrency_total_tva;
3399
            $this->line->multicurrency_total_ttc 	= $multicurrency_total_ttc;
3400
3401
			if (is_array($array_options) && count($array_options) > 0) {
3402
				// We replace values in this->line->array_options only for entries defined into $array_options
3403
				foreach ($array_options as $key => $value) {
3404
					$this->line->array_options[$key] = $array_options[$key];
3405
				}
3406
			}
3407
3408
			$result = $this->line->update($user, $notrigger);
3409
			if ($result > 0)
3410
			{
3411
				// Reorder if child line
3412
				if (!empty($fk_parent_line)) $this->line_order(true, 'DESC');
3413
3414
				// Mise a jour info denormalisees au niveau facture
3415
				$this->update_price(1);
3416
				$this->db->commit();
3417
				return $result;
3418
			}
3419
			else
3420
			{
3421
			    $this->error = $this->line->error;
3422
				$this->db->rollback();
3423
				return -1;
3424
			}
3425
		}
3426
		else
3427
		{
3428
			$this->error = "Invoice statut makes operation forbidden";
3429
			return -2;
3430
		}
3431
	}
3432
3433
	/**
3434
	 * Check if the percent edited is lower of next invoice line
3435
	 *
3436
	 * @param	int		$idline				id of line to check
3437
	 * @param	float	$situation_percent	progress percentage need to be test
3438
	 * @return false if KO, true if OK
3439
	 */
3440
    public function checkProgressLine($idline, $situation_percent)
3441
	{
3442
		$sql = 'SELECT fd.situation_percent FROM '.MAIN_DB_PREFIX.'facturedet fd
3443
				INNER JOIN '.MAIN_DB_PREFIX.'facture f ON (fd.fk_facture = f.rowid)
3444
				WHERE fd.fk_prev_id = '.$idline.'
3445
				AND f.fk_statut <> 0';
3446
3447
		$result = $this->db->query($sql);
3448
		if (!$result)
3449
		{
3450
			$this->error = $this->db->error();
3451
			return false;
3452
		}
3453
3454
		$obj = $this->db->fetch_object($result);
3455
3456
		if ($obj === null) return true;
3457
		else return $situation_percent < $obj->situation_percent;
3458
	}
3459
3460
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3461
	/**
3462
	 * Update invoice line with percentage
3463
	 *
3464
	 * @param  FactureLigne $line       Invoice line
3465
	 * @param  int          $percent    Percentage
3466
	 * @return void
3467
	 */
3468
    public function update_percent($line, $percent)
3469
	{
3470
        // phpcs:enable
3471
	    global $mysoc, $user;
3472
3473
	    // Progress should never be changed for discount lines
3474
	    if (($line->info_bits & 2) == 2)
3475
	    {
3476
	    	return;
3477
	    }
3478
3479
		include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
3480
3481
		// Cap percentages to 100
3482
		if ($percent > 100) $percent = 100;
3483
		$line->situation_percent = $percent;
3484
		$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);
3485
		$line->total_ht = $tabprice[0];
3486
		$line->total_tva = $tabprice[1];
3487
		$line->total_ttc = $tabprice[2];
3488
		$line->total_localtax1 = $tabprice[9];
3489
		$line->total_localtax2 = $tabprice[10];
3490
		$line->multicurrency_total_ht  = $tabprice[16];
3491
		$line->multicurrency_total_tva = $tabprice[17];
3492
		$line->multicurrency_total_ttc = $tabprice[18];
3493
		$line->update($user);
3494
		$this->update_price(1);
3495
	}
3496
3497
	/**
3498
	 *	Delete line in database
3499
	 *
3500
	 *	@param		int		$rowid		Id of line to delete
3501
	 *	@return		int					<0 if KO, >0 if OK
3502
	 */
3503
    public function deleteline($rowid)
3504
	{
3505
        global $user;
3506
3507
		dol_syslog(get_class($this)."::deleteline rowid=".$rowid, LOG_DEBUG);
3508
3509
		if (!$this->brouillon)
3510
		{
3511
			$this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
3512
			return -1;
3513
		}
3514
3515
		$this->db->begin();
3516
3517
		// Libere remise liee a ligne de facture
3518
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
3519
		$sql .= ' SET fk_facture_line = NULL';
3520
		$sql .= ' WHERE fk_facture_line = '.$rowid;
3521
3522
		dol_syslog(get_class($this)."::deleteline", LOG_DEBUG);
3523
		$result = $this->db->query($sql);
3524
		if (!$result)
3525
		{
3526
			$this->error = $this->db->error();
3527
			$this->db->rollback();
3528
			return -1;
3529
		}
3530
3531
		$line = new FactureLigne($this->db);
3532
3533
        $line->context = $this->context;
3534
3535
		// For triggers
3536
		$result = $line->fetch($rowid);
3537
		if (!($result > 0)) dol_print_error($this->db, $line->error, $line->errors);
3538
3539
		if ($line->delete($user) > 0)
3540
		{
3541
			$result = $this->update_price(1);
3542
3543
			if ($result > 0)
3544
			{
3545
				$this->db->commit();
3546
				return 1;
3547
			}
3548
			else
3549
			{
3550
				$this->db->rollback();
3551
				$this->error = $this->db->lasterror();
3552
				return -1;
3553
			}
3554
		}
3555
		else
3556
		{
3557
			$this->db->rollback();
3558
			$this->error = $line->error;
3559
			return -1;
3560
		}
3561
	}
3562
3563
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3564
	/**
3565
	 *	Set percent discount
3566
	 *
3567
	 *	@param     	User	$user		User that set discount
3568
	 *	@param     	double	$remise		Discount
3569
	 *  @param     	int		$notrigger	1=Does not execute triggers, 0= execute triggers
3570
	 *	@return		int 		<0 if ko, >0 if ok
3571
	 */
3572
    public function set_remise($user, $remise, $notrigger = 0)
3573
	{
3574
        // phpcs:enable
3575
		// Clean parameters
3576
		if (empty($remise)) $remise = 0;
3577
3578
		if ($user->rights->facture->creer)
3579
		{
3580
			$remise = price2num($remise);
3581
3582
			$error = 0;
3583
3584
			$this->db->begin();
3585
3586
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
3587
			$sql .= ' SET remise_percent = '.$remise;
3588
			$sql .= ' WHERE rowid = '.$this->id;
3589
			$sql .= ' AND fk_statut = '.self::STATUS_DRAFT;
3590
3591
			dol_syslog(__METHOD__, LOG_DEBUG);
3592
			$resql = $this->db->query($sql);
3593
			if (!$resql)
3594
			{
3595
				$this->errors[] = $this->db->error();
3596
				$error++;
3597
			}
3598
3599
			if (!$notrigger && empty($error))
3600
			{
3601
				// Call trigger
3602
				$result = $this->call_trigger('BILL_MODIFY', $user);
3603
				if ($result < 0) $error++;
3604
				// End call triggers
3605
			}
3606
3607
			if (!$error)
3608
			{
3609
				$this->remise_percent = $remise;
3610
				$this->update_price(1);
3611
3612
				$this->db->commit();
3613
				return 1;
3614
			}
3615
			else
3616
			{
3617
				foreach ($this->errors as $errmsg)
3618
				{
3619
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3620
					$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3621
				}
3622
				$this->db->rollback();
3623
				return -1 * $error;
3624
			}
3625
		}
3626
	}
3627
3628
3629
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3630
	/**
3631
	 *	Set absolute discount
3632
	 *
3633
	 *	@param     	User	$user 		User that set discount
3634
	 *	@param     	double	$remise		Discount
3635
	 *  @param     	int		$notrigger	1=Does not execute triggers, 0= execute triggers
3636
	 *	@return		int 				<0 if KO, >0 if OK
3637
	 */
3638
    public function set_remise_absolue($user, $remise, $notrigger = 0)
3639
	{
3640
        // phpcs:enable
3641
		if (empty($remise)) $remise = 0;
3642
3643
		if ($user->rights->facture->creer)
3644
		{
3645
			$error = 0;
3646
3647
			$this->db->begin();
3648
3649
			$remise = price2num($remise);
3650
3651
			$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
3652
			$sql .= ' SET remise_absolue = '.$remise;
3653
			$sql .= ' WHERE rowid = '.$this->id;
3654
			$sql .= ' AND fk_statut = '.self::STATUS_DRAFT;
3655
3656
			dol_syslog(__METHOD__, LOG_DEBUG);
3657
			$resql = $this->db->query($sql);
3658
			if (!$resql)
3659
			{
3660
				$this->errors[] = $this->db->error();
3661
				$error++;
3662
			}
3663
3664
			if (!$error)
3665
			{
3666
				$this->oldcopy = clone $this;
3667
				$this->remise_absolue = $remise;
3668
				$this->update_price(1);
3669
			}
3670
3671
			if (!$notrigger && empty($error))
3672
			{
3673
				// Call trigger
3674
				$result = $this->call_trigger('BILL_MODIFY', $user);
3675
				if ($result < 0) $error++;
3676
				// End call triggers
3677
			}
3678
3679
			if (!$error)
3680
			{
3681
				$this->db->commit();
3682
				return 1;
3683
			}
3684
			else
3685
			{
3686
				foreach ($this->errors as $errmsg)
3687
				{
3688
					dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
3689
					$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
3690
				}
3691
				$this->db->rollback();
3692
				return -1 * $error;
3693
			}
3694
		}
3695
	}
3696
3697
	/**
3698
	 *      Return next reference of customer invoice not already used (or last reference)
3699
	 *      according to numbering module defined into constant FACTURE_ADDON
3700
	 *
3701
	 *      @param	   Societe		$soc		object company
3702
	 *      @param     string		$mode		'next' for next value or 'last' for last value
3703
	 *      @return    string					free ref or last ref
3704
	 */
3705
    public function getNextNumRef($soc, $mode = 'next')
3706
	{
3707
		global $conf, $langs;
3708
3709
		if ($this->module_source == 'takepos') {
3710
			$langs->load('cashdesk@cashdesk');
3711
3712
			$moduleName = 'takepos';
3713
			$moduleSourceName = 'Takepos';
3714
			$addonConstName = 'TAKEPOS_REF_ADDON';
3715
3716
			// Clean parameters (if not defined or using deprecated value)
3717
			if (empty($conf->global->TAKEPOS_REF_ADDON)) $conf->global->TAKEPOS_REF_ADDON = 'mod_takepos_ref_simple';
3718
3719
			$addon = $conf->global->TAKEPOS_REF_ADDON;
3720
		} else {
3721
			$langs->load('bills');
3722
3723
			$moduleName = 'facture';
3724
			$moduleSourceName = 'Invoice';
3725
			$addonConstName = 'FACTURE_ADDON';
3726
3727
			// Clean parameters (if not defined or using deprecated value)
3728
			if (empty($conf->global->FACTURE_ADDON)) $conf->global->FACTURE_ADDON = 'mod_facture_terre';
3729
			elseif ($conf->global->FACTURE_ADDON == 'terre') $conf->global->FACTURE_ADDON = 'mod_facture_terre';
3730
			elseif ($conf->global->FACTURE_ADDON == 'mercure') $conf->global->FACTURE_ADDON = 'mod_facture_mercure';
3731
3732
			$addon = $conf->global->FACTURE_ADDON;
3733
		}
3734
3735
		if (!empty($addon)) {
3736
			dol_syslog("Call getNextNumRef with " . $addonConstName . " = " . $conf->global->FACTURE_ADDON . ", thirdparty=" . $soc->nom . ", type=" . $soc->typent_code, LOG_DEBUG);
1 ignored issue
show
Deprecated Code introduced by
The property Societe::$nom has been deprecated: Use $name instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

3736
			dol_syslog("Call getNextNumRef with " . $addonConstName . " = " . $conf->global->FACTURE_ADDON . ", thirdparty=" . /** @scrutinizer ignore-deprecated */ $soc->nom . ", type=" . $soc->typent_code, LOG_DEBUG);

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
3737
3738
			$mybool = false;
3739
3740
3741
			$file = $addon . '.php';
3742
			$classname = $addon;
3743
3744
3745
			// Include file with class
3746
			$dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3747
			foreach ($dirmodels as $reldir) {
3748
				$dir = dol_buildpath($reldir . 'core/modules/' . $moduleName . '/');
3749
3750
				// Load file with numbering class (if found)
3751
				if (is_file($dir . $file) && is_readable($dir . $file)) {
3752
					$mybool |= include_once $dir . $file;
3753
				}
3754
			}
3755
3756
			// For compatibility
3757
			if (!$mybool) {
3758
				$file = $addon . '/' . $addon . '.modules.php';
3759
				$classname = 'mod_' . $moduleName . '_' . $addon;
3760
				$classname = preg_replace('/\-.*$/', '', $classname);
3761
				// Include file with class
3762
				foreach ($conf->file->dol_document_root as $dirroot) {
3763
					$dir = $dirroot . '/core/modules/' . $moduleName . '/';
3764
3765
					// Load file with numbering class (if found)
3766
					if (is_file($dir . $file) && is_readable($dir . $file)) {
3767
						$mybool |= include_once $dir . $file;
3768
					}
3769
				}
3770
			}
3771
3772
			if (!$mybool) {
3773
				dol_print_error('', 'Failed to include file ' . $file);
3774
				return '';
3775
			}
3776
3777
			$obj = new $classname();
3778
			$numref = $obj->getNextValue($soc, $this, $mode);
3779
3780
			/**
3781
			 * $numref can be empty in case we ask for the last value because if there is no invoice created with the
3782
			 * set up mask.
3783
			 */
3784
			if ($mode != 'last' && !$numref) {
3785
				$this->error = $obj->error;
3786
				return '';
3787
			}
3788
3789
			return $numref;
3790
		} else {
3791
			$langs->load('errors');
3792
			print $langs->trans('Error') . ' ' . $langs->trans('ErrorModuleSetupNotComplete', $langs->transnoentitiesnoconv($moduleSourceName));
3793
			return '';
3794
		}
3795
	}
3796
3797
	/**
3798
	 *	Load miscellaneous information for tab "Info"
3799
	 *
3800
	 *	@param  int		$id		Id of object to load
3801
	 *	@return	void
3802
	 */
3803
    public function info($id)
3804
	{
3805
		$sql = 'SELECT c.rowid, datec, date_valid as datev, tms as datem,';
3806
		$sql .= ' date_closing as dateclosing,';
3807
		$sql .= ' fk_user_author, fk_user_valid, fk_user_closing';
3808
		$sql .= ' FROM '.MAIN_DB_PREFIX.'facture as c';
3809
		$sql .= ' WHERE c.rowid = '.$id;
3810
3811
		$result = $this->db->query($sql);
3812
		if ($result)
3813
		{
3814
			if ($this->db->num_rows($result))
3815
			{
3816
				$obj = $this->db->fetch_object($result);
3817
				$this->id = $obj->rowid;
3818
				if ($obj->fk_user_author)
3819
				{
3820
					$cuser = new User($this->db);
3821
					$cuser->fetch($obj->fk_user_author);
3822
					$this->user_creation = $cuser;
3823
				}
3824
				if ($obj->fk_user_valid)
3825
				{
3826
					$vuser = new User($this->db);
3827
					$vuser->fetch($obj->fk_user_valid);
3828
					$this->user_validation = $vuser;
3829
				}
3830
				if ($obj->fk_user_closing)
3831
				{
3832
					$cluser = new User($this->db);
3833
					$cluser->fetch($obj->fk_user_closing);
3834
					$this->user_closing = $cluser;
3835
				}
3836
3837
				$this->date_creation     = $this->db->jdate($obj->datec);
3838
				$this->date_modification = $this->db->jdate($obj->datem);
3839
				$this->date_validation   = $this->db->jdate($obj->datev);
3840
				$this->date_closing      = $this->db->jdate($obj->dateclosing);
3841
			}
3842
			$this->db->free($result);
3843
		}
3844
		else
3845
		{
3846
			dol_print_error($this->db);
3847
		}
3848
	}
3849
3850
3851
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3852
	/**
3853
	 *  Return list of invoices (eventually filtered on a user) into an array
3854
	 *
3855
	 *  @param		int		$shortlist		0=Return array[id]=ref, 1=Return array[](id=>id,ref=>ref,name=>name)
3856
	 *  @param      int		$draft      	0=not draft, 1=draft
3857
	 *  @param      User	$excluser      	Objet user to exclude
3858
	 *  @param    	int		$socid			Id third pary
3859
	 *  @param    	int		$limit			For pagination
3860
	 *  @param    	int		$offset			For pagination
3861
	 *  @param    	string	$sortfield		Sort criteria
3862
	 *  @param    	string	$sortorder		Sort order
3863
	 *  @return     array|int             	-1 if KO, array with result if OK
3864
	 */
3865
    public function liste_array($shortlist = 0, $draft = 0, $excluser = '', $socid = 0, $limit = 0, $offset = 0, $sortfield = 'f.datef,f.rowid', $sortorder = 'DESC')
3866
	{
3867
        // phpcs:enable
3868
		global $conf, $user;
3869
3870
		$ga = array();
3871
3872
		$sql = "SELECT s.rowid, s.nom as name, s.client,";
3873
		$sql .= " f.rowid as fid, f.ref as ref, f.datef as df";
3874
		if (!$user->rights->societe->client->voir && !$socid) $sql .= ", sc.fk_soc, sc.fk_user";
3875
		$sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."facture as f";
3876
		if (!$user->rights->societe->client->voir && !$socid) $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
3877
		$sql .= " WHERE f.entity IN (".getEntity('invoice').")";
3878
		$sql .= " AND f.fk_soc = s.rowid";
3879
		if (!$user->rights->societe->client->voir && !$socid) //restriction
3880
		{
3881
			$sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = ".$user->id;
3882
		}
3883
		if ($socid) $sql .= " AND s.rowid = ".$socid;
3884
		if ($draft) $sql .= " AND f.fk_statut = ".self::STATUS_DRAFT;
3885
		if (is_object($excluser)) $sql .= " AND f.fk_user_author <> ".$excluser->id;
3886
		$sql .= $this->db->order($sortfield, $sortorder);
3887
		$sql .= $this->db->plimit($limit, $offset);
3888
3889
		$result = $this->db->query($sql);
3890
		if ($result)
3891
		{
3892
			$numc = $this->db->num_rows($result);
3893
			if ($numc)
3894
			{
3895
				$i = 0;
3896
				while ($i < $numc)
3897
				{
3898
					$obj = $this->db->fetch_object($result);
3899
3900
					if ($shortlist == 1)
3901
					{
3902
						$ga[$obj->fid] = $obj->ref;
3903
					}
3904
					elseif ($shortlist == 2)
3905
					{
3906
						$ga[$obj->fid] = $obj->ref.' ('.$obj->name.')';
3907
					}
3908
					else
3909
					{
3910
						$ga[$i]['id'] = $obj->fid;
3911
						$ga[$i]['ref'] 	= $obj->ref;
3912
						$ga[$i]['name'] = $obj->name;
3913
					}
3914
					$i++;
3915
				}
3916
			}
3917
			return $ga;
3918
		}
3919
		else
3920
		{
3921
			dol_print_error($this->db);
3922
			return -1;
3923
		}
3924
	}
3925
3926
3927
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3928
	/**
3929
	 *	Return list of invoices qualified to be replaced by another invoice.
3930
	 *	Invoices matching the following rules are returned:
3931
	 *	(Status validated or abandonned for a reason 'other') + not payed + no payment at all + not already replaced
3932
	 *
3933
	 *	@param		int		$socid		Id thirdparty
3934
	 *	@return    	array|int			Array of invoices ('id'=>id, 'ref'=>ref, 'status'=>status, 'paymentornot'=>0/1)
3935
	 */
3936
    public function list_replacable_invoices($socid = 0)
3937
	{
3938
        // phpcs:enable
3939
		global $conf;
3940
3941
		$return = array();
3942
3943
		$sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut,";
3944
		$sql .= " ff.rowid as rowidnext";
3945
		$sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
3946
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."paiement_facture as pf ON f.rowid = pf.fk_facture";
3947
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facture as ff ON f.rowid = ff.fk_facture_source";
3948
		$sql .= " WHERE (f.fk_statut = ".self::STATUS_VALIDATED." OR (f.fk_statut = ".self::STATUS_ABANDONED." AND f.close_code = '".self::CLOSECODE_ABANDONED."'))";
3949
		$sql .= " AND f.entity IN (".getEntity('invoice').")";
3950
		$sql .= " AND f.paye = 0"; // Pas classee payee completement
3951
		$sql .= " AND pf.fk_paiement IS NULL"; // Aucun paiement deja fait
3952
		$sql .= " AND ff.fk_statut IS NULL"; // Renvoi vrai si pas facture de remplacement
3953
		if ($socid > 0) $sql .= " AND f.fk_soc = ".$socid;
3954
		$sql .= " ORDER BY f.ref";
3955
3956
		dol_syslog(get_class($this)."::list_replacable_invoices", LOG_DEBUG);
3957
		$resql = $this->db->query($sql);
3958
		if ($resql)
3959
		{
3960
			while ($obj = $this->db->fetch_object($resql))
3961
			{
3962
				$return[$obj->rowid] = array('id' => $obj->rowid,
3963
				'ref' => $obj->ref,
3964
				'status' => $obj->fk_statut);
3965
			}
3966
			//print_r($return);
3967
			return $return;
3968
		}
3969
		else
3970
		{
3971
			$this->error = $this->db->error();
3972
			return -1;
3973
		}
3974
	}
3975
3976
3977
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3978
	/**
3979
	 *	Return list of invoices qualified to be corrected by a credit note.
3980
	 *	Invoices matching the following rules are returned:
3981
	 *	(validated + payment on process) or classified (payed completely or payed partiely) + not already replaced + not already a credit note
3982
	 *
3983
	 *	@param		int		$socid		Id thirdparty
3984
	 *	@return    	array				Array of invoices ($id => array('ref'=>,'paymentornot'=>,'status'=>,'paye'=>)
3985
	 */
3986
    public function list_qualified_avoir_invoices($socid = 0)
3987
	{
3988
        // phpcs:enable
3989
		global $conf;
3990
3991
		$return = array();
3992
3993
3994
		$sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut, f.type, f.paye, pf.fk_paiement";
3995
		$sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
3996
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."paiement_facture as pf ON f.rowid = pf.fk_facture";
3997
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facture as ff ON (f.rowid = ff.fk_facture_source AND ff.type=".self::TYPE_REPLACEMENT.")";
3998
		$sql .= " WHERE f.entity IN (".getEntity('invoice').")";
3999
		$sql .= " AND f.fk_statut in (".self::STATUS_VALIDATED.",".self::STATUS_CLOSED.")";
4000
		//  $sql.= " WHERE f.fk_statut >= 1";
4001
		//	$sql.= " AND (f.paye = 1";				// Classee payee completement
4002
		//	$sql.= " OR f.close_code IS NOT NULL)";	// Classee payee partiellement
4003
		$sql .= " AND ff.type IS NULL"; // Renvoi vrai si pas facture de remplacement
4004
		$sql .= " AND f.type != ".self::TYPE_CREDIT_NOTE; // Type non 2 si facture non avoir
4005
4006
		if (!empty($conf->global->INVOICE_USE_SITUATION_CREDIT_NOTE)) {
4007
		    // Select the last situation invoice
4008
		    $sqlSit = 'SELECT MAX(fs.rowid)';
4009
		    $sqlSit .= " FROM ".MAIN_DB_PREFIX."facture as fs";
4010
		    $sqlSit .= " WHERE fs.entity IN (".getEntity('invoice').")";
4011
		    $sqlSit .= " AND fs.type = ".self::TYPE_SITUATION;
4012
		    $sqlSit .= " AND fs.fk_statut in (".self::STATUS_VALIDATED.",".self::STATUS_CLOSED.")";
4013
		    $sqlSit .= " GROUP BY fs.situation_cycle_ref";
4014
		    $sqlSit .= " ORDER BY fs.situation_counter";
4015
            $sql .= " AND ( f.type != ".self::TYPE_SITUATION." OR f.rowid IN (".$sqlSit.") )"; // Type non 5 si facture non avoir
4016
		}
4017
		else
4018
		{
4019
		    $sql .= " AND f.type != ".self::TYPE_SITUATION; // Type non 5 si facture non avoir
4020
		}
4021
4022
		if ($socid > 0) $sql .= " AND f.fk_soc = ".$socid;
4023
		$sql .= " ORDER BY f.ref";
4024
4025
		dol_syslog(get_class($this)."::list_qualified_avoir_invoices", LOG_DEBUG);
4026
		$resql = $this->db->query($sql);
4027
		if ($resql)
4028
		{
4029
			while ($obj = $this->db->fetch_object($resql))
4030
			{
4031
				$qualified = 0;
4032
				if ($obj->fk_statut == self::STATUS_VALIDATED) $qualified = 1;
4033
				if ($obj->fk_statut == self::STATUS_CLOSED) $qualified = 1;
4034
				if ($qualified)
4035
				{
4036
					//$ref=$obj->ref;
4037
					$paymentornot = ($obj->fk_paiement ? 1 : 0);
4038
					$return[$obj->rowid] = array('ref'=>$obj->ref, 'status'=>$obj->fk_statut, 'type'=>$obj->type, 'paye'=>$obj->paye, 'paymentornot'=>$paymentornot);
4039
				}
4040
			}
4041
4042
			return $return;
4043
		}
4044
		else
4045
		{
4046
			$this->error = $this->db->error();
4047
			return -1;
4048
		}
4049
	}
4050
4051
4052
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4053
	/**
4054
	 *	Create a withdrawal request for a standing order.
4055
	 *  Use the remain to pay excluding all existing open direct debit requests.
4056
	 *
4057
	 *	@param      User	$fuser      User asking the direct debit transfer
4058
	 *  @param		float	$amount		Amount we request direct debit for
4059
	 *	@return     int         		<0 if KO, >0 if OK
4060
	 */
4061
    public function demande_prelevement($fuser, $amount = 0)
4062
	{
4063
        // phpcs:enable
4064
4065
		$error = 0;
4066
4067
		dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
4068
4069
		if ($this->statut > self::STATUS_DRAFT && $this->paye == 0)
4070
		{
4071
	        require_once DOL_DOCUMENT_ROOT.'/societe/class/companybankaccount.class.php';
4072
	        $bac = new CompanyBankAccount($this->db);
4073
	        $bac->fetch(0, $this->socid);
4074
4075
        	$sql = 'SELECT count(*)';
4076
			$sql .= ' FROM '.MAIN_DB_PREFIX.'prelevement_facture_demande';
4077
			$sql .= ' WHERE fk_facture = '.$this->id;
4078
			$sql .= ' AND traite = 0';
4079
4080
			dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
4081
			$resql = $this->db->query($sql);
4082
			if ($resql)
4083
			{
4084
				$row = $this->db->fetch_row($resql);
4085
				if ($row[0] == 0)
4086
				{
4087
					$now = dol_now();
4088
4089
                    $totalpaye = $this->getSommePaiement();
4090
                    $totalcreditnotes = $this->getSumCreditNotesUsed();
4091
                    $totaldeposits = $this->getSumDepositsUsed();
4092
                    //print "totalpaye=".$totalpaye." totalcreditnotes=".$totalcreditnotes." totaldeposts=".$totaldeposits;
4093
4094
                    // We can also use bcadd to avoid pb with floating points
4095
                    // For example print 239.2 - 229.3 - 9.9; does not return 0.
4096
                    //$resteapayer=bcadd($this->total_ttc,$totalpaye,$conf->global->MAIN_MAX_DECIMALS_TOT);
4097
                    //$resteapayer=bcadd($resteapayer,$totalavoir,$conf->global->MAIN_MAX_DECIMALS_TOT);
4098
					if (empty($amount)) $amount = price2num($this->total_ttc - $totalpaye - $totalcreditnotes - $totaldeposits, 'MT');
4099
4100
					if (is_numeric($amount) && $amount != 0)
4101
					{
4102
						$sql = 'INSERT INTO '.MAIN_DB_PREFIX.'prelevement_facture_demande';
4103
						$sql .= ' (fk_facture, amount, date_demande, fk_user_demande, code_banque, code_guichet, number, cle_rib)';
4104
						$sql .= ' VALUES ('.$this->id;
4105
						$sql .= ",'".price2num($amount)."'";
4106
						$sql .= ",'".$this->db->idate($now)."'";
4107
						$sql .= ",".$fuser->id;
4108
						$sql .= ",'".$bac->code_banque."'";
4109
						$sql .= ",'".$bac->code_guichet."'";
4110
						$sql .= ",'".$bac->number."'";
4111
						$sql .= ",'".$bac->cle_rib."')";
4112
4113
						dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
4114
						$resql = $this->db->query($sql);
4115
						if (!$resql)
4116
						{
4117
						    $this->error = $this->db->lasterror();
4118
						    dol_syslog(get_class($this).'::demandeprelevement Erreur');
4119
						    $error++;
4120
						}
4121
					}
4122
					else
4123
					{
4124
						$this->error = 'WithdrawRequestErrorNilAmount';
4125
	                    dol_syslog(get_class($this).'::demandeprelevement WithdrawRequestErrorNilAmount');
4126
	                    $error++;
4127
					}
4128
4129
        			if (!$error)
4130
        			{
4131
        				// Force payment mode of invoice to withdraw
4132
        				$payment_mode_id = dol_getIdFromCode($this->db, 'PRE', 'c_paiement', 'code', 'id', 1);
4133
        				if ($payment_mode_id > 0)
4134
        				{
4135
        					$result = $this->setPaymentMethods($payment_mode_id);
4136
        				}
4137
        			}
4138
4139
                    if ($error) return -1;
4140
                    return 1;
4141
                }
4142
                else
4143
                {
4144
                    $this->error = "A request already exists";
4145
                    dol_syslog(get_class($this).'::demandeprelevement Impossible de creer une demande, demande deja en cours');
4146
                    return 0;
4147
                }
4148
            }
4149
            else
4150
            {
4151
                $this->error = $this->db->error();
4152
                dol_syslog(get_class($this).'::demandeprelevement Erreur -2');
4153
                return -2;
4154
            }
4155
        }
4156
        else
4157
        {
4158
            $this->error = "Status of invoice does not allow this";
4159
            dol_syslog(get_class($this)."::demandeprelevement ".$this->error." $this->statut, $this->paye, $this->mode_reglement_id");
4160
            return -3;
4161
        }
4162
    }
4163
4164
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4165
	/**
4166
	 *  Supprime une demande de prelevement
4167
	 *
4168
	 *  @param  User	$fuser      User making delete
4169
	 *  @param  int		$did        id de la demande a supprimer
4170
	 *  @return	int					<0 if OK, >0 if KO
4171
	 */
4172
    public function demande_prelevement_delete($fuser, $did)
4173
	{
4174
        // phpcs:enable
4175
		$sql = 'DELETE FROM '.MAIN_DB_PREFIX.'prelevement_facture_demande';
4176
		$sql .= ' WHERE rowid = '.$did;
4177
		$sql .= ' AND traite = 0';
4178
		if ($this->db->query($sql))
4179
		{
4180
			return 0;
4181
		}
4182
		else
4183
		{
4184
			$this->error = $this->db->lasterror();
4185
			dol_syslog(get_class($this).'::demande_prelevement_delete Error '.$this->error);
4186
			return -1;
4187
		}
4188
	}
4189
4190
4191
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4192
	/**
4193
	 *	Load indicators for dashboard (this->nbtodo and this->nbtodolate)
4194
	 *
4195
	 *	@param  User		$user    	Object user
4196
	 *	@return WorkboardResponse|int 	<0 if KO, WorkboardResponse if OK
4197
	 */
4198
    public function load_board($user)
4199
	{
4200
        // phpcs:enable
4201
		global $conf, $langs;
4202
4203
		$clause = " WHERE";
4204
4205
		$sql = "SELECT f.rowid, f.date_lim_reglement as datefin,f.fk_statut, f.total";
4206
		$sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
4207
		if (!$user->rights->societe->client->voir && !$user->socid)
4208
		{
4209
			$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON f.fk_soc = sc.fk_soc";
4210
			$sql .= " WHERE sc.fk_user = ".$user->id;
4211
			$clause = " AND";
4212
		}
4213
		$sql .= $clause." f.paye=0";
4214
		$sql .= " AND f.entity IN (".getEntity('invoice').")";
4215
		$sql .= " AND f.fk_statut = ".self::STATUS_VALIDATED;
4216
		if ($user->socid) $sql .= " AND f.fk_soc = ".$user->socid;
4217
4218
		$resql = $this->db->query($sql);
4219
		if ($resql)
4220
		{
4221
			$langs->load("bills");
4222
			$now = dol_now();
4223
4224
			$response = new WorkboardResponse();
4225
			$response->warning_delay = $conf->facture->client->warning_delay / 60 / 60 / 24;
4226
			$response->label = $langs->trans("CustomerBillsUnpaid");
4227
			$response->labelShort = $langs->trans("Unpaid");
4228
			$response->url = DOL_URL_ROOT.'/compta/facture/list.php?search_status=1&mainmenu=billing&leftmenu=customers_bills';
4229
			$response->img = img_object('', "bill");
4230
4231
			$generic_facture = new Facture($this->db);
4232
4233
			while ($obj = $this->db->fetch_object($resql))
4234
			{
4235
				$generic_facture->date_lim_reglement = $this->db->jdate($obj->datefin);
4236
				$generic_facture->statut = $obj->fk_statut;
4237
4238
				$response->nbtodo++;
4239
				$response->total += $obj->total;
4240
4241
				if ($generic_facture->hasDelay()) {
4242
					$response->nbtodolate++;
4243
					$response->url_late = DOL_URL_ROOT.'/compta/facture/list.php?search_option=late&mainmenu=billing&leftmenu=customers_bills';
4244
				}
4245
			}
4246
4247
			return $response;
4248
		}
4249
		else
4250
		{
4251
			dol_print_error($this->db);
4252
			$this->error = $this->db->error();
4253
			return -1;
4254
		}
4255
	}
4256
4257
4258
	/* gestion des contacts d'une facture */
4259
4260
	/**
4261
	 *	Retourne id des contacts clients de facturation
4262
	 *
4263
	 *	@return     array       Liste des id contacts facturation
4264
	 */
4265
    public function getIdBillingContact()
4266
	{
4267
		return $this->getIdContact('external', 'BILLING');
4268
	}
4269
4270
	/**
4271
	 *	Retourne id des contacts clients de livraison
4272
	 *
4273
	 *	@return     array       Liste des id contacts livraison
4274
	 */
4275
    public function getIdShippingContact()
4276
	{
4277
		return $this->getIdContact('external', 'SHIPPING');
4278
	}
4279
4280
4281
	/**
4282
	 *  Initialise an instance with random values.
4283
	 *  Used to build previews or test instances.
4284
	 *	id must be 0 if object instance is a specimen.
4285
	 *
4286
	 *	@param	string		$option		''=Create a specimen invoice with lines, 'nolines'=No lines
4287
	 *  @return	void
4288
	 */
4289
    public function initAsSpecimen($option = '')
4290
	{
4291
		global $conf, $langs;
4292
4293
		$now = dol_now();
4294
		$arraynow = dol_getdate($now);
4295
		$nownotime = dol_mktime(0, 0, 0, $arraynow['mon'], $arraynow['mday'], $arraynow['year']);
4296
4297
        // Load array of products prodids
4298
		$num_prods = 0;
4299
		$prodids = array();
4300
		$sql = "SELECT rowid";
4301
		$sql .= " FROM ".MAIN_DB_PREFIX."product";
4302
		$sql .= " WHERE entity IN (".getEntity('product').")";
4303
		$resql = $this->db->query($sql);
4304
		if ($resql)
4305
		{
4306
			$num_prods = $this->db->num_rows($resql);
4307
			$i = 0;
4308
			while ($i < $num_prods)
4309
			{
4310
				$i++;
4311
				$row = $this->db->fetch_row($resql);
4312
				$prodids[$i] = $row[0];
4313
			}
4314
		}
4315
		//Avoid php warning Warning: mt_rand(): max(0) is smaller than min(1) when no product exists
4316
		if (empty($num_prods)) {
4317
			$num_prods = 1;
4318
		}
4319
4320
		// Initialize parameters
4321
		$this->id = 0;
4322
		$this->entity = 1;
4323
		$this->ref = 'SPECIMEN';
4324
		$this->specimen = 1;
4325
		$this->socid = 1;
4326
		$this->date = $nownotime;
4327
		$this->date_lim_reglement = $nownotime + 3600 * 24 * 30;
4328
		$this->cond_reglement_id   = 1;
4329
		$this->cond_reglement_code = 'RECEP';
4330
		$this->date_lim_reglement = $this->calculate_date_lim_reglement();
4331
		$this->mode_reglement_id   = 0; // Not forced to show payment mode CHQ + VIR
4332
		$this->mode_reglement_code = ''; // Not forced to show payment mode CHQ + VIR
4333
4334
		$this->note_public = 'This is a comment (public)';
4335
		$this->note_private = 'This is a comment (private)';
4336
		$this->note = 'This is a comment (private)';
4337
4338
		$this->multicurrency_tx = 1;
4339
		$this->multicurrency_code = $conf->currency;
4340
4341
		$this->fk_incoterms = 0;
4342
		$this->location_incoterms = '';
4343
4344
		if (empty($option) || $option != 'nolines')
4345
		{
4346
			// Lines
4347
			$nbp = 5;
4348
			$xnbp = 0;
4349
			while ($xnbp < $nbp)
4350
			{
4351
				$line = new FactureLigne($this->db);
4352
				$line->desc = $langs->trans("Description")." ".$xnbp;
4353
				$line->qty = 1;
4354
				$line->subprice = 100;
4355
				$line->tva_tx = 19.6;
4356
				$line->localtax1_tx = 0;
4357
				$line->localtax2_tx = 0;
4358
				$line->remise_percent = 0;
4359
				if ($xnbp == 1)        // Qty is negative (product line)
4360
				{
4361
					$prodid = mt_rand(1, $num_prods);
4362
					$line->fk_product = $prodids[$prodid];
4363
					$line->qty = -1;
4364
					$line->total_ht = -100;
4365
					$line->total_ttc = -119.6;
4366
					$line->total_tva = -19.6;
4367
					$line->multicurrency_total_ht = -200;
4368
					$line->multicurrency_total_ttc = -239.2;
4369
					$line->multicurrency_total_tva = -39.2;
4370
				}
4371
				elseif ($xnbp == 2)    // UP is negative (free line)
4372
				{
4373
					$line->subprice = -100;
4374
					$line->total_ht = -100;
4375
					$line->total_ttc = -119.6;
4376
					$line->total_tva = -19.6;
4377
					$line->remise_percent = 0;
4378
					$line->multicurrency_total_ht = -200;
4379
					$line->multicurrency_total_ttc = -239.2;
4380
					$line->multicurrency_total_tva = -39.2;
4381
				}
4382
				elseif ($xnbp == 3)    // Discount is 50% (product line)
4383
				{
4384
					$prodid = mt_rand(1, $num_prods);
4385
					$line->fk_product = $prodids[$prodid];
4386
					$line->total_ht = 50;
4387
					$line->total_ttc = 59.8;
4388
					$line->total_tva = 9.8;
4389
					$line->multicurrency_total_ht = 100;
4390
					$line->multicurrency_total_ttc = 119.6;
4391
					$line->multicurrency_total_tva = 19.6;
4392
					$line->remise_percent = 50;
4393
				}
4394
				else    // (product line)
4395
				{
4396
					$prodid = mt_rand(1, $num_prods);
4397
					$line->fk_product = $prodids[$prodid];
4398
					$line->total_ht = 100;
4399
					$line->total_ttc = 119.6;
4400
					$line->total_tva = 19.6;
4401
					$line->multicurrency_total_ht = 200;
4402
					$line->multicurrency_total_ttc = 239.2;
4403
					$line->multicurrency_total_tva = 39.2;
4404
					$line->remise_percent = 0;
4405
				}
4406
4407
				$this->lines[$xnbp] = $line;
4408
4409
4410
				$this->total_ht       += $line->total_ht;
4411
				$this->total_tva      += $line->total_tva;
4412
				$this->total_ttc      += $line->total_ttc;
4413
4414
				$this->multicurrency_total_ht       += $line->multicurrency_total_ht;
4415
				$this->multicurrency_total_tva      += $line->multicurrency_total_tva;
4416
				$this->multicurrency_total_ttc      += $line->multicurrency_total_ttc;
4417
4418
				$xnbp++;
4419
			}
4420
			$this->revenuestamp = 0;
4421
4422
			// Add a line "offered"
4423
			$line = new FactureLigne($this->db);
4424
			$line->desc = $langs->trans("Description")." (offered line)";
4425
			$line->qty = 1;
4426
			$line->subprice = 100;
4427
			$line->tva_tx = 19.6;
4428
			$line->localtax1_tx = 0;
4429
			$line->localtax2_tx = 0;
4430
			$line->remise_percent = 100;
4431
			$line->total_ht = 0;
4432
			$line->total_ttc = 0; // 90 * 1.196
4433
			$line->total_tva = 0;
4434
			$line->multicurrency_total_ht = 0;
4435
			$line->multicurrency_total_ttc = 0;
4436
			$line->multicurrency_total_tva = 0;
4437
			$prodid = mt_rand(1, $num_prods);
4438
			$line->fk_product = $prodids[$prodid];
4439
4440
			$this->lines[$xnbp] = $line;
4441
			$xnbp++;
4442
		}
4443
	}
4444
4445
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4446
	/**
4447
	 *      Load indicators for dashboard (this->nbtodo and this->nbtodolate)
4448
	 *
4449
	 *      @return         int     <0 if KO, >0 if OK
4450
	 */
4451
    public function load_state_board()
4452
	{
4453
        // phpcs:enable
4454
		global $conf, $user;
4455
4456
		$this->nb = array();
4457
4458
		$clause = "WHERE";
4459
4460
		$sql = "SELECT count(f.rowid) as nb";
4461
		$sql .= " FROM ".MAIN_DB_PREFIX."facture as f";
4462
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON f.fk_soc = s.rowid";
4463
		if (!$user->rights->societe->client->voir && !$user->socid)
4464
		{
4465
			$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
4466
			$sql .= " WHERE sc.fk_user = ".$user->id;
4467
			$clause = "AND";
4468
		}
4469
		$sql .= " ".$clause." f.entity IN (".getEntity('invoice').")";
4470
4471
		$resql = $this->db->query($sql);
4472
		if ($resql)
4473
		{
4474
			while ($obj = $this->db->fetch_object($resql))
4475
			{
4476
				$this->nb["invoices"] = $obj->nb;
4477
			}
4478
            $this->db->free($resql);
4479
			return 1;
4480
		}
4481
		else
4482
		{
4483
			dol_print_error($this->db);
4484
			$this->error = $this->db->error();
4485
			return -1;
4486
		}
4487
	}
4488
4489
	/**
4490
	 * 	Create an array of invoice lines
4491
	 *
4492
	 * 	@return int		>0 if OK, <0 if KO
4493
	 */
4494
    public function getLinesArray()
4495
	{
4496
	    return $this->fetch_lines();
4497
	}
4498
4499
	/**
4500
	 *  Create a document onto disk according to template module.
4501
	 *
4502
	 *	@param	string		$modele			Generator to use. Caller must set it to obj->modelpdf or GETPOST('modelpdf','alpha') for example.
4503
	 *	@param	Translate	$outputlangs	objet lang a utiliser pour traduction
4504
	 *  @param  int			$hidedetails    Hide details of lines
4505
	 *  @param  int			$hidedesc       Hide description
4506
	 *  @param  int			$hideref        Hide ref
4507
	 *  @param   null|array  $moreparams     Array to provide more information
4508
	 *	@return int        					<0 if KO, >0 if OK
4509
	 */
4510
	public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
4511
	{
4512
		global $conf, $langs;
4513
4514
		$langs->load("bills");
4515
4516
		if (!dol_strlen($modele))
4517
		{
4518
			$modele = 'crabe';
4519
			$thisTypeConfName = 'FACTURE_ADDON_PDF_'.$this->type;
4520
4521
			if ($this->modelpdf) {
4522
				$modele = $this->modelpdf;
4523
			} elseif (!empty($conf->global->$thisTypeConfName)) {
4524
				$modele = $conf->global->$thisTypeConfName;
4525
			} elseif (!empty($conf->global->FACTURE_ADDON_PDF)) {
4526
				$modele = $conf->global->FACTURE_ADDON_PDF;
4527
			}
4528
		}
4529
4530
		$modelpath = "core/modules/facture/doc/";
4531
4532
		return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
4533
	}
4534
4535
	/**
4536
	 * Gets the smallest reference available for a new cycle
4537
	 *
4538
	 * @return int >= 1 if OK, -1 if error
4539
	 */
4540
    public function newCycle()
4541
	{
4542
		$sql = 'SELECT max(situation_cycle_ref) FROM '.MAIN_DB_PREFIX.'facture as f';
4543
		$sql .= " WHERE f.entity IN (".getEntity('invoice', 0).")";
4544
		$resql = $this->db->query($sql);
4545
		if ($resql) {
4546
			if ($resql->num_rows > 0)
4547
			{
4548
				$res = $this->db->fetch_array($resql);
4549
				$ref = $res['max(situation_cycle_ref)'];
4550
				$ref++;
4551
			} else {
4552
				$ref = 1;
4553
			}
4554
			$this->db->free($resql);
4555
			return $ref;
4556
		} else {
4557
			$this->error = $this->db->lasterror();
4558
			dol_syslog("Error sql=".$sql.", error=".$this->error, LOG_ERR);
4559
			return -1;
4560
		}
4561
	}
4562
4563
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4564
	/**
4565
	 * Checks if the invoice is the first of a cycle
4566
	 *
4567
	 * @return boolean
4568
	 */
4569
    public function is_first()
4570
	{
4571
        // phpcs:enable
4572
		return ($this->situation_counter == 1);
4573
	}
4574
4575
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4576
	/**
4577
	 * Returns an array containing the previous situations as Facture objects
4578
	 *
4579
	 * @return mixed -1 if error, array of previous situations
4580
	 */
4581
    public function get_prev_sits()
4582
	{
4583
        // phpcs:enable
4584
		global $conf;
4585
4586
		$sql = 'SELECT rowid FROM '.MAIN_DB_PREFIX.'facture';
4587
		$sql .= ' WHERE situation_cycle_ref = '.$this->situation_cycle_ref;
4588
		$sql .= ' AND situation_counter < '.$this->situation_counter;
4589
		$sql .= ' AND entity = '.($this->entity > 0 ? $this->entity : $conf->entity);
4590
		$resql = $this->db->query($sql);
4591
		$res = array();
4592
		if ($resql && $resql->num_rows > 0) {
4593
			while ($row = $this->db->fetch_object($resql)) {
4594
				$id = $row->rowid;
4595
				$situation = new Facture($this->db);
4596
				$situation->fetch($id);
4597
				$res[] = $situation;
4598
			}
4599
		} else {
4600
			$this->error = $this->db->error();
4601
			dol_syslog("Error sql=".$sql.", error=".$this->error, LOG_ERR);
4602
			return -1;
4603
		}
4604
4605
		return $res;
4606
	}
4607
4608
	/**
4609
	 * Sets the invoice as a final situation
4610
	 *
4611
	 *  @param  	User	$user    	Object user
4612
	 *  @param     	int		$notrigger	1=Does not execute triggers, 0= execute triggers
4613
	 *	@return		int 				<0 if KO, >0 if OK
4614
	 */
4615
    public function setFinal(User $user, $notrigger = 0)
4616
	{
4617
		$error = 0;
4618
4619
		$this->db->begin();
4620
4621
		$sql = 'UPDATE '.MAIN_DB_PREFIX.'facture SET situation_final = '.$this->situation_final.' where rowid = '.$this->id;
4622
4623
		dol_syslog(__METHOD__, LOG_DEBUG);
4624
		$resql = $this->db->query($sql);
4625
		if (!$resql)
4626
		{
4627
			$this->errors[] = $this->db->error();
4628
			$error++;
4629
		}
4630
4631
		if (!$notrigger && empty($error))
4632
		{
4633
			// Call trigger
4634
			$result = $this->call_trigger('BILL_MODIFY', $user);
4635
			if ($result < 0) $error++;
4636
			// End call triggers
4637
		}
4638
4639
		if (!$error)
4640
		{
4641
			$this->db->commit();
4642
			return 1;
4643
		}
4644
		else
4645
		{
4646
			foreach ($this->errors as $errmsg)
4647
			{
4648
				dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
4649
				$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
4650
			}
4651
			$this->db->rollback();
4652
			return -1 * $error;
4653
		}
4654
	}
4655
4656
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4657
	/**
4658
	 * Checks if the invoice is the last in its cycle
4659
	 *
4660
	 * @return bool Last of the cycle status
4661
	 */
4662
    public function is_last_in_cycle()
4663
	{
4664
        // phpcs:enable
4665
		global $conf;
4666
4667
		if (!empty($this->situation_cycle_ref)) {
4668
			// No point in testing anything if we're not inside a cycle
4669
			$sql = 'SELECT max(situation_counter) FROM '.MAIN_DB_PREFIX.'facture';
4670
			$sql .= ' WHERE situation_cycle_ref = '.$this->situation_cycle_ref;
4671
			$sql .= ' AND entity = '.($this->entity > 0 ? $this->entity : $conf->entity);
4672
			$resql = $this->db->query($sql);
4673
4674
			if ($resql && $resql->num_rows > 0) {
4675
				$res = $this->db->fetch_array($resql);
4676
				$last = $res['max(situation_counter)'];
4677
				return ($last == $this->situation_counter);
4678
			} else {
4679
				$this->error = $this->db->lasterror();
4680
				dol_syslog(get_class($this)."::select Error ".$this->error, LOG_ERR);
4681
				return false;
4682
			}
4683
		} else {
4684
			return true;
4685
		}
4686
	}
4687
4688
	/**
4689
	 * Function used to replace a thirdparty id with another one.
4690
	 *
4691
	 * @param  DoliDB  $db             Database handler
4692
	 * @param  int     $origin_id      Old third-party id
4693
	 * @param  int     $dest_id        New third-party id
4694
	 * @return bool
4695
	 */
4696
	public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
4697
	{
4698
		$tables = array(
4699
			'facture'
4700
		);
4701
4702
		return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
4703
	}
4704
4705
	/**
4706
	 * Is the customer invoice delayed?
4707
	 *
4708
	 * @return bool
4709
	 */
4710
	public function hasDelay()
4711
	{
4712
		global $conf;
4713
4714
		$now = dol_now();
4715
4716
		// Paid invoices have status STATUS_CLOSED
4717
		if ($this->statut != Facture::STATUS_VALIDATED) return false;
4718
4719
		$hasDelay = $this->date_lim_reglement < ($now - $conf->facture->client->warning_delay);
4720
		if ($hasDelay && !empty($this->retained_warranty) && !empty($this->retained_warranty_date_limit))
4721
		{
4722
		    $totalpaye = $this->getSommePaiement();
4723
		    $totalpaye = floatval($totalpaye);
4724
		    $RetainedWarrantyAmount = $this->getRetainedWarrantyAmount();
4725
		    if ($totalpaye >= 0 && $RetainedWarrantyAmount >= 0)
4726
		    {
4727
		        if (($totalpaye < $this->total_ttc - $RetainedWarrantyAmount) && $this->date_lim_reglement < ($now - $conf->facture->client->warning_delay))
4728
		        {
4729
		            $hasDelay = 1;
4730
		        }
4731
		        elseif ($totalpaye < $this->total_ttc && $this->retained_warranty_date_limit < ($now - $conf->facture->client->warning_delay))
4732
		        {
4733
		            $hasDelay = 1;
4734
		        }
4735
		        else
4736
		        {
4737
		            $hasDelay = 0;
4738
		        }
4739
		    }
4740
		}
4741
4742
		return $hasDelay;
4743
	}
4744
4745
	/**
4746
	 * Currently used for documents generation : to know if retained warranty need to be displayed
4747
	 * @return bool
4748
	 */
4749
	public function displayRetainedWarranty()
4750
	{
4751
		global $conf;
4752
4753
		// TODO : add a flag on invoices to store this conf : INVOICE_RETAINED_WARRANTY_LIMITED_TO_FINAL_SITUATION
4754
4755
		// 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
4756
4757
		$displayWarranty = false;
4758
		if(!empty($this->retained_warranty)) {
4759
			$displayWarranty = true;
4760
4761
			if ($this->type == Facture::TYPE_SITUATION && !empty($conf->global->INVOICE_RETAINED_WARRANTY_LIMITED_TO_FINAL_SITUATION)) {
4762
				// Check if this situation invoice is 100% for real
4763
				$displayWarranty = false;
4764
				if (!empty($this->situation_final)) {
4765
					$displayWarranty = true;
4766
				} elseif (!empty($this->lines) && $this->status == Facture::STATUS_DRAFT) {
4767
					// $object->situation_final need validation to be done so this test is need for draft
4768
					$displayWarranty = true;
4769
4770
					foreach ($this->lines as $i => $line) {
4771
						if ($line->product_type < 2 && $line->situation_percent < 100) {
4772
							$displayWarranty = false;
4773
							break;
4774
						}
4775
					}
4776
				}
4777
			}
4778
		}
4779
4780
		return $displayWarranty;
4781
	}
4782
4783
	/**
4784
	 * @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)
4785
	 * @return number or -1 if not available
4786
	 */
4787
	public function getRetainedWarrantyAmount($rounding = -1)
4788
	{
4789
		global $conf;
4790
	    if (empty($this->retained_warranty)) {
4791
	        return -1;
4792
	    }
4793
4794
	    $retainedWarrantyAmount = 0;
4795
4796
	    // Billed - retained warranty
4797
	    if($this->type == Facture::TYPE_SITUATION && !empty($conf->global->INVOICE_RETAINED_WARRANTY_LIMITED_TO_FINAL_SITUATION))
4798
	    {
4799
	        $displayWarranty = true;
4800
	        // Check if this situation invoice is 100% for real
4801
	        if (!empty($this->lines)) {
4802
	            foreach ($this->lines as $i => $line) {
4803
	                if ($line->product_type < 2 && $line->situation_percent < 100) {
4804
	                    $displayWarranty = false;
4805
	                    break;
4806
	            	}
4807
	            }
4808
	        }
4809
4810
	        if ($displayWarranty && !empty($this->situation_final))
4811
	        {
4812
	            $this->fetchPreviousNextSituationInvoice();
4813
	            $TPreviousIncoice = $this->tab_previous_situation_invoice;
4814
4815
	            $total2BillWT = 0;
4816
	            foreach ($TPreviousIncoice as &$fac) {
4817
	                $total2BillWT += $fac->total_ttc;
4818
	            }
4819
	            $total2BillWT += $this->total_ttc;
4820
4821
	            $retainedWarrantyAmount = $total2BillWT * $this->retained_warranty / 100;
4822
	        }
4823
	        else {
4824
	            return -1;
4825
	        }
4826
	    }
4827
	    else
4828
	    {
4829
	        // Because one day retained warranty could be used on standard invoices
4830
	        $retainedWarrantyAmount = $this->total_ttc * $this->retained_warranty / 100;
4831
	    }
4832
4833
		if ($rounding < 0) {
4834
			$rounding = min($conf->global->MAIN_MAX_DECIMALS_UNIT, $conf->global->MAIN_MAX_DECIMALS_TOT);
4835
		}
4836
4837
		if($rounding>0){
4838
			return round($retainedWarrantyAmount, $rounding);
4839
		}
4840
4841
	    return $retainedWarrantyAmount;
4842
	}
4843
4844
	/**
4845
	 *  Change the retained warranty
4846
	 *
4847
	 *  @param		float		$value		value of retained warranty
4848
	 *  @return		int				>0 if OK, <0 if KO
4849
	 */
4850
	public function setRetainedWarranty($value)
4851
	{
4852
	    dol_syslog(get_class($this).'::setRetainedWarranty('.$value.')');
4853
	    if ($this->statut >= 0)
4854
	    {
4855
	        $fieldname = 'retained_warranty';
4856
	        $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
4857
	        $sql .= ' SET '.$fieldname.' = '.floatval($value);
4858
	        $sql .= ' WHERE rowid='.$this->id;
4859
4860
	        if ($this->db->query($sql))
4861
	        {
4862
	            $this->retained_warranty = floatval($value);
4863
	            return 1;
4864
	        }
4865
	        else
4866
	        {
4867
	            dol_syslog(get_class($this).'::setRetainedWarranty Erreur '.$sql.' - '.$this->db->error());
4868
	            $this->error = $this->db->error();
4869
	            return -1;
4870
	        }
4871
	    }
4872
	    else
4873
	    {
4874
	        dol_syslog(get_class($this).'::setRetainedWarranty, status of the object is incompatible');
4875
	        $this->error = 'Status of the object is incompatible '.$this->statut;
4876
	        return -2;
4877
	    }
4878
	}
4879
4880
4881
	/**
4882
	 *  Change the retained_warranty_date_limit
4883
	 *
4884
	 *  @param		int		$timestamp		date limit of retained warranty in timestamp format
4885
	 *  @param		string	$dateYmd		date limit of retained warranty in Y m d format
4886
	 *  @return		int				>0 if OK, <0 if KO
4887
	 */
4888
	public function setRetainedWarrantyDateLimit($timestamp, $dateYmd = false)
4889
	{
4890
	    if (!$timestamp && $dateYmd) {
4891
	        $timestamp = $this->db->jdate($dateYmd);
4892
	    }
4893
4894
4895
	    dol_syslog(get_class($this).'::setRetainedWarrantyDateLimit('.$timestamp.')');
4896
	    if ($this->statut >= 0)
4897
	    {
4898
	        $fieldname = 'retained_warranty_date_limit';
4899
	        $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
4900
	        $sql .= ' SET '.$fieldname.' = '.(strval($timestamp) != '' ? '\''.$this->db->idate($timestamp).'\'' : 'null');
4901
	        $sql .= ' WHERE rowid='.$this->id;
4902
4903
	        if ($this->db->query($sql))
4904
	        {
4905
	            $this->retained_warranty_date_limit = $timestamp;
4906
	            return 1;
4907
	        }
4908
	        else
4909
	        {
4910
	            dol_syslog(get_class($this).'::setRetainedWarrantyDateLimit Erreur '.$sql.' - '.$this->db->error());
4911
	            $this->error = $this->db->error();
4912
	            return -1;
4913
	        }
4914
	    }
4915
	    else
4916
	    {
4917
	        dol_syslog(get_class($this).'::setRetainedWarrantyDateLimit, status of the object is incompatible');
4918
	        $this->error = 'Status of the object is incompatible '.$this->statut;
4919
	        return -2;
4920
	    }
4921
	}
4922
}
4923
4924
/**
4925
 *	Class to manage invoice lines.
4926
 *  Saved into database table llx_facturedet
4927
 */
4928
class FactureLigne extends CommonInvoiceLine
4929
{
4930
    /**
4931
	 * @var string ID to identify managed object
4932
	 */
4933
	public $element = 'facturedet';
4934
4935
    /**
4936
	 * @var string Name of table without prefix where object is stored
4937
	 */
4938
	public $table_element = 'facturedet';
4939
4940
	public $oldline;
4941
4942
	//! From llx_facturedet
4943
	//! Id facture
4944
	public $fk_facture;
4945
	//! Id parent line
4946
	public $fk_parent_line;
4947
	/**
4948
	 * @deprecated
4949
	 */
4950
	public $label;
4951
	//! Description ligne
4952
	public $desc;
4953
4954
	public $localtax1_type; // Local tax 1 type
4955
	public $localtax2_type; // Local tax 2 type
4956
	public $fk_remise_except; // Link to line into llx_remise_except
4957
	public $rang = 0;
4958
4959
	public $fk_fournprice;
4960
	public $pa_ht;
4961
	public $marge_tx;
4962
	public $marque_tx;
4963
4964
	public $remise_percent;
4965
4966
	public $special_code; // Liste d'options non cumulabels:
4967
	// 1: frais de port
4968
	// 2: ecotaxe
4969
	// 3: ??
4970
4971
	public $origin;
4972
	public $origin_id;
4973
4974
	public $fk_code_ventilation = 0;
4975
4976
	public $date_start;
4977
	public $date_end;
4978
4979
	// From llx_product
4980
	/**
4981
	 * @deprecated
4982
	 * @see $product_ref
4983
	 */
4984
	public $ref; // Product ref (deprecated)
4985
	public $product_ref; // Product ref
4986
	/**
4987
	 * @deprecated
4988
	 * @see $product_label
4989
	 */
4990
	public $libelle; // Product label (deprecated)
4991
	public $product_label; // Product label
4992
	public $product_desc; // Description produit
4993
4994
	public $skip_update_total; // Skip update price total for special lines
4995
4996
	/**
4997
	 * @var int Situation advance percentage
4998
	 */
4999
	public $situation_percent;
5000
5001
	/**
5002
	 * @var int Previous situation line id reference
5003
	 */
5004
	public $fk_prev_id;
5005
5006
	// Multicurrency
5007
	public $fk_multicurrency;
5008
	public $multicurrency_code;
5009
	public $multicurrency_subprice;
5010
	public $multicurrency_total_ht;
5011
	public $multicurrency_total_tva;
5012
	public $multicurrency_total_ttc;
5013
5014
	/**
5015
	 *	Load invoice line from database
5016
	 *
5017
	 *	@param	int		$rowid      id of invoice line to get
5018
	 *	@return	int					<0 if KO, >0 if OK
5019
	 */
5020
    public function fetch($rowid)
5021
	{
5022
		$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,';
5023
		$sql .= ' fd.localtax1_tx, fd. localtax2_tx, fd.remise, fd.remise_percent, fd.fk_remise_except, fd.subprice,';
5024
		$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,';
5025
		$sql .= ' fd.info_bits, fd.special_code, fd.total_ht, fd.total_tva, fd.total_ttc, fd.total_localtax1, fd.total_localtax2, fd.rang,';
5026
		$sql .= ' fd.fk_code_ventilation,';
5027
		$sql .= ' fd.fk_unit, fd.fk_user_author, fd.fk_user_modif,';
5028
		$sql .= ' fd.situation_percent, fd.fk_prev_id,';
5029
		$sql .= ' fd.multicurrency_subprice,';
5030
		$sql .= ' fd.multicurrency_total_ht,';
5031
		$sql .= ' fd.multicurrency_total_tva,';
5032
		$sql .= ' fd.multicurrency_total_ttc,';
5033
		$sql .= ' p.ref as product_ref, p.label as product_libelle, p.description as product_desc';
5034
		$sql .= ' FROM '.MAIN_DB_PREFIX.'facturedet as fd';
5035
		$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON fd.fk_product = p.rowid';
5036
		$sql .= ' WHERE fd.rowid = '.$rowid;
5037
5038
		$result = $this->db->query($sql);
5039
		if ($result)
5040
		{
5041
			$objp = $this->db->fetch_object($result);
5042
5043
			$this->rowid = $objp->rowid;
5044
			$this->id = $objp->rowid;
5045
			$this->fk_facture = $objp->fk_facture;
5046
			$this->fk_parent_line = $objp->fk_parent_line;
5047
			$this->label				= $objp->custom_label;
5048
			$this->desc					= $objp->description;
5049
			$this->qty = $objp->qty;
5050
			$this->subprice = $objp->subprice;
5051
			$this->vat_src_code = $objp->vat_src_code;
5052
			$this->tva_tx = $objp->tva_tx;
5053
			$this->localtax1_tx			= $objp->localtax1_tx;
5054
			$this->localtax2_tx			= $objp->localtax2_tx;
5055
			$this->remise_percent = $objp->remise_percent;
5056
			$this->fk_remise_except = $objp->fk_remise_except;
5057
			$this->fk_product			= $objp->fk_product;
5058
			$this->product_type = $objp->product_type;
5059
			$this->date_start			= $this->db->jdate($objp->date_start);
5060
			$this->date_end				= $this->db->jdate($objp->date_end);
5061
			$this->info_bits			= $objp->info_bits;
5062
			$this->tva_npr = ($objp->info_bits & 1 == 1) ? 1 : 0;
5063
			$this->special_code = $objp->special_code;
5064
			$this->total_ht				= $objp->total_ht;
5065
			$this->total_tva			= $objp->total_tva;
5066
			$this->total_localtax1		= $objp->total_localtax1;
5067
			$this->total_localtax2		= $objp->total_localtax2;
5068
			$this->total_ttc			= $objp->total_ttc;
5069
			$this->fk_code_ventilation = $objp->fk_code_ventilation;
5070
			$this->rang					= $objp->rang;
5071
			$this->fk_fournprice = $objp->fk_fournprice;
5072
			$marginInfos				= getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
5073
			$this->pa_ht				= $marginInfos[0];
5074
			$this->marge_tx				= $marginInfos[1];
5075
			$this->marque_tx			= $marginInfos[2];
5076
5077
			$this->ref = $objp->product_ref; // deprecated
5078
			$this->product_ref = $objp->product_ref;
5079
			$this->libelle				= $objp->product_libelle; // deprecated
5080
			$this->product_label		= $objp->product_libelle;
5081
			$this->product_desc			= $objp->product_desc;
5082
			$this->fk_unit				= $objp->fk_unit;
5083
			$this->fk_user_modif		= $objp->fk_user_modif;
5084
			$this->fk_user_author = $objp->fk_user_author;
5085
5086
			$this->situation_percent    = $objp->situation_percent;
5087
			$this->fk_prev_id           = $objp->fk_prev_id;
5088
5089
			$this->multicurrency_subprice = $objp->multicurrency_subprice;
5090
			$this->multicurrency_total_ht = $objp->multicurrency_total_ht;
5091
			$this->multicurrency_total_tva = $objp->multicurrency_total_tva;
5092
			$this->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
5093
5094
			$this->db->free($result);
5095
5096
			return 1;
5097
		}
5098
		else
5099
		{
5100
		    $this->error = $this->db->lasterror();
5101
			return -1;
5102
		}
5103
	}
5104
5105
	/**
5106
	 *	Insert line into database
5107
	 *
5108
	 *	@param      int		$notrigger		                 1 no triggers
5109
	 *  @param      int     $noerrorifdiscountalreadylinked  1=Do not make error if lines is linked to a discount and discount already linked to another
5110
	 *	@return		int						                 <0 if KO, >0 if OK
5111
	 */
5112
    public function insert($notrigger = 0, $noerrorifdiscountalreadylinked = 0)
5113
	{
5114
		global $langs, $user, $conf;
5115
5116
		$error = 0;
5117
5118
        $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'.
5119
5120
        dol_syslog(get_class($this)."::insert rang=".$this->rang, LOG_DEBUG);
5121
5122
		// Clean parameters
5123
		$this->desc = trim($this->desc);
5124
		if (empty($this->tva_tx)) $this->tva_tx = 0;
5125
		if (empty($this->localtax1_tx)) $this->localtax1_tx = 0;
5126
		if (empty($this->localtax2_tx)) $this->localtax2_tx = 0;
5127
		if (empty($this->localtax1_type)) $this->localtax1_type = 0;
5128
		if (empty($this->localtax2_type)) $this->localtax2_type = 0;
5129
		if (empty($this->total_localtax1)) $this->total_localtax1 = 0;
5130
		if (empty($this->total_localtax2)) $this->total_localtax2 = 0;
5131
		if (empty($this->rang)) $this->rang = 0;
5132
		if (empty($this->remise_percent)) $this->remise_percent = 0;
5133
		if (empty($this->info_bits)) $this->info_bits = 0;
5134
		if (empty($this->subprice)) $this->subprice = 0;
5135
		if (empty($this->special_code)) $this->special_code = 0;
5136
		if (empty($this->fk_parent_line)) $this->fk_parent_line = 0;
5137
		if (empty($this->fk_prev_id)) $this->fk_prev_id = 0;
5138
		if (!isset($this->situation_percent) || $this->situation_percent > 100 || (string) $this->situation_percent == '') $this->situation_percent = 100;
5139
5140
		if (empty($this->pa_ht)) $this->pa_ht = 0;
5141
		if (empty($this->multicurrency_subprice)) $this->multicurrency_subprice = 0;
5142
		if (empty($this->multicurrency_total_ht)) $this->multicurrency_total_ht = 0;
5143
		if (empty($this->multicurrency_total_tva)) $this->multicurrency_total_tva = 0;
5144
		if (empty($this->multicurrency_total_ttc)) $this->multicurrency_total_ttc = 0;
5145
5146
		// if buy price not defined, define buyprice as configured in margin admin
5147
		if ($this->pa_ht == 0 && $pa_ht_isemptystring)
5148
		{
5149
			if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0)
5150
			{
5151
				return $result;
5152
			}
5153
			else
5154
			{
5155
				$this->pa_ht = $result;
5156
			}
5157
		}
5158
5159
		// Check parameters
5160
		if ($this->product_type < 0)
5161
		{
5162
			$this->error = 'ErrorProductTypeMustBe0orMore';
5163
			return -1;
5164
		}
5165
		if (!empty($this->fk_product))
5166
		{
5167
			// Check product exists
5168
			$result = Product::isExistingObject('product', $this->fk_product);
5169
			if ($result <= 0)
5170
			{
5171
				$this->error = 'ErrorProductIdDoesNotExists';
5172
				dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
5173
				return -1;
5174
			}
5175
		}
5176
5177
		$this->db->begin();
5178
5179
		// Insertion dans base de la ligne
5180
		$sql = 'INSERT INTO '.MAIN_DB_PREFIX.'facturedet';
5181
		$sql .= ' (fk_facture, fk_parent_line, label, description, qty,';
5182
		$sql .= ' vat_src_code, tva_tx, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
5183
		$sql .= ' fk_product, product_type, remise_percent, subprice, fk_remise_except,';
5184
		$sql .= ' date_start, date_end, fk_code_ventilation, ';
5185
		$sql .= ' rang, special_code, fk_product_fournisseur_price, buy_price_ht,';
5186
		$sql .= ' info_bits, total_ht, total_tva, total_ttc, total_localtax1, total_localtax2,';
5187
		$sql .= ' situation_percent, fk_prev_id,';
5188
		$sql .= ' fk_unit, fk_user_author, fk_user_modif,';
5189
		$sql .= ' fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
5190
		$sql .= ')';
5191
		$sql .= " VALUES (".$this->fk_facture.",";
5192
		$sql .= " ".($this->fk_parent_line > 0 ? $this->fk_parent_line : "null").",";
5193
		$sql .= " ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null").",";
5194
		$sql .= " '".$this->db->escape($this->desc)."',";
5195
		$sql .= " ".price2num($this->qty).",";
5196
        $sql .= " ".(empty($this->vat_src_code) ? "''" : "'".$this->db->escape($this->vat_src_code)."'").",";
5197
		$sql .= " ".price2num($this->tva_tx).",";
5198
		$sql .= " ".price2num($this->localtax1_tx).",";
5199
		$sql .= " ".price2num($this->localtax2_tx).",";
5200
		$sql .= " '".$this->db->escape($this->localtax1_type)."',";
5201
		$sql .= " '".$this->db->escape($this->localtax2_type)."',";
5202
		$sql .= ' '.(!empty($this->fk_product) ? $this->fk_product : "null").',';
5203
		$sql .= " ".((int) $this->product_type).",";
5204
		$sql .= " ".price2num($this->remise_percent).",";
5205
		$sql .= " ".price2num($this->subprice).",";
5206
		$sql .= ' '.(!empty($this->fk_remise_except) ? $this->fk_remise_except : "null").',';
5207
		$sql .= " ".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null").",";
5208
		$sql .= " ".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null").",";
5209
		$sql .= ' '.$this->fk_code_ventilation.',';
5210
		$sql .= ' '.$this->rang.',';
5211
		$sql .= ' '.$this->special_code.',';
5212
		$sql .= ' '.(!empty($this->fk_fournprice) ? $this->fk_fournprice : "null").',';
5213
		$sql .= ' '.price2num($this->pa_ht).',';
5214
		$sql .= " '".$this->db->escape($this->info_bits)."',";
5215
		$sql .= " ".price2num($this->total_ht).",";
5216
		$sql .= " ".price2num($this->total_tva).",";
5217
		$sql .= " ".price2num($this->total_ttc).",";
5218
		$sql .= " ".price2num($this->total_localtax1).",";
5219
		$sql .= " ".price2num($this->total_localtax2);
5220
		$sql .= ", ".$this->situation_percent;
5221
		$sql .= ", ".(!empty($this->fk_prev_id) ? $this->fk_prev_id : "null");
5222
		$sql .= ", ".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
5223
		$sql .= ", ".$user->id;
5224
		$sql .= ", ".$user->id;
5225
		$sql .= ", ".(int) $this->fk_multicurrency;
5226
		$sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
5227
		$sql .= ", ".price2num($this->multicurrency_subprice);
5228
		$sql .= ", ".price2num($this->multicurrency_total_ht);
5229
		$sql .= ", ".price2num($this->multicurrency_total_tva);
5230
		$sql .= ", ".price2num($this->multicurrency_total_ttc);
5231
		$sql .= ')';
5232
5233
		dol_syslog(get_class($this)."::insert", LOG_DEBUG);
5234
		$resql = $this->db->query($sql);
5235
		if ($resql)
5236
		{
5237
			$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'facturedet');
5238
			$this->rowid = $this->id; // For backward compatibility
5239
5240
            if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) // For avoid conflicts if trigger used
5241
            {
5242
            	$result = $this->insertExtraFields();
5243
            	if ($result < 0)
5244
            	{
5245
            		$error++;
5246
            	}
5247
            }
5248
5249
			// Si fk_remise_except defini, on lie la remise a la facture
5250
			// ce qui la flague comme "consommee".
5251
			if ($this->fk_remise_except)
5252
			{
5253
				$discount = new DiscountAbsolute($this->db);
5254
				$result = $discount->fetch($this->fk_remise_except);
5255
				if ($result >= 0)
5256
				{
5257
					// Check if discount was found
5258
					if ($result > 0)
5259
					{
5260
					    // Check if discount not already affected to another invoice
5261
						if ($discount->fk_facture_line > 0)
5262
						{
5263
						    if (empty($noerrorifdiscountalreadylinked))
5264
						    {
5265
    							$this->error = $langs->trans("ErrorDiscountAlreadyUsed", $discount->id);
5266
    							dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
5267
    							$this->db->rollback();
5268
    							return -3;
5269
						    }
5270
						}
5271
						else
5272
						{
5273
							$result = $discount->link_to_invoice($this->rowid, 0);
5274
							if ($result < 0)
5275
							{
5276
								$this->error = $discount->error;
5277
								dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
5278
								$this->db->rollback();
5279
								return -3;
5280
							}
5281
						}
5282
					}
5283
					else
5284
					{
5285
						$this->error = $langs->trans("ErrorADiscountThatHasBeenRemovedIsIncluded");
5286
						dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
5287
						$this->db->rollback();
5288
						return -3;
5289
					}
5290
				}
5291
				else
5292
				{
5293
					$this->error = $discount->error;
5294
					dol_syslog(get_class($this)."::insert Error ".$this->error, LOG_ERR);
5295
					$this->db->rollback();
5296
					return -3;
5297
				}
5298
			}
5299
5300
			if (!$notrigger)
5301
			{
5302
                // Call trigger
5303
                $result = $this->call_trigger('LINEBILL_INSERT', $user);
5304
                if ($result < 0)
5305
                {
5306
					$this->db->rollback();
5307
					return -2;
5308
				}
5309
                // End call triggers
5310
			}
5311
5312
			$this->db->commit();
5313
			return $this->id;
5314
		}
5315
		else
5316
		{
5317
			$this->error = $this->db->lasterror();
5318
			$this->db->rollback();
5319
			return -2;
5320
		}
5321
	}
5322
5323
	/**
5324
	 *	Update line into database
5325
	 *
5326
	 *	@param		User	$user		User object
5327
	 *	@param		int		$notrigger	Disable triggers
5328
	 *	@return		int					<0 if KO, >0 if OK
5329
	 */
5330
    public function update($user = '', $notrigger = 0)
5331
	{
5332
		global $user, $conf;
5333
5334
		$error = 0;
5335
5336
		$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'.
5337
5338
		// Clean parameters
5339
		$this->desc = trim($this->desc);
5340
		if (empty($this->tva_tx)) $this->tva_tx = 0;
5341
		if (empty($this->localtax1_tx)) $this->localtax1_tx = 0;
5342
		if (empty($this->localtax2_tx)) $this->localtax2_tx = 0;
5343
		if (empty($this->localtax1_type)) $this->localtax1_type = 0;
5344
		if (empty($this->localtax2_type)) $this->localtax2_type = 0;
5345
		if (empty($this->total_localtax1)) $this->total_localtax1 = 0;
5346
		if (empty($this->total_localtax2)) $this->total_localtax2 = 0;
5347
		if (empty($this->remise_percent)) $this->remise_percent = 0;
5348
		if (empty($this->info_bits)) $this->info_bits = 0;
5349
		if (empty($this->special_code)) $this->special_code = 0;
5350
		if (empty($this->product_type)) $this->product_type = 0;
5351
		if (empty($this->fk_parent_line)) $this->fk_parent_line = 0;
5352
		if (!isset($this->situation_percent) || $this->situation_percent > 100 || (string) $this->situation_percent == '') $this->situation_percent = 100;
5353
		if (empty($this->pa_ht)) $this->pa_ht = 0;
5354
5355
		if (empty($this->multicurrency_subprice)) $this->multicurrency_subprice = 0;
5356
		if (empty($this->multicurrency_total_ht)) $this->multicurrency_total_ht = 0;
5357
		if (empty($this->multicurrency_total_tva)) $this->multicurrency_total_tva = 0;
5358
		if (empty($this->multicurrency_total_ttc)) $this->multicurrency_total_ttc = 0;
5359
5360
		// Check parameters
5361
		if ($this->product_type < 0) return -1;
5362
5363
		// if buy price not defined, define buyprice as configured in margin admin
5364
		if ($this->pa_ht == 0 && $pa_ht_isemptystring)
5365
		{
5366
			if (($result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product)) < 0)
5367
			{
5368
				return $result;
5369
			}
5370
			else
5371
			{
5372
				$this->pa_ht = $result;
5373
			}
5374
		}
5375
5376
		$this->db->begin();
5377
5378
        // Mise a jour ligne en base
5379
        $sql = "UPDATE ".MAIN_DB_PREFIX."facturedet SET";
5380
        $sql .= " description='".$this->db->escape($this->desc)."'";
5381
        $sql .= ", label=".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
5382
        $sql .= ", subprice=".price2num($this->subprice)."";
5383
        $sql .= ", remise_percent=".price2num($this->remise_percent)."";
5384
        if ($this->fk_remise_except) $sql .= ", fk_remise_except=".$this->fk_remise_except;
5385
        else $sql .= ", fk_remise_except=null";
5386
		$sql .= ", vat_src_code = '".(empty($this->vat_src_code) ? '' : $this->db->escape($this->vat_src_code))."'";
5387
        $sql .= ", tva_tx=".price2num($this->tva_tx)."";
5388
        $sql .= ", localtax1_tx=".price2num($this->localtax1_tx)."";
5389
        $sql .= ", localtax2_tx=".price2num($this->localtax2_tx)."";
5390
		$sql .= ", localtax1_type='".$this->db->escape($this->localtax1_type)."'";
5391
		$sql .= ", localtax2_type='".$this->db->escape($this->localtax2_type)."'";
5392
        $sql .= ", qty=".price2num($this->qty);
5393
        $sql .= ", date_start=".(!empty($this->date_start) ? "'".$this->db->idate($this->date_start)."'" : "null");
5394
        $sql .= ", date_end=".(!empty($this->date_end) ? "'".$this->db->idate($this->date_end)."'" : "null");
5395
        $sql .= ", product_type=".$this->product_type;
5396
        $sql .= ", info_bits='".$this->db->escape($this->info_bits)."'";
5397
        $sql .= ", special_code='".$this->db->escape($this->special_code)."'";
5398
        if (empty($this->skip_update_total))
5399
        {
5400
        	$sql .= ", total_ht=".price2num($this->total_ht);
5401
        	$sql .= ", total_tva=".price2num($this->total_tva);
5402
        	$sql .= ", total_ttc=".price2num($this->total_ttc);
5403
        	$sql .= ", total_localtax1=".price2num($this->total_localtax1);
5404
        	$sql .= ", total_localtax2=".price2num($this->total_localtax2);
5405
        }
5406
		$sql .= ", fk_product_fournisseur_price=".(!empty($this->fk_fournprice) ? "'".$this->db->escape($this->fk_fournprice)."'" : "null");
5407
		$sql .= ", buy_price_ht='".price2num($this->pa_ht)."'";
5408
		$sql .= ", fk_parent_line=".($this->fk_parent_line > 0 ? $this->fk_parent_line : "null");
5409
		if (!empty($this->rang)) $sql .= ", rang=".$this->rang;
5410
		$sql .= ", situation_percent=".$this->situation_percent;
5411
		$sql .= ", fk_unit=".(!$this->fk_unit ? 'NULL' : $this->fk_unit);
5412
		$sql .= ", fk_user_modif =".$user->id;
5413
5414
		// Multicurrency
5415
		$sql .= ", multicurrency_subprice=".price2num($this->multicurrency_subprice)."";
5416
        $sql .= ", multicurrency_total_ht=".price2num($this->multicurrency_total_ht)."";
5417
        $sql .= ", multicurrency_total_tva=".price2num($this->multicurrency_total_tva)."";
5418
        $sql .= ", multicurrency_total_ttc=".price2num($this->multicurrency_total_ttc)."";
5419
5420
		$sql .= " WHERE rowid = ".$this->rowid;
5421
5422
		dol_syslog(get_class($this)."::update", LOG_DEBUG);
5423
		$resql = $this->db->query($sql);
5424
		if ($resql)
5425
		{
5426
        	if (empty($conf->global->MAIN_EXTRAFIELDS_DISABLED)) // For avoid conflicts if trigger used
5427
        	{
5428
        		$this->id = $this->rowid;
5429
        		$result = $this->insertExtraFields();
5430
        		if ($result < 0)
5431
        		{
5432
        			$error++;
5433
        		}
5434
        	}
5435
5436
			if (!$error && !$notrigger)
5437
			{
5438
                // Call trigger
5439
                $result = $this->call_trigger('LINEBILL_UPDATE', $user);
5440
                if ($result < 0)
5441
 				{
5442
					$this->db->rollback();
5443
					return -2;
5444
				}
5445
                // End call triggers
5446
			}
5447
			$this->db->commit();
5448
			return 1;
5449
		}
5450
		else
5451
		{
5452
			$this->error = $this->db->error();
5453
			$this->db->rollback();
5454
			return -2;
5455
		}
5456
	}
5457
5458
	/**
5459
	 * 	Delete line in database
5460
	 *  TODO Add param User $user and notrigger (see skeleton)
5461
     *
5462
	 *	@return	    int		           <0 if KO, >0 if OK
5463
	 */
5464
    public function delete()
5465
	{
5466
		global $user;
5467
5468
		$this->db->begin();
5469
5470
		// Call trigger
5471
		$result = $this->call_trigger('LINEBILL_DELETE', $user);
5472
		if ($result < 0)
5473
		{
5474
			$this->db->rollback();
5475
			return -1;
5476
		}
5477
		// End call triggers
5478
5479
5480
		$sql = "DELETE FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid = ".$this->rowid;
1 ignored issue
show
Deprecated Code introduced by
The property CommonObjectLine::$rowid has been deprecated: Try to use id property as possible (even if field into database is still rowid) ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

5480
		$sql = "DELETE FROM ".MAIN_DB_PREFIX."facturedet WHERE rowid = "./** @scrutinizer ignore-deprecated */ $this->rowid;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
5481
		dol_syslog(get_class($this)."::delete", LOG_DEBUG);
5482
		if ($this->db->query($sql))
5483
		{
5484
			$this->db->commit();
5485
			return 1;
5486
		}
5487
		else
5488
		{
5489
			$this->error = $this->db->error()." sql=".$sql;
5490
			$this->db->rollback();
5491
			return -1;
5492
		}
5493
	}
5494
5495
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5496
	/**
5497
     *	Update DB line fields total_xxx
5498
	 *	Used by migration
5499
	 *
5500
	 *	@return		int		<0 if KO, >0 if OK
5501
	 */
5502
    public function update_total()
5503
	{
5504
        // phpcs:enable
5505
		$this->db->begin();
5506
		dol_syslog(get_class($this)."::update_total", LOG_DEBUG);
5507
5508
		// Clean parameters
5509
		if (empty($this->total_localtax1)) $this->total_localtax1 = 0;
5510
		if (empty($this->total_localtax2)) $this->total_localtax2 = 0;
5511
5512
		// Mise a jour ligne en base
5513
		$sql = "UPDATE ".MAIN_DB_PREFIX."facturedet SET";
5514
		$sql .= " total_ht=".price2num($this->total_ht)."";
5515
		$sql .= ",total_tva=".price2num($this->total_tva)."";
5516
		$sql .= ",total_localtax1=".price2num($this->total_localtax1)."";
5517
		$sql .= ",total_localtax2=".price2num($this->total_localtax2)."";
5518
		$sql .= ",total_ttc=".price2num($this->total_ttc)."";
5519
		$sql .= " WHERE rowid = ".$this->rowid;
1 ignored issue
show
Deprecated Code introduced by
The property CommonObjectLine::$rowid has been deprecated: Try to use id property as possible (even if field into database is still rowid) ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

5519
		$sql .= " WHERE rowid = "./** @scrutinizer ignore-deprecated */ $this->rowid;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
5520
5521
		dol_syslog(get_class($this)."::update_total", LOG_DEBUG);
5522
5523
		$resql = $this->db->query($sql);
5524
		if ($resql)
5525
		{
5526
			$this->db->commit();
5527
			return 1;
5528
		}
5529
		else
5530
		{
5531
			$this->error = $this->db->error();
5532
			$this->db->rollback();
5533
			return -2;
5534
		}
5535
	}
5536
5537
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5538
	/**
5539
	 * Returns situation_percent of the previous line.
5540
	 * Warning: If invoice is a replacement invoice, this->fk_prev_id is id of the replaced line.
5541
	 *
5542
	 * @param  int     $invoiceid      Invoice id
5543
	 * @param  bool    $include_credit_note		Include credit note or not
5544
	 * @return int                     >= 0
5545
	 */
5546
    public function get_prev_progress($invoiceid, $include_credit_note = true)
5547
	{
5548
        // phpcs:enable
5549
		global $invoicecache;
5550
		if (is_null($this->fk_prev_id) || empty($this->fk_prev_id) || $this->fk_prev_id == "") {
5551
			return 0;
5552
		} else {
5553
		    // If invoice is not a situation invoice, this->fk_prev_id is used for something else
5554
			if (!isset($invoicecache[$invoiceid])) {
5555
				$invoicecache[$invoiceid] = new Facture($this->db);
5556
				$invoicecache[$invoiceid]->fetch($invoiceid);
5557
			}
5558
			if ($invoicecache[$invoiceid]->type != Facture::TYPE_SITUATION) return 0;
5559
5560
			$sql = 'SELECT situation_percent FROM '.MAIN_DB_PREFIX.'facturedet WHERE rowid='.$this->fk_prev_id;
5561
			$resql = $this->db->query($sql);
5562
			if ($resql && $resql->num_rows > 0) {
5563
				$res = $this->db->fetch_array($resql);
5564
5565
				$returnPercent = floatval($res['situation_percent']);
5566
5567
				if ($include_credit_note) {
5568
				    $sql = 'SELECT fd.situation_percent FROM '.MAIN_DB_PREFIX.'facturedet fd';
5569
				    $sql .= ' JOIN '.MAIN_DB_PREFIX.'facture f ON (f.rowid = fd.fk_facture) ';
5570
				    $sql .= ' WHERE fd.fk_prev_id ='.$this->fk_prev_id;
5571
				    $sql .= ' AND f.situation_cycle_ref = '.$tmpinvoice->situation_cycle_ref; // Prevent cycle outed
5572
				    $sql .= ' AND f.type = '.Facture::TYPE_CREDIT_NOTE;
5573
5574
				    $res = $this->db->query($sql);
5575
				    if ($res) {
5576
				        while ($obj = $this->db->fetch_object($res)) {
5577
				            $returnPercent = $returnPercent + floatval($obj->situation_percent);
5578
				        }
5579
				    }
5580
				}
5581
5582
				return $returnPercent;
5583
			} else {
5584
				$this->error = $this->db->error();
5585
				dol_syslog(get_class($this)."::select Error ".$this->error, LOG_ERR);
5586
				$this->db->rollback();
5587
				return -1;
5588
			}
5589
		}
5590
	}
5591
}
5592