Facture::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
/* Copyright (C) 2002-2007  Rodolphe Quiedeville        <[email protected]>
4
 * Copyright (C) 2004-2013  Laurent Destailleur         <[email protected]>
5
 * Copyright (C) 2004       Sebastien Di Cintio         <[email protected]>
6
 * Copyright (C) 2004       Benoit Mortier              <[email protected]>
7
 * Copyright (C) 2005       Marc Barilley / Ocebo       <[email protected]>
8
 * Copyright (C) 2005-2014  Regis Houssin               <[email protected]>
9
 * Copyright (C) 2006       Andre Cianfarani            <[email protected]>
10
 * Copyright (C) 2007       Franky Van Liedekerke       <[email protected]>
11
 * Copyright (C) 2010-2020  Juanjo Menent               <[email protected]>
12
 * Copyright (C) 2012-2014  Christophe Battarel         <[email protected]>
13
 * Copyright (C) 2012-2015  Marcos García               <[email protected]>
14
 * Copyright (C) 2012       Cédric Salvador             <[email protected]>
15
 * Copyright (C) 2012-2014  Raphaël Doursenaud          <[email protected]>
16
 * Copyright (C) 2013       Cedric Gross                <[email protected]>
17
 * Copyright (C) 2013       Florian Henry               <[email protected]>
18
 * Copyright (C) 2016-2022  Ferran Marcet               <[email protected]>
19
 * Copyright (C) 2018-2024  Alexandre Spangaro          <[email protected]>
20
 * Copyright (C) 2018       Nicolas ZABOURI             <[email protected]>
21
 * Copyright (C) 2022       Sylvain Legrand             <[email protected]>
22
 * Copyright (C) 2023      	Gauthier VERDOL       	    <[email protected]>
23
 * Copyright (C) 2023		Nick Fragoulis
24
 * Copyright (C) 2024		MDW							<[email protected]>
25
 * Copyright (C) 2024       Frédéric France             <[email protected]>
26
 * Copyright (C) 2024       Rafael San José             <[email protected]>
27
 *
28
 * This program is free software; you can redistribute it and/or modify
29
 * it under the terms of the GNU General Public License as published by
30
 * the Free Software Foundation; either version 3 of the License, or
31
 * (at your option) any later version.
32
 *
33
 * This program is distributed in the hope that it will be useful,
34
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
35
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
36
 * GNU General Public License for more details.
37
 *
38
 * You should have received a copy of the GNU General Public License
39
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
40
 */
41
42
namespace Dolibarr\Code\Compta\Classes;
43
44
use Dolibarr\Code\Comm\Classes\Propal;
45
use Dolibarr\Code\Commande\Classes\Commande;
46
use Dolibarr\Code\Core\Classes\CommonInvoice;
47
use Dolibarr\Code\Core\Classes\CommonInvoiceLine;
48
use Dolibarr\Code\Core\Classes\DiscountAbsolute;
49
use Dolibarr\Code\Core\Classes\FormMargin;
50
use Dolibarr\Code\Core\Classes\Translate;
51
use Dolibarr\Code\Core\Classes\WorkboardResponse;
52
use Dolibarr\Code\Expedition\Classes\Expedition;
53
use Dolibarr\Code\Fourn\Classes\ProductFournisseur;
54
use Dolibarr\Code\MultiCurrency\Classes\MultiCurrency;
55
use Dolibarr\Code\Product\Classes\Entrepot;
56
use Dolibarr\Code\Product\Classes\MouvementStock;
57
use Dolibarr\Code\Product\Classes\Product;
58
use Dolibarr\Code\Product\Classes\Productbatch;
59
use Dolibarr\Code\Societe\Classes\Societe;
60
use Dolibarr\Code\User\Classes\User;
61
use Dolibarr\Core\Base\CommonObject;
62
use DoliDB;
63
64
/**
65
 *  \file       htdocs/compta/facture/class/facture.class.php
66
 *  \ingroup    invoice
67
 *  \brief      File of class to manage invoices
68
 */
69
70
require_once constant('DOL_DOCUMENT_ROOT') . '/margin/lib/margins.lib.php';
71
72
/**
73
 *  Class to manage invoices
74
 * Model Facture
75
 */
76
class Facture extends CommonInvoice
77
{
78
    /**
79
     * @var string ID to identify managed object
80
     */
81
    public $element = 'facture';
82
83
    /**
84
     * @var string Name of table without prefix where object is stored
85
     */
86
    public $table_element = 'facture';
87
88
    /**
89
     * @var string    Name of subtable line
90
     */
91
    public $table_element_line = 'facturedet';
92
93
    /**
94
     * @var string Fieldname with ID of parent key if this field has a parent
95
     */
96
    public $fk_element = 'fk_facture';
97
98
    /**
99
     * @var string String with name of icon for myobject.
100
     */
101
    public $picto = 'bill';
102
103
    /**
104
     * 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
105
     * @var integer
106
     */
107
    public $restrictiononfksoc = 1;
108
109
    /**
110
     * {@inheritdoc}
111
     */
112
    protected $table_ref_field = 'ref';
113
114
    /**
115
     * @var int ID
116
     * @deprecated      Use $user_creation_id
117
     */
118
    public $fk_user_author;
119
120
    /**
121
     * @var int|null ID
122
     * @deprecated      Use $user_validation_id
123
     */
124
    public $fk_user_valid;
125
126
    /**
127
     * @var int ID
128
     * @deprecated      Use $user_modification_id
129
     */
130
    public $fk_user_modif;
131
132
133
    public $datem;
134
135
    /**
136
     * @var int Date expected for delivery
137
     */
138
    public $delivery_date; // Date expected of shipment (date of start of shipment, not the reception that occurs some days after)
139
140
    /**
141
     * @var string customer ref
142
     * @deprecated
143
     * @see $ref_customer
144
     */
145
    public $ref_client;
146
147
    /**
148
     * @var string customer ref
149
     */
150
    public $ref_customer;
151
152
    public $total_ht;
153
    public $total_tva;
154
    public $total_localtax1;
155
    public $total_localtax2;
156
    public $total_ttc;
157
    public $revenuestamp;
158
159
    public $resteapayer;
160
161
    /**
162
     * 1 if invoice paid COMPLETELY, 0 otherwise (do not use it anymore, use statut and close_code)
163
     */
164
    public $paye;
165
166
    //! key of module source when invoice generated from a dedicated module ('cashdesk', 'takepos', ...)
167
    public $module_source;
168
    //! key of pos source ('0', '1', ...)
169
    public $pos_source;
170
    //! id of template invoice when generated from a template invoice
171
    public $fk_fac_rec_source;
172
    //! id of source invoice if replacement invoice or credit note
173
    public $fk_facture_source;
174
    public $linked_objects = array();
175
176
    /**
177
     * @var int ID Field to store bank id to use when payment mode is withdraw
178
     */
179
    public $fk_bank;
180
181
    /**
182
     * @var CommonInvoiceLine[]
183
     */
184
    public $lines = array();
185
186
    /**
187
     * @var FactureLigne
188
     */
189
    public $line;
190
    public $extraparams = array();
191
192
    /**
193
     * @var int ID facture rec
194
     */
195
    public $fac_rec;
196
197
    public $date_pointoftax;
198
199
200
    /**
201
     * @var int Situation cycle reference number
202
     */
203
    public $situation_cycle_ref;
204
205
    /**
206
     * @var int Situation counter inside the cycle
207
     */
208
    public $situation_counter;
209
210
    /**
211
     * @var int Final situation flag
212
     */
213
    public $situation_final;
214
215
    /**
216
     * @var array Table of previous situations
217
     */
218
    public $tab_previous_situation_invoice = array();
219
220
    /**
221
     * @var array Table of next situations
222
     */
223
    public $tab_next_situation_invoice = array();
224
225
    /**
226
     * @var static object oldcopy
227
     */
228
    public $oldcopy;
229
230
    /**
231
     * @var double percentage of retainage
232
     */
233
    public $retained_warranty;
234
235
    /**
236
     * @var int timestamp of date limit of retainage
237
     */
238
    public $retained_warranty_date_limit;
239
240
    /**
241
     * @var int Code in llx_c_paiement
242
     */
243
    public $retained_warranty_fk_cond_reglement;
244
245
    /**
246
     * @var int availability ID
247
     */
248
    public $availability_id;
249
250
    public $date_closing;
251
252
    /**
253
     * @var int
254
     */
255
    public $source;
256
257
    /**
258
     * @var float   Percent of discount ("remise" in French)
259
     * @deprecated The discount percent is on line level now
260
     */
261
    public $remise_percent;
262
263
    /**
264
     * @var string payment url
265
     */
266
    public $online_payment_url;
267
268
269
270
    /**
271
     *  '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')
272
     *         Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)"
273
     *  'label' the translation key.
274
     *  'enabled' is a condition when the field must be managed.
275
     *  'position' is the sort order of field.
276
     *  'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
277
     *  '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)
278
     *  'noteditable' says if field is not editable (1 or 0)
279
     *  '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.
280
     *  'index' if we want an index in database.
281
     *  'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommended to name the field fk_...).
282
     *  'searchall' is 1 if we want to search in this field when making a search from the quick search button.
283
     *  '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).
284
     *  'css' is the CSS style to use on field. For example: 'maxwidth200'
285
     *  'help' is a string visible as a tooltip on field
286
     *  'showoncombobox' if value of the field must be visible into the label of the combobox that list record
287
     *  '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.
288
     *  'arrayofkeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel")
289
     *  'comment' is not used. You can store here any text of your choice. It is not used by application.
290
     *
291
     *  Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
292
     */
293
294
    // BEGIN MODULEBUILDER PROPERTIES
295
    /**
296
     * @var array<string,array{type:string,label:string,enabled:int<0,2>|string,position:int,notnull?:int,visible:int,noteditable?:int,default?:string,index?:int,foreignkey?:string,searchall?:int,isameasure?:int,css?:string,csslist?:string,help?:string,showoncombobox?:int,disabled?:int,arrayofkeyval?:array<int,string>,comment?:string}>  Array with all fields and their property. Do not use it as a static var. It may be modified by constructor.
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string,array{type:...ring>,comment?:string}> at position 16 could not be parsed: Expected '}' at position 16, but found 'int'.
Loading history...
297
     */
298
    public $fields = array(
299
        'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 1),
300
        'ref' => array('type' => 'varchar(30)', 'label' => 'Ref', 'enabled' => 1, 'visible' => 1, 'notnull' => 1, 'showoncombobox' => 1, 'position' => 5),
301
        'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 20, 'index' => 1),
302
        'ref_client' => array('type' => 'varchar(255)', 'label' => 'RefCustomer', 'enabled' => 1, 'visible' => -1, 'position' => 10),
303
        'ref_ext' => array('type' => 'varchar(255)', 'label' => 'Ref ext', 'enabled' => 1, 'visible' => 0, 'position' => 12),
304
        'type' => array('type' => 'smallint(6)', 'label' => 'Type', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 15),
305
        'subtype' => array('type' => 'smallint(6)', 'label' => 'InvoiceSubtype', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 15),
306
        //'increment' =>array('type'=>'varchar(10)', 'label'=>'Increment', 'enabled'=>1, 'visible'=>-1, 'position'=>45),
307
        'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 50),
308
        'datef' => array('type' => 'date', 'label' => 'DateInvoice', 'enabled' => 1, 'visible' => 1, 'position' => 20),
309
        'date_valid' => array('type' => 'date', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -1, 'position' => 22),
310
        'date_lim_reglement' => array('type' => 'date', 'label' => 'DateDue', 'enabled' => 1, 'visible' => 1, 'position' => 25),
311
        'date_closing' => array('type' => 'datetime', 'label' => 'Date closing', 'enabled' => 1, 'visible' => -1, 'position' => 30),
312
        'paye' => array('type' => 'smallint(6)', 'label' => 'InvoicePaidCompletely', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 80),
313
        //'amount' =>array('type'=>'double(24,8)', 'label'=>'Amount', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>85),
314
        //'remise_percent' =>array('type'=>'double', 'label'=>'RelativeDiscount', 'enabled'=>1, 'visible'=>-1, 'position'=>90),
315
        //'remise_absolue' =>array('type'=>'double', 'label'=>'CustomerRelativeDiscount', 'enabled'=>1, 'visible'=>-1, 'position'=>91),
316
        //'remise' =>array('type'=>'double', 'label'=>'Remise', 'enabled'=>1, 'visible'=>-1, 'position'=>100),
317
        'close_code' => array('type' => 'varchar(16)', 'label' => 'EarlyClosingReason', 'enabled' => 1, 'visible' => -1, 'position' => 92),
318
        'close_note' => array('type' => 'varchar(128)', 'label' => 'EarlyClosingComment', 'enabled' => 1, 'visible' => -1, 'position' => 93),
319
        'total_ht' => array('type' => 'double(24,8)', 'label' => 'AmountHT', 'enabled' => 1, 'visible' => 1, 'position' => 95, 'isameasure' => 1),
320
        'total_tva' => array('type' => 'double(24,8)', 'label' => 'AmountVAT', 'enabled' => 1, 'visible' => -1, 'position' => 100, 'isameasure' => 1),
321
        'localtax1' => array('type' => 'double(24,8)', 'label' => 'LT1', 'enabled' => 1, 'visible' => -1, 'position' => 110, 'isameasure' => 1),
322
        'localtax2' => array('type' => 'double(24,8)', 'label' => 'LT2', 'enabled' => 1, 'visible' => -1, 'position' => 120, 'isameasure' => 1),
323
        'revenuestamp' => array('type' => 'double(24,8)', 'label' => 'RevenueStamp', 'enabled' => 1, 'visible' => -1, 'position' => 115, 'isameasure' => 1),
324
        'total_ttc' => array('type' => 'double(24,8)', 'label' => 'AmountTTC', 'enabled' => 1, 'visible' => 1, 'position' => 130, 'isameasure' => 1),
325
        'fk_facture_source' => array('type' => 'integer', 'label' => 'SourceInvoice', 'enabled' => 1, 'visible' => -1, 'position' => 170),
326
        'fk_projet' => array('type' => 'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label' => 'Project', 'enabled' => 1, 'visible' => -1, 'position' => 175),
327
        'fk_account' => array('type' => 'integer', 'label' => 'Fk account', 'enabled' => 1, 'visible' => -1, 'position' => 180),
328
        'fk_currency' => array('type' => 'varchar(3)', 'label' => 'CurrencyCode', 'enabled' => 1, 'visible' => -1, 'position' => 185),
329
        'fk_cond_reglement' => array('type' => 'integer', 'label' => 'PaymentTerm', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 190),
330
        'fk_mode_reglement' => array('type' => 'integer', 'label' => 'PaymentMode', 'enabled' => 1, 'visible' => -1, 'position' => 195),
331
        'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 205),
332
        'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 210),
333
        'model_pdf' => array('type' => 'varchar(255)', 'label' => 'Model pdf', 'enabled' => 1, 'visible' => 0, 'position' => 215),
334
        'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => -1, 'position' => 225),
335
        'situation_cycle_ref' => array('type' => 'smallint(6)', 'label' => 'Situation cycle ref', 'enabled' => '$conf->global->INVOICE_USE_SITUATION', 'visible' => -1, 'position' => 230),
336
        'situation_counter' => array('type' => 'smallint(6)', 'label' => 'Situation counter', 'enabled' => '$conf->global->INVOICE_USE_SITUATION', 'visible' => -1, 'position' => 235),
337
        'situation_final' => array('type' => 'smallint(6)', 'label' => 'Situation final', 'enabled' => 'empty($conf->global->INVOICE_USE_SITUATION) ? 0 : 1', 'visible' => -1, 'position' => 240),
338
        'retained_warranty' => array('type' => 'double', 'label' => 'Retained warranty', 'enabled' => '$conf->global->INVOICE_USE_RETAINED_WARRANTY', 'visible' => -1, 'position' => 245),
339
        'retained_warranty_date_limit' => array('type' => 'date', 'label' => 'Retained warranty date limit', 'enabled' => '$conf->global->INVOICE_USE_RETAINED_WARRANTY', 'visible' => -1, 'position' => 250),
340
        '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),
341
        'fk_incoterms' => array('type' => 'integer', 'label' => 'IncotermCode', 'enabled' => '$conf->incoterm->enabled', 'visible' => -1, 'position' => 260),
342
        'location_incoterms' => array('type' => 'varchar(255)', 'label' => 'IncotermLabel', 'enabled' => '$conf->incoterm->enabled', 'visible' => -1, 'position' => 265),
343
        'date_pointoftax' => array('type' => 'date', 'label' => 'DatePointOfTax', 'enabled' => '$conf->global->INVOICE_POINTOFTAX_DATE', 'visible' => -1, 'position' => 270),
344
        'fk_multicurrency' => array('type' => 'integer', 'label' => 'MulticurrencyID', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 275),
345
        'multicurrency_code' => array('type' => 'varchar(255)', 'label' => 'Currency', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 280),
346
        'multicurrency_tx' => array('type' => 'double(24,8)', 'label' => 'CurrencyRate', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 285, 'isameasure' => 1),
347
        'multicurrency_total_ht' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountHT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 290, 'isameasure' => 1),
348
        'multicurrency_total_tva' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountVAT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 291, 'isameasure' => 1),
349
        'multicurrency_total_ttc' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountTTC', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 292, 'isameasure' => 1),
350
        'fk_fac_rec_source' => array('type' => 'integer', 'label' => 'RecurringInvoiceSource', 'enabled' => 1, 'visible' => -1, 'position' => 305),
351
        'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'LastMainDoc', 'enabled' => 1, 'visible' => -1, 'position' => 310),
352
        'module_source' => array('type' => 'varchar(32)', 'label' => 'POSModule', 'enabled' => "(isModEnabled('cashdesk') || isModEnabled('takepos') || getDolGlobalInt('INVOICE_SHOW_POS'))", 'visible' => -1, 'position' => 315),
353
        'pos_source' => array('type' => 'varchar(32)', 'label' => 'POSTerminal', 'enabled' => "(isModEnabled('cashdesk') || isModEnabled('takepos') || getDolGlobalInt('INVOICE_SHOW_POS'))", 'visible' => -1, 'position' => 320),
354
        'datec' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -1, 'position' => 500),
355
        'tms' => array('type' => 'timestamp', 'label' => 'DateModificationShort', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 502),
356
        'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => -1, 'position' => 506),
357
        'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModification', 'enabled' => 1, 'visible' => -1, 'notnull' => -1, 'position' => 508),
358
        'fk_user_valid' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserValidation', 'enabled' => 1, 'visible' => -1, 'position' => 510),
359
        'fk_user_closing' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserClosing', 'enabled' => 1, 'visible' => -1, 'position' => 512),
360
        'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'position' => 900),
361
        'fk_statut' => array('type' => 'smallint(6)', 'label' => 'Status', 'enabled' => 1, 'visible' => 1, 'notnull' => 1, 'position' => 1000, 'arrayofkeyval' => array(0 => 'Draft', 1 => 'Validated', 2 => 'Paid', 3 => 'Abandonned')),
362
    );
363
    // END MODULEBUILDER PROPERTIES
364
365
    /**
366
     * Standard invoice
367
     */
368
    const TYPE_STANDARD = 0;
369
370
    /**
371
     * Replacement invoice
372
     */
373
    const TYPE_REPLACEMENT = 1;
374
375
    /**
376
     * Credit note invoice
377
     */
378
    const TYPE_CREDIT_NOTE = 2;
379
380
    /**
381
     * Deposit invoice
382
     */
383
    const TYPE_DEPOSIT = 3;
384
385
    /**
386
     * Proforma invoice (should not be used. a proforma is an order)
387
     */
388
    const TYPE_PROFORMA = 4;
389
390
    /**
391
     * Situation invoice
392
     */
393
    const TYPE_SITUATION = 5;
394
395
    /**
396
     * Draft status
397
     */
398
    const STATUS_DRAFT = 0;
399
400
    /**
401
     * Validated (need to be paid)
402
     */
403
    const STATUS_VALIDATED = 1;
404
405
    /**
406
     * Classified paid.
407
     * If paid partially, $this->close_code can be:
408
     * - CLOSECODE_DISCOUNTVAT
409
     * - CLOSECODE_BADDEBT
410
     * If paid completely, this->close_code will be null
411
     */
412
    const STATUS_CLOSED = 2;
413
414
    /**
415
     * Classified abandoned and no payment done.
416
     * $this->close_code can be:
417
     * - CLOSECODE_BADDEBT
418
     * - CLOSECODE_ABANDONED
419
     * - CLOSECODE_REPLACED
420
     */
421
    const STATUS_ABANDONED = 3;
422
423
    const CLOSECODE_DISCOUNTVAT = 'discount_vat'; // Abandoned remain - escompte
424
    const CLOSECODE_BADDEBT = 'badcustomer'; // Abandoned remain - bad customer
425
    const CLOSECODE_BANKCHARGE = 'bankcharge'; // Abandoned remain - bank charge
426
    const CLOSECODE_OTHER = 'other'; // Abandoned remain - other
427
428
    const CLOSECODE_ABANDONED = 'abandon'; // Abandoned - other
429
    const CLOSECODE_REPLACED = 'replaced'; // Closed after doing a replacement invoice
430
431
432
    /**
433
     *  Constructor
434
     *
435
     *  @param  DoliDB      $db         Database handler
436
     */
437
    public function __construct(DoliDB $db)
438
    {
439
        $this->db = $db;
440
441
        $this->ismultientitymanaged = 1;
442
        $this->isextrafieldmanaged = 1;
443
    }
444
445
    /**
446
     *  Create invoice in database.
447
     *  Note: this->ref can be set or empty. If empty, we will use "(PROV999)"
448
     *  Note: this->fac_rec must be set to create invoice from a recurring invoice
449
     *
450
     *  @param  User    $user           Object user that create
451
     *  @param  int     $notrigger      1=Does not execute triggers, 0 otherwise
452
     *  @param  int     $forceduedate   If set, do not recalculate due date from payment condition but force it with value
453
     *  @return int                     Return integer <0 if KO, >0 if OK
454
     */
455
    public function create(User $user, $notrigger = 0, $forceduedate = 0)
456
    {
457
        global $langs, $conf, $mysoc, $hookmanager;
458
        $error = 0;
459
        $origin_user_author_id = ($user->id > 0 ? (int) $user->id : 0);
460
        // Clean parameters
461
        if (empty($this->type)) {
462
            $this->type = self::TYPE_STANDARD;
463
        }
464
465
        $this->ref_client = trim($this->ref_client);
466
467
        $this->note_private = (isset($this->note_private) ? trim($this->note_private) : '');
468
        $this->note = (isset($this->note) ? trim($this->note) : $this->note_private); // deprecated
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$note has been deprecated: Use $note_private instead. ( Ignorable by Annotation )

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

468
        $this->note = (isset(/** @scrutinizer ignore-deprecated */ $this->note) ? trim($this->note) : $this->note_private); // deprecated

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...
469
        $this->note_public = trim($this->note_public);
470
        if (!$this->cond_reglement_id) {
471
            $this->cond_reglement_id = 0;
472
        }
473
        if (!$this->mode_reglement_id) {
474
            $this->mode_reglement_id = 0;
475
        }
476
        $this->status = self::STATUS_DRAFT;
477
        $this->statut = self::STATUS_DRAFT; // deprecated
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

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

477
        /** @scrutinizer ignore-deprecated */ $this->statut = self::STATUS_DRAFT; // deprecated

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...
478
479
        if (!empty($this->multicurrency_code)) {
480
            // Multicurrency (test on $this->multicurrency_tx because we should take the default rate of multicurrency_code only if not using original rate)
481
            if (empty($this->multicurrency_tx)) {
482
                // If original rate is not set, we take a default value from date
483
                list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date);
484
            } else {
485
                // original rate multicurrency_tx and multicurrency_code are set, we use them
486
                $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
487
            }
488
        } else {
489
            $this->fk_multicurrency = 0;
490
        }
491
        if (empty($this->fk_multicurrency)) {
492
            $this->multicurrency_code = $conf->currency;
493
            $this->fk_multicurrency = 0;
494
            $this->multicurrency_tx = 1;
495
        }
496
497
        dol_syslog(get_only_class($this) . "::create user=" . $user->id . " date=" . $this->date);
498
499
        // Check parameters
500
        if (empty($this->date)) {
501
            $this->error = "Try to create an invoice with an empty parameter (date)";
502
            dol_syslog(get_only_class($this) . "::create " . $this->error, LOG_ERR);
503
            return -3;
504
        }
505
        $soc = new Societe($this->db);
506
        $result = $soc->fetch($this->socid);
507
        if ($result < 0) {
508
            $this->error = "Failed to fetch company: " . $soc->error;
509
            dol_syslog(get_only_class($this) . "::create " . $this->error, LOG_ERR);
510
            return -2;
511
        }
512
513
        $now = dol_now();
514
        $this->date_creation = $now;
515
516
        $this->db->begin();
517
518
        $originaldatewhen = null;
519
        $nextdatewhen = null;
520
        $previousdaynextdatewhen = null;
521
522
        // Erase some properties of the invoice to create with the one of the recurring invoice
523
        if ($this->fac_rec > 0) {
524
            $this->fk_fac_rec_source = $this->fac_rec;
525
526
            if (getDolGlobalString('MODEL_FAC_REC_AUTHOR')) {
527
                $origin_user_author_id = ($this->fk_user_author > 0 ? $this->fk_user_author : $origin_user_author_id);
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Compta\Cla...acture::$fk_user_author has been deprecated: Use $user_creation_id ( Ignorable by Annotation )

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

527
                $origin_user_author_id = (/** @scrutinizer ignore-deprecated */ $this->fk_user_author > 0 ? $this->fk_user_author : $origin_user_author_id);

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...
528
            }
529
            $_facrec = new FactureRec($this->db);
530
            $result = $_facrec->fetch($this->fac_rec);
531
            $result = $_facrec->fetchObjectLinked(null, '', null, '', 'OR', 1, 'sourcetype', 0); // This load $_facrec->linkedObjectsIds
532
533
            // Define some dates
534
            $originaldatewhen = $_facrec->date_when;
535
            $nextdatewhen = null;
536
            $previousdaynextdatewhen = null;
537
            if ($originaldatewhen) {
538
                $nextdatewhen = dol_time_plus_duree($originaldatewhen, $_facrec->frequency, $_facrec->unit_frequency);
539
                $previousdaynextdatewhen = dol_time_plus_duree($nextdatewhen, -1, 'd');
540
            }
541
542
            if (!empty($_facrec->frequency)) {  // Invoice are created on same thirdparty than template when there is a recurrence, but not necessarily when there is no recurrence.
543
                $this->socid = $_facrec->socid;
544
            }
545
            $this->entity            = $_facrec->entity; // Invoice created in same entity than template
546
547
            // Fields coming from GUI.
548
            // @TODO Value of template should be used as default value on the form on the GUI, and we should here always use the value from GUI
549
            // set by posted page with $object->xxx = ... and this section should be removed.
550
            $this->fk_project        = GETPOSTINT('projectid') > 0 ? GETPOSTINT('projectid') : $_facrec->fk_project;
551
            $this->note_public       = GETPOSTISSET('note_public') ? GETPOST('note_public', 'restricthtml') : $_facrec->note_public;
0 ignored issues
show
Documentation Bug introduced by
It seems like GETPOSTISSET('note_publi...: $_facrec->note_public can also be of type array or array or array. However, the property $note_public is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
552
            $this->note_private      = GETPOSTISSET('note_private') ? GETPOST('note_private', 'restricthtml') : $_facrec->note_private;
0 ignored issues
show
Documentation Bug introduced by
It seems like GETPOSTISSET('note_priva... $_facrec->note_private can also be of type array or array or array. However, the property $note_private is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
553
            $this->model_pdf = GETPOSTISSET('model') ? GETPOST('model', 'alpha') : $_facrec->model_pdf;
0 ignored issues
show
Documentation Bug introduced by
It seems like GETPOSTISSET('model') ? ...) : $_facrec->model_pdf can also be of type array or array or array. However, the property $model_pdf is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
554
            $this->cond_reglement_id = GETPOSTINT('cond_reglement_id') > 0 ? GETPOSTINT('cond_reglement_id') : $_facrec->cond_reglement_id;
555
            $this->mode_reglement_id = GETPOSTINT('mode_reglement_id') > 0 ? GETPOSTINT('mode_reglement_id') : $_facrec->mode_reglement_id;
556
            $this->fk_account        = GETPOST('fk_account') > 0 ? GETPOSTINT('fk_account') : $_facrec->fk_account;
557
558
            // Set here to have this defined for substitution into notes, should be recalculated after adding lines to get same result
559
            $this->total_ht          = $_facrec->total_ht;
560
            $this->total_ttc         = $_facrec->total_ttc;
561
562
            // Fields always coming from template
563
            //$this->remise_absolue    = $_facrec->remise_absolue;
564
            //$this->remise_percent    = $_facrec->remise_percent;  // TODO deprecated
565
            $this->fk_incoterms = $_facrec->fk_incoterms;
566
            $this->location_incoterms = $_facrec->location_incoterms;
567
568
            // Clean parameters
569
            if (!$this->type) {
570
                $this->type = self::TYPE_STANDARD;
571
            }
572
            $this->ref_client = trim($this->ref_client);
573
            $this->ref_customer = trim($this->ref_customer);
574
            $this->note_public = trim($this->note_public);
575
            $this->note_private = trim($this->note_private);
576
            $this->note_private = dol_concatdesc($this->note_private, $langs->trans("GeneratedFromRecurringInvoice", $_facrec->ref));
577
578
            $this->array_options = $_facrec->array_options;
579
580
            if (!$this->mode_reglement_id) {
581
                $this->mode_reglement_id = 0;
582
            }
583
            $this->status = self::STATUS_DRAFT;
584
            $this->statut = self::STATUS_DRAFT; // deprecated
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

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

584
            /** @scrutinizer ignore-deprecated */ $this->statut = self::STATUS_DRAFT; // deprecated

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...
585
586
            $this->linked_objects = $_facrec->linkedObjectsIds;
587
            // We do not add link to template invoice or next invoice will be linked to all generated invoices
588
            //$this->linked_objects['facturerec'][0] = $this->fac_rec;
589
590
            // For recurring invoices, update date and number of last generation of recurring template invoice, before inserting new invoice
591
            if ($_facrec->frequency > 0) {
592
                dol_syslog("This is a recurring invoice so we set date_last_gen and next date_when");
593
                if (empty($_facrec->date_when)) {
594
                    $_facrec->date_when = $now;
595
                }
596
                $next_date = $_facrec->getNextDate(); // Calculate next date
597
                $result = $_facrec->setValueFrom('date_last_gen', $now, '', null, 'date', '', $user, '');
598
                //$_facrec->setValueFrom('nb_gen_done', $_facrec->nb_gen_done + 1);     // Not required, +1 already included into setNextDate when second param is 1.
599
                $result = $_facrec->setNextDate($next_date, 1);
600
            }
601
602
            // Define lang of customer
603
            $outputlangs = $langs;
604
            $newlang = '';
605
606
            if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang) && isset($this->thirdparty->default_lang)) {
607
                $newlang = $this->thirdparty->default_lang; // for proposal, order, invoice, ...
608
            }
609
            if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang) && isset($this->default_lang)) {
0 ignored issues
show
Bug Best Practice introduced by
The property default_lang does not exist on Dolibarr\Code\Compta\Classes\Facture. Since you implemented __get, consider adding a @property annotation.
Loading history...
610
                $newlang = $this->default_lang; // for thirdparty
611
            }
612
            if (!empty($newlang)) {
613
                $outputlangs = new Translate("", $conf);
614
                $outputlangs->setDefaultLang($newlang);
615
            }
616
617
            // Array of possible substitutions (See also file mailing-send.php that should manage same substitutions)
618
            $substitutionarray = getCommonSubstitutionArray($outputlangs, 0, null, $this);
619
            $substitutionarray['__INVOICE_PREVIOUS_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%m');
620
            $substitutionarray['__INVOICE_MONTH__'] = dol_print_date($this->date, '%m');
621
            $substitutionarray['__INVOICE_NEXT_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%m');
622
            $substitutionarray['__INVOICE_PREVIOUS_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%B');
623
            $substitutionarray['__INVOICE_MONTH_TEXT__'] = dol_print_date($this->date, '%B');
624
            $substitutionarray['__INVOICE_NEXT_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%B');
625
            $substitutionarray['__INVOICE_PREVIOUS_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'y'), '%Y');
626
            $substitutionarray['__INVOICE_YEAR__'] = dol_print_date($this->date, '%Y');
627
            $substitutionarray['__INVOICE_NEXT_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'y'), '%Y');
628
            // Only for template invoice
629
            $substitutionarray['__INVOICE_DATE_NEXT_INVOICE_BEFORE_GEN__'] = (isset($originaldatewhen) ? dol_print_date($originaldatewhen, 'dayhour') : '');
630
            $substitutionarray['__INVOICE_DATE_NEXT_INVOICE_AFTER_GEN__'] = (isset($nextdatewhen) ? dol_print_date($nextdatewhen, 'dayhour') : '');
631
            $substitutionarray['__INVOICE_PREVIOUS_DATE_NEXT_INVOICE_AFTER_GEN__'] = (isset($previousdaynextdatewhen) ? dol_print_date($previousdaynextdatewhen, 'dayhour') : '');
632
            $substitutionarray['__INVOICE_COUNTER_CURRENT__'] = $_facrec->nb_gen_done;
633
            $substitutionarray['__INVOICE_COUNTER_MAX__'] = $_facrec->nb_gen_max;
634
635
            //var_dump($substitutionarray);exit;
636
637
            complete_substitutions_array($substitutionarray, $outputlangs);
638
639
            $this->note_public = make_substitutions($this->note_public, $substitutionarray);
640
            $this->note_private = make_substitutions($this->note_private, $substitutionarray);
641
        }
642
643
        // Define due date if not already defined
644
        if (empty($forceduedate)) {
645
            $duedate = $this->calculate_date_lim_reglement();
646
            /*if ($duedate < 0) {   Regression, a date can be negative if before 1970.
647
                dol_syslog(__METHOD__ . ' Error in calculate_date_lim_reglement. We got ' . $duedate, LOG_ERR);
648
                return -1;
649
            }*/
650
            $this->date_lim_reglement = $duedate;
0 ignored issues
show
Documentation Bug introduced by
It seems like $duedate can also be of type string. However, the property $date_lim_reglement is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
651
        } else {
652
            $this->date_lim_reglement = $forceduedate;
653
        }
654
655
        // Insert into database
656
        $socid = $this->socid;
657
658
        $sql = "INSERT INTO " . MAIN_DB_PREFIX . "facture (";
659
        $sql .= " ref";
660
        $sql .= ", entity";
661
        $sql .= ", ref_ext";
662
        $sql .= ", type";
663
        $sql .= ", subtype";
664
        $sql .= ", fk_soc";
665
        $sql .= ", datec";
666
        $sql .= ", datef";
667
        $sql .= ", date_pointoftax";
668
        $sql .= ", note_private";
669
        $sql .= ", note_public";
670
        $sql .= ", ref_client";
671
        $sql .= ", fk_account";
672
        $sql .= ", module_source, pos_source, fk_fac_rec_source, fk_facture_source, fk_user_author, fk_projet";
673
        $sql .= ", fk_cond_reglement, fk_mode_reglement, date_lim_reglement, model_pdf";
674
        $sql .= ", situation_cycle_ref, situation_counter, situation_final";
675
        $sql .= ", fk_incoterms, location_incoterms";
676
        $sql .= ", fk_multicurrency";
677
        $sql .= ", multicurrency_code";
678
        $sql .= ", multicurrency_tx";
679
        $sql .= ", retained_warranty";
680
        $sql .= ", retained_warranty_date_limit";
681
        $sql .= ", retained_warranty_fk_cond_reglement";
682
        $sql .= ")";
683
        $sql .= " VALUES (";
684
        $sql .= "'(PROV)'";
685
        $sql .= ", " . setEntity($this);
686
        $sql .= ", " . ($this->ref_ext ? "'" . $this->db->escape($this->ref_ext) . "'" : "null");
687
        $sql .= ", '" . $this->db->escape($this->type) . "'";
688
        $sql .= ", " . ($this->subtype ? "'" . $this->db->escape($this->subtype) . "'" : "null");
689
        $sql .= ", " . ((int) $socid);
690
        $sql .= ", '" . $this->db->idate($this->date_creation) . "'";
691
        $sql .= ", '" . $this->db->idate($this->date) . "'";
692
        $sql .= ", " . (empty($this->date_pointoftax) ? "null" : "'" . $this->db->idate($this->date_pointoftax) . "'");
693
        $sql .= ", " . ($this->note_private ? "'" . $this->db->escape($this->note_private) . "'" : "null");
694
        $sql .= ", " . ($this->note_public ? "'" . $this->db->escape($this->note_public) . "'" : "null");
695
        $sql .= ", " . ($this->ref_customer ? "'" . $this->db->escape($this->ref_customer) . "'" : ($this->ref_client ? "'" . $this->db->escape($this->ref_client) . "'" : "null"));
696
        $sql .= ", " . ($this->fk_account > 0 ? $this->fk_account : 'NULL');
697
        $sql .= ", " . ($this->module_source ? "'" . $this->db->escape($this->module_source) . "'" : "null");
698
        $sql .= ", " . ($this->pos_source != '' ? "'" . $this->db->escape($this->pos_source) . "'" : "null");
699
        $sql .= ", " . ($this->fk_fac_rec_source ? "'" . $this->db->escape($this->fk_fac_rec_source) . "'" : "null");
700
        $sql .= ", " . ($this->fk_facture_source ? "'" . $this->db->escape($this->fk_facture_source) . "'" : "null");
701
        $sql .= ", " . ($origin_user_author_id > 0 ? (int) $origin_user_author_id : "null");
702
        $sql .= ", " . ($this->fk_project ? (int) $this->fk_project : "null");
703
        $sql .= ", " . ((int) $this->cond_reglement_id);
704
        $sql .= ", " . ((int) $this->mode_reglement_id);
705
        $sql .= ", '" . $this->db->idate($this->date_lim_reglement) . "'";
706
        $sql .= ", " . (isset($this->model_pdf) ? "'" . $this->db->escape($this->model_pdf) . "'" : "null");
707
        $sql .= ", " . ($this->situation_cycle_ref ? "'" . $this->db->escape($this->situation_cycle_ref) . "'" : "null");
708
        $sql .= ", " . ($this->situation_counter ? "'" . $this->db->escape($this->situation_counter) . "'" : "null");
709
        $sql .= ", " . ($this->situation_final ? (int) $this->situation_final : 0);
710
        $sql .= ", " . (int) $this->fk_incoterms;
711
        $sql .= ", '" . $this->db->escape($this->location_incoterms) . "'";
712
        $sql .= ", " . (int) $this->fk_multicurrency;
713
        $sql .= ", '" . $this->db->escape($this->multicurrency_code) . "'";
714
        $sql .= ", " . (float) $this->multicurrency_tx;
715
        $sql .= ", " . (empty($this->retained_warranty) ? "0" : $this->db->escape($this->retained_warranty));
716
        $sql .= ", " . (!empty($this->retained_warranty_date_limit) ? "'" . $this->db->idate($this->retained_warranty_date_limit) . "'" : 'NULL');
717
        $sql .= ", " . (int) $this->retained_warranty_fk_cond_reglement;
718
        $sql .= ")";
719
720
        $resql = $this->db->query($sql);
721
        if ($resql) {
722
            $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX . 'facture');
723
724
            // Update ref with new one
725
            $this->ref = '(PROV' . $this->id . ')';
726
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . "facture SET ref='" . $this->db->escape($this->ref) . "' WHERE rowid=" . ((int) $this->id);
727
728
            $resql = $this->db->query($sql);
729
            if (!$resql) {
730
                $error++;
731
            }
732
733
            if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) {  // To use new linkedObjectsIds instead of old linked_objects
734
                $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
735
            }
736
737
            // Add object linked
738
            if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
739
                foreach ($this->linked_objects as $origin => $tmp_origin_id) {
740
                    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, ...))
741
                        foreach ($tmp_origin_id as $origin_id) {
742
                            $ret = $this->add_object_linked($origin, $origin_id);
743
                            if (!$ret) {
744
                                $this->error = $this->db->lasterror();
745
                                $error++;
746
                            }
747
                        }
748
                    } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
749
                        $origin_id = $tmp_origin_id;
750
                        $ret = $this->add_object_linked($origin, $origin_id);
751
                        if (!$ret) {
752
                            $this->error = $this->db->lasterror();
753
                            $error++;
754
                        }
755
                    }
756
                }
757
            }
758
759
            // Propagate contacts
760
            if (!$error && $this->id && getDolGlobalString('MAIN_PROPAGATE_CONTACTS_FROM_ORIGIN') && !empty($this->origin) && !empty($this->origin_id)) {   // Get contact from origin object
761
                $originforcontact = $this->origin;
762
                $originidforcontact = $this->origin_id;
763
                if ($originforcontact == 'shipping') {     // shipment and order share the same contacts. If creating from shipment we take data of order
764
                    $exp = new Expedition($this->db);
765
                    $exp->fetch($this->origin_id);
766
                    $exp->fetchObjectLinked(null, '', null, '', 'OR', 1, 'sourcetype', 0);
767
                    if (count($exp->linkedObjectsIds['commande']) > 0) {
768
                        foreach ($exp->linkedObjectsIds['commande'] as $key => $value) {
769
                            $originforcontact = 'commande';
770
                            if (is_object($value)) {
771
                                $originidforcontact = $value->id;
772
                            } else {
773
                                $originidforcontact = $value;
774
                            }
775
                            break; // We take first one
776
                        }
777
                    }
778
                }
779
780
                $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";
781
                $sqlcontact .= " WHERE element_id = " . ((int) $originidforcontact) . " AND ec.fk_c_type_contact = ctc.rowid AND ctc.element = '" . $this->db->escape($originforcontact) . "'";
782
783
                $resqlcontact = $this->db->query($sqlcontact);
784
                if ($resqlcontact) {
785
                    while ($objcontact = $this->db->fetch_object($resqlcontact)) {
786
                        //print $objcontact->code.'-'.$objcontact->source.'-'.$objcontact->fk_socpeople."\n";
787
                        $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
788
                    }
789
                } else {
790
                    dol_print_error($resqlcontact);
791
                }
792
            }
793
794
            /*
795
             *  Insert lines of invoices, if not from template invoice, into database
796
             */
797
            if (!$error && empty($this->fac_rec) && count($this->lines) && is_object($this->lines[0])) {    // If this->lines is array of InvoiceLines (preferred mode)
798
                $fk_parent_line = 0;
799
800
                dol_syslog("There is " . count($this->lines) . " lines into ->lines that are InvoiceLines");
801
                foreach ($this->lines as $i => $val) {
802
                    $newinvoiceline = $this->lines[$i];
803
804
                    $newinvoiceline->context = $this->context;
805
806
                    $newinvoiceline->fk_facture = $this->id;
0 ignored issues
show
Bug Best Practice introduced by
The property fk_facture does not exist on Dolibarr\Code\Core\Classes\CommonInvoiceLine. Since you implemented __set, consider adding a @property annotation.
Loading history...
807
808
                    $newinvoiceline->origin = $this->lines[$i]->element;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

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

808
                    /** @scrutinizer ignore-deprecated */ $newinvoiceline->origin = $this->lines[$i]->element;

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...
809
                    $newinvoiceline->origin_id = $this->lines[$i]->id;
810
811
                    // Auto set date of service ?
812
                    if ($this->lines[$i]->date_start_fill == 1 && $originaldatewhen) {      // $originaldatewhen is defined when generating from recurring invoice only
813
                        $newinvoiceline->date_start = $originaldatewhen;
0 ignored issues
show
Bug Best Practice introduced by
The property date_start does not exist on Dolibarr\Code\Core\Classes\CommonInvoiceLine. Since you implemented __set, consider adding a @property annotation.
Loading history...
814
                    }
815
                    if ($this->lines[$i]->date_end_fill == 1 && $previousdaynextdatewhen) { // $previousdaynextdatewhen is defined when generating from recurring invoice only
0 ignored issues
show
Bug Best Practice introduced by
The expression $previousdaynextdatewhen of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
816
                        $newinvoiceline->date_end = $previousdaynextdatewhen;
0 ignored issues
show
Bug Best Practice introduced by
The property date_end does not exist on Dolibarr\Code\Core\Classes\CommonInvoiceLine. Since you implemented __set, consider adding a @property annotation.
Loading history...
817
                    }
818
819
                    if ($result >= 0) {
820
                        // Reset fk_parent_line for no child products and special product
821
                        if (($newinvoiceline->product_type != 9 && empty($newinvoiceline->fk_parent_line)) || $newinvoiceline->product_type == 9) {
0 ignored issues
show
Bug Best Practice introduced by
The property fk_parent_line does not exist on Dolibarr\Code\Core\Classes\CommonInvoiceLine. Since you implemented __get, consider adding a @property annotation.
Loading history...
822
                            $fk_parent_line = 0;
823
                        }
824
825
                        // Complete vat rate with code
826
                        $vatrate = $newinvoiceline->tva_tx;
827
                        if ($newinvoiceline->vat_src_code && ! preg_match('/\(.*\)/', (string) $vatrate)) {
828
                            $vatrate .= ' (' . $newinvoiceline->vat_src_code . ')';
829
                        }
830
831
                        $newinvoiceline->fk_parent_line = $fk_parent_line;
0 ignored issues
show
Bug Best Practice introduced by
The property fk_parent_line does not exist on Dolibarr\Code\Core\Classes\CommonInvoiceLine. Since you implemented __set, consider adding a @property annotation.
Loading history...
832
833
                        if ($this->type === Facture::TYPE_REPLACEMENT && $newinvoiceline->fk_remise_except) {
0 ignored issues
show
Bug Best Practice introduced by
The property fk_remise_except does not exist on Dolibarr\Code\Core\Classes\CommonInvoiceLine. Since you implemented __get, consider adding a @property annotation.
Loading history...
834
                            $discount = new DiscountAbsolute($this->db);
835
                            $discount->fetch($newinvoiceline->fk_remise_except);
836
837
                            $discountId = $soc->set_remise_except($discount->amount_ht, $user, $discount->description, $discount->tva_tx);
838
                            $newinvoiceline->fk_remise_except = $discountId;
0 ignored issues
show
Bug Best Practice introduced by
The property fk_remise_except does not exist on Dolibarr\Code\Core\Classes\CommonInvoiceLine. Since you implemented __set, consider adding a @property annotation.
Loading history...
839
                        }
840
841
                        $result = $this->addline(
842
                            $newinvoiceline->desc,
843
                            $newinvoiceline->subprice,
844
                            $newinvoiceline->qty,
845
                            $vatrate,
846
                            $newinvoiceline->localtax1_tx,
847
                            $newinvoiceline->localtax2_tx,
848
                            $newinvoiceline->fk_product,
849
                            $newinvoiceline->remise_percent,
850
                            $newinvoiceline->date_start,
0 ignored issues
show
Bug Best Practice introduced by
The property date_start does not exist on Dolibarr\Code\Core\Classes\CommonInvoiceLine. Since you implemented __get, consider adding a @property annotation.
Loading history...
851
                            $newinvoiceline->date_end,
0 ignored issues
show
Bug Best Practice introduced by
The property date_end does not exist on Dolibarr\Code\Core\Classes\CommonInvoiceLine. Since you implemented __get, consider adding a @property annotation.
Loading history...
852
                            $newinvoiceline->fk_code_ventilation,
0 ignored issues
show
Bug Best Practice introduced by
The property fk_code_ventilation does not exist on Dolibarr\Code\Core\Classes\CommonInvoiceLine. Since you implemented __get, consider adding a @property annotation.
Loading history...
853
                            $newinvoiceline->info_bits,
854
                            $newinvoiceline->fk_remise_except,
855
                            'HT',
856
                            0,
857
                            $newinvoiceline->product_type,
858
                            $newinvoiceline->rang,
0 ignored issues
show
Bug Best Practice introduced by
The property rang does not exist on Dolibarr\Code\Core\Classes\CommonInvoiceLine. Since you implemented __get, consider adding a @property annotation.
Loading history...
859
                            $newinvoiceline->special_code,
860
                            $newinvoiceline->element,
861
                            $newinvoiceline->id,
862
                            $fk_parent_line,
863
                            $newinvoiceline->fk_fournprice,
0 ignored issues
show
Bug Best Practice introduced by
The property fk_fournprice does not exist on Dolibarr\Code\Core\Classes\CommonInvoiceLine. Since you implemented __get, consider adding a @property annotation.
Loading history...
864
                            $newinvoiceline->pa_ht,
865
                            $newinvoiceline->label,
866
                            $newinvoiceline->array_options,
867
                            $newinvoiceline->situation_percent,
0 ignored issues
show
Bug Best Practice introduced by
The property situation_percent does not exist on Dolibarr\Code\Core\Classes\CommonInvoiceLine. Since you implemented __get, consider adding a @property annotation.
Loading history...
868
                            $newinvoiceline->fk_prev_id,
0 ignored issues
show
Bug Best Practice introduced by
The property fk_prev_id does not exist on Dolibarr\Code\Core\Classes\CommonInvoiceLine. Since you implemented __get, consider adding a @property annotation.
Loading history...
869
                            $newinvoiceline->fk_unit,
870
                            $newinvoiceline->multicurrency_subprice,
871
                            $newinvoiceline->ref_ext,
872
                            1
873
                        );
874
875
                        // Defined the new fk_parent_line
876
                        if ($result > 0 && $newinvoiceline->product_type == 9) {
877
                            $fk_parent_line = $result;
878
                        }
879
                    }
880
                    if ($result < 0) {
881
                        $this->error = $newinvoiceline->error;
882
                        $this->errors = $newinvoiceline->errors;
883
                        $error++;
884
                        break;
885
                    }
886
                }
887
            } elseif (!$error && empty($this->fac_rec)) {       // If this->lines is an array of invoice line arrays
888
                $fk_parent_line = 0;
889
890
                dol_syslog("There is " . count($this->lines) . " lines into ->lines as a simple array");
891
892
                foreach ($this->lines as $i => $val) {
893
                    $line = $this->lines[$i];
894
895
                    // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
896
                    //if (! is_object($line)) $line=json_decode(json_encode($line), false);  // convert recursively array into object.
897
                    if (!is_object($line)) {
898
                        $line = (object) $line;
899
                    }
900
901
                    if ($result >= 0) {
902
                        // Reset fk_parent_line for no child products and special product
903
                        if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
904
                            $fk_parent_line = 0;
905
                        }
906
907
                        // Complete vat rate with code
908
                        $vatrate = $line->tva_tx;
909
                        if ($line->vat_src_code && !preg_match('/\(.*\)/', (string) $vatrate)) {
910
                            $vatrate .= ' (' . $line->vat_src_code . ')';
911
                        }
912
913
                        if (getDolGlobalString('MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION')) {
914
                            $originid = $line->origin_id;
915
                            $origintype = $line->origin;
916
                        } else {
917
                            $originid = $line->id;
918
                            $origintype = $this->element;
919
                        }
920
921
                        // init ref_ext
922
                        if (empty($line->ref_ext)) {
923
                            $line->ref_ext = '';
924
                        }
925
926
                        $result = $this->addline(
927
                            $line->desc,
928
                            $line->subprice,
929
                            $line->qty,
930
                            $vatrate,
931
                            $line->localtax1_tx,
932
                            $line->localtax2_tx,
933
                            $line->fk_product,
934
                            $line->remise_percent,
935
                            $line->date_start,
936
                            $line->date_end,
937
                            $line->fk_code_ventilation,
938
                            $line->info_bits,
939
                            $line->fk_remise_except,
940
                            'HT',
941
                            0,
942
                            $line->product_type,
943
                            $line->rang,
944
                            $line->special_code,
945
                            $origintype,
946
                            $originid,
947
                            $fk_parent_line,
948
                            $line->fk_fournprice,
949
                            $line->pa_ht,
950
                            $line->label,
951
                            $line->array_options,
952
                            $line->situation_percent,
953
                            $line->fk_prev_id,
954
                            $line->fk_unit,
955
                            $line->multicurrency_subprice,
956
                            $line->ref_ext,
957
                            1
958
                        );
959
                        if ($result < 0) {
960
                            $this->error = $this->db->lasterror();
961
                            dol_print_error($this->db);
962
                            $this->db->rollback();
963
                            return -1;
964
                        }
965
966
                        // Defined the new fk_parent_line
967
                        if ($result > 0 && $line->product_type == 9) {
968
                            $fk_parent_line = $result;
969
                        }
970
                    }
971
                }
972
            }
973
974
            /*
975
             * Insert lines coming from the template invoice
976
             */
977
            if (!$error && $this->fac_rec > 0) {
978
                dol_syslog("There is " . count($_facrec->lines) . " lines from recurring invoice");
979
                $fk_parent_line = 0;
980
981
                foreach ($_facrec->lines as $i => $val) {
982
                    if ($_facrec->lines[$i]->fk_product) {
983
                        $prod = new Product($this->db);
984
                        $res = $prod->fetch($_facrec->lines[$i]->fk_product);
985
                    }
986
987
                    // Reset fk_parent_line for no child products and special product
988
                    if (($_facrec->lines[$i]->product_type != 9 && empty($_facrec->lines[$i]->fk_parent_line)) || $_facrec->lines[$i]->product_type == 9) {
989
                        $fk_parent_line = 0;
990
                    }
991
992
                    // For line from template invoice, we use data from template invoice
993
                    /*
994
                    $tva_tx = get_default_tva($mysoc,$soc,$prod->id);
995
                    $tva_npr = get_default_npr($mysoc,$soc,$prod->id);
996
                    if (empty($tva_tx)) $tva_npr=0;
997
                    $localtax1_tx=get_localtax($tva_tx,1,$soc,$mysoc,$tva_npr);
998
                    $localtax2_tx=get_localtax($tva_tx,2,$soc,$mysoc,$tva_npr);
999
                    */
1000
                    $tva_tx = $_facrec->lines[$i]->tva_tx . ($_facrec->lines[$i]->vat_src_code ? '(' . $_facrec->lines[$i]->vat_src_code . ')' : '');
1001
                    $tva_npr = $_facrec->lines[$i]->info_bits;
1002
                    if (empty($tva_tx)) {
1003
                        $tva_npr = 0;
1004
                    }
1005
                    $localtax1_tx = $_facrec->lines[$i]->localtax1_tx;
1006
                    $localtax2_tx = $_facrec->lines[$i]->localtax2_tx;
1007
1008
                    $fk_product_fournisseur_price = empty($_facrec->lines[$i]->fk_product_fournisseur_price) ? null : $_facrec->lines[$i]->fk_product_fournisseur_price;
1009
                    $buyprice = empty($_facrec->lines[$i]->buyprice) ? 0 : $_facrec->lines[$i]->buyprice;
1010
1011
                    // If buyprice not defined from template invoice, we try to guess the best value
1012
                    if (!$buyprice && $_facrec->lines[$i]->fk_product > 0) {
1013
                        $producttmp = new ProductFournisseur($this->db);
1014
                        $producttmp->fetch($_facrec->lines[$i]->fk_product);
1015
1016
                        // If margin module defined on costprice, we try the costprice
1017
                        // If not defined or if module margin defined and pmp and stock module enabled, we try pmp price
1018
                        // else we get the best supplier price
1019
                        if (getDolGlobalString('MARGIN_TYPE') == 'costprice' && !empty($producttmp->cost_price)) {
1020
                            $buyprice = $producttmp->cost_price;
1021
                        } elseif (isModEnabled('stock') && (getDolGlobalString('MARGIN_TYPE') == 'costprice' || getDolGlobalString('MARGIN_TYPE') == 'pmp') && !empty($producttmp->pmp)) {
1022
                            $buyprice = $producttmp->pmp;
1023
                        } else {
1024
                            if ($producttmp->find_min_price_product_fournisseur($_facrec->lines[$i]->fk_product) > 0) {
1025
                                if ($producttmp->product_fourn_price_id > 0) {
1026
                                    $buyprice = price2num($producttmp->fourn_unitprice * (1 - $producttmp->fourn_remise_percent / 100) + $producttmp->fourn_remise, 'MU');
1027
                                }
1028
                            }
1029
                        }
1030
                    }
1031
1032
                    $result_insert = $this->addline(
1033
                        $_facrec->lines[$i]->desc,
1034
                        $_facrec->lines[$i]->subprice,
1035
                        $_facrec->lines[$i]->qty,
1036
                        $tva_tx,
1037
                        $localtax1_tx,
1038
                        $localtax2_tx,
1039
                        $_facrec->lines[$i]->fk_product,
1040
                        $_facrec->lines[$i]->remise_percent,
1041
                        ($_facrec->lines[$i]->date_start_fill == 1 && $originaldatewhen) ? $originaldatewhen : '',
1042
                        ($_facrec->lines[$i]->date_end_fill == 1 && $previousdaynextdatewhen) ? $previousdaynextdatewhen : '',
0 ignored issues
show
Bug Best Practice introduced by
The expression $previousdaynextdatewhen of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1043
                        0,
1044
                        $tva_npr,
1045
                        '',
1046
                        'HT',
1047
                        0,
1048
                        $_facrec->lines[$i]->product_type,
1049
                        $_facrec->lines[$i]->rang,
1050
                        $_facrec->lines[$i]->special_code,
1051
                        '',
1052
                        0,
1053
                        $fk_parent_line,
1054
                        $fk_product_fournisseur_price,
1055
                        $buyprice,
1056
                        $_facrec->lines[$i]->label,
1057
                        empty($_facrec->lines[$i]->array_options) ? null : $_facrec->lines[$i]->array_options,
1058
                        100,    // situation percent is undefined on recurring invoice lines
1059
                        '',
1060
                        $_facrec->lines[$i]->fk_unit,
1061
                        $_facrec->lines[$i]->multicurrency_subprice,
1062
                        $_facrec->lines[$i]->ref_ext,
1063
                        1
1064
                    );
1065
1066
                    // Defined the new fk_parent_line
1067
                    if ($result_insert > 0 && $_facrec->lines[$i]->product_type == 9) {
1068
                        $fk_parent_line = $result_insert;
1069
                    }
1070
1071
                    if ($result_insert < 0) {
1072
                        $error++;
1073
                        $this->error = $this->db->error();
1074
                        break;
1075
                    }
1076
                }
1077
            }
1078
1079
            if (!$error) {
1080
                $result = $this->update_price(1, 'auto', 0, $mysoc);
1081
                if ($result > 0) {
1082
                    $action = 'create';
1083
1084
                    // Actions on extra fields
1085
                    if (!$error) {
1086
                        $result = $this->insertExtraFields();
1087
                        if ($result < 0) {
1088
                            $error++;
1089
                        }
1090
                    }
1091
1092
                    if (!$error && !$notrigger) {
1093
                        // Call trigger
1094
                        $result = $this->call_trigger('BILL_CREATE', $user);
1095
                        if ($result < 0) {
1096
                            $error++;
1097
                        }
1098
                    }
1099
1100
                    if (!$error) {
1101
                        $this->db->commit();
1102
                        return $this->id;
1103
                    } else {
1104
                        $this->db->rollback();
1105
                        return -4;
1106
                    }
1107
                } else {
1108
                    $this->error = $langs->trans('FailedToUpdatePrice');
1109
                    $this->db->rollback();
1110
                    return -3;
1111
                }
1112
            } else {
1113
                dol_syslog(get_only_class($this) . "::create error " . $this->error, LOG_ERR);
1114
                $this->db->rollback();
1115
                return -2;
1116
            }
1117
        } else {
1118
            $this->error = $this->db->error();
1119
            $this->db->rollback();
1120
            return -1;
1121
        }
1122
    }
1123
1124
1125
    /**
1126
     *  Create a new invoice in database from current invoice
1127
     *
1128
     *  @param      User    $user           Object user that ask creation
1129
     *  @param      int     $invertdetail   Reverse sign of amounts for lines
1130
     *  @return     int                     Return integer <0 if KO, >0 if OK
1131
     */
1132
    public function createFromCurrent(User $user, $invertdetail = 0)
1133
    {
1134
        // Source invoice load
1135
        $facture = new Facture($this->db);
1136
1137
        // Retrieve all extrafield
1138
        // fetch optionals attributes and labels
1139
        $this->fetch_optionals();
1140
1141
        if (!empty($this->array_options)) {
1142
            $facture->array_options = $this->array_options;
1143
        }
1144
1145
        foreach ($this->lines as &$line) {
1146
            $line->fetch_optionals(); //fetch extrafields
1147
        }
1148
1149
        $facture->fk_facture_source = $this->fk_facture_source;
1150
        $facture->type              = $this->type;
1151
        $facture->subtype           = $this->subtype;
1152
        $facture->socid             = $this->socid;
1153
        $facture->date              = $this->date;
1154
        $facture->date_pointoftax   = $this->date_pointoftax;
1155
        $facture->note_public       = $this->note_public;
1156
        $facture->note_private      = $this->note_private;
1157
        $facture->ref_client        = $this->ref_client;
1158
        $facture->ref_customer      = $this->ref_customer;
1159
        $facture->model_pdf         = $this->model_pdf;
1160
        $facture->fk_project        = $this->fk_project;
1161
        $facture->cond_reglement_id = $this->cond_reglement_id;
1162
        $facture->mode_reglement_id = $this->mode_reglement_id;
1163
        //$facture->remise_absolue    = $this->remise_absolue;
1164
        //$facture->remise_percent    = $this->remise_percent;  // TODO deprecated
1165
1166
        $facture->origin            = $this->origin;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

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

1166
        $facture->origin            = /** @scrutinizer ignore-deprecated */ $this->origin;

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...
1167
        $facture->origin_id         = $this->origin_id;
1168
        $facture->fk_account         = $this->fk_account;
1169
1170
        $facture->lines = $this->lines; // Array of lines of invoice
1171
        $facture->situation_counter = $this->situation_counter;
1172
        $facture->situation_cycle_ref = $this->situation_cycle_ref;
1173
        $facture->situation_final = $this->situation_final;
1174
1175
        $facture->retained_warranty = $this->retained_warranty;
1176
        $facture->retained_warranty_fk_cond_reglement = $this->retained_warranty_fk_cond_reglement;
1177
        $facture->retained_warranty_date_limit = $this->retained_warranty_date_limit;
1178
1179
        $facture->fk_user_author = $user->id;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Compta\Cla...acture::$fk_user_author has been deprecated: Use $user_creation_id ( Ignorable by Annotation )

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

1179
        /** @scrutinizer ignore-deprecated */ $facture->fk_user_author = $user->id;

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...
1180
        $facture->user_creation_id = $user->id;
1181
1182
1183
        // Loop on each line of new invoice
1184
        foreach ($facture->lines as $i => $tmpline) {
1185
            $facture->lines[$i]->fk_prev_id = $this->lines[$i]->rowid;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\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

1185
            $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...
Bug Best Practice introduced by
The property fk_prev_id does not exist on Dolibarr\Code\Core\Classes\CommonInvoiceLine. Since you implemented __set, consider adding a @property annotation.
Loading history...
1186
            if ($invertdetail) {
1187
                $facture->lines[$i]->subprice  = -$facture->lines[$i]->subprice;
1188
                $facture->lines[$i]->total_ht  = -$facture->lines[$i]->total_ht;
1189
                $facture->lines[$i]->total_tva = -$facture->lines[$i]->total_tva;
1190
                $facture->lines[$i]->total_localtax1 = -$facture->lines[$i]->total_localtax1;
1191
                $facture->lines[$i]->total_localtax2 = -$facture->lines[$i]->total_localtax2;
1192
                $facture->lines[$i]->total_ttc = -$facture->lines[$i]->total_ttc;
1193
                $facture->lines[$i]->ref_ext = '';
1194
            }
1195
        }
1196
1197
        dol_syslog(get_only_class($this) . "::createFromCurrent invertdetail=" . $invertdetail . " socid=" . $this->socid . " nboflines=" . count($facture->lines));
1198
1199
        $facid = $facture->create($user);
1200
        if ($facid <= 0) {
1201
            $this->error = $facture->error;
1202
            $this->errors = $facture->errors;
1203
        } elseif ($this->type == self::TYPE_SITUATION && getDolGlobalString('INVOICE_USE_SITUATION')) {
1204
            $this->fetchObjectLinked('', '', $this->id, 'facture');
1205
1206
            foreach ($this->linkedObjectsIds as $typeObject => $Tfk_object) {
1207
                foreach ($Tfk_object as $fk_object) {
1208
                    $facture->add_object_linked($typeObject, $fk_object);
1209
                }
1210
            }
1211
1212
            $facture->add_object_linked('facture', $this->fk_facture_source);
1213
        }
1214
1215
        return $facid;
1216
    }
1217
1218
1219
    /**
1220
     *  Load an object from its id and create a new one in database
1221
     *
1222
     *  @param      User    $user           User that clone
1223
     *  @param      int     $fromid         Id of object to clone
1224
     *  @return     int                     New id of clone
1225
     */
1226
    public function createFromClone(User $user, $fromid = 0)
1227
    {
1228
        global $conf, $hookmanager;
1229
1230
        $error = 0;
1231
1232
        $object = new Facture($this->db);
1233
1234
        $this->db->begin();
1235
1236
        $object->fetch($fromid);
1237
1238
        // Load source object
1239
        $objFrom = clone $object;
1240
1241
        // Change socid if needed
1242
        if (!empty($this->socid) && $this->socid != $object->socid) {
1243
            $objsoc = new Societe($this->db);
1244
1245
            if ($objsoc->fetch($this->socid) > 0) {
1246
                $object->socid = $objsoc->id;
1247
                $object->cond_reglement_id  = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1248
                $object->mode_reglement_id  = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1249
                $object->fk_project = 0;
1250
                $object->fk_delivery_address = 0;
1251
            }
1252
1253
            // TODO Change product price if multi-prices
1254
        }
1255
1256
        $object->id = 0;
1257
        $object->statut = self::STATUS_DRAFT;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

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

1257
        /** @scrutinizer ignore-deprecated */ $object->statut = self::STATUS_DRAFT;

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...
1258
        $object->status = self::STATUS_DRAFT;
1259
1260
        // Clear fields
1261
        $object->date               = (empty($this->date) ? dol_now() : $this->date);
1262
        $object->user_creation_id   = $user->id;
1263
        $object->user_validation_id = null;
1264
        $object->fk_user_author     = $user->id;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Compta\Cla...acture::$fk_user_author has been deprecated: Use $user_creation_id ( Ignorable by Annotation )

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

1264
        /** @scrutinizer ignore-deprecated */ $object->fk_user_author     = $user->id;

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...
1265
        $object->fk_user_valid      = null;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Compta\Cla...Facture::$fk_user_valid has been deprecated: Use $user_validation_id ( Ignorable by Annotation )

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

1265
        /** @scrutinizer ignore-deprecated */ $object->fk_user_valid      = null;

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...
1266
        $object->fk_facture_source  = 0;
1267
        $object->date_creation      = '';
1268
        $object->date_modification = '';
1269
        $object->date_validation    = '';
1270
        $object->ref_client         = '';
1271
        $object->close_code         = '';
1272
        $object->close_note         = '';
1273
        if (getDolGlobalInt('MAIN_DONT_KEEP_NOTE_ON_CLONING') == 1) {
1274
            $object->note_private = '';
1275
            $object->note_public = '';
1276
        }
1277
1278
        // Loop on each line of new invoice
1279
        foreach ($object->lines as $i => $line) {
1280
            if (($object->lines[$i]->info_bits & 0x02) == 0x02) {   // We do not clone line of discounts
1281
                unset($object->lines[$i]);
1282
                continue;
1283
            }
1284
1285
            // Block to update dates of service (month by month only if previously filled and similar to start and end of month)
1286
            // If it's a service with start and end dates
1287
            if (getDolGlobalString('INVOICE_AUTO_NEXT_MONTH_ON_LINES') && !empty($line->date_start) && !empty($line->date_end)) {
0 ignored issues
show
Bug Best Practice introduced by
The property date_end does not exist on Dolibarr\Code\Core\Classes\CommonInvoiceLine. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property date_start does not exist on Dolibarr\Code\Core\Classes\CommonInvoiceLine. Since you implemented __get, consider adding a @property annotation.
Loading history...
1288
                // Get the dates
1289
                $start = dol_getdate($line->date_start);
1290
                $end = dol_getdate($line->date_end);
1291
1292
                // Get the first and last day of the month
1293
                $first = dol_get_first_day($start['year'], $start['mon']);
1294
                $last = dol_get_last_day($end['year'], $end['mon']);
1295
1296
                //print dol_print_date(dol_mktime(0, 0, 0, $start['mon'], $start['mday'], $start['year'], 'gmt'), 'dayhour').' '.dol_print_date($first, 'dayhour').'<br>';
1297
                //print dol_mktime(23, 59, 59, $end['mon'], $end['mday'], $end['year'], 'gmt').' '.$last.'<br>';exit;
1298
                // If start date is first date of month and end date is last date of month
1299
                if (
1300
                    dol_mktime(0, 0, 0, $start['mon'], $start['mday'], $start['year'], 'gmt') == $first
1301
                    && dol_mktime(23, 59, 59, $end['mon'], $end['mday'], $end['year'], 'gmt') == $last
1302
                ) {
1303
                    $nextMonth = dol_get_next_month($end['mon'], $end['year']);
1304
                    $newFirst = dol_get_first_day($nextMonth['year'], $nextMonth['month']);
1305
                    $newLast = dol_get_last_day($nextMonth['year'], $nextMonth['month']);
1306
                    $object->lines[$i]->date_start = $newFirst;
0 ignored issues
show
Bug Best Practice introduced by
The property date_start does not exist on Dolibarr\Code\Core\Classes\CommonInvoiceLine. Since you implemented __set, consider adding a @property annotation.
Loading history...
1307
                    $object->lines[$i]->date_end = $newLast;
0 ignored issues
show
Bug Best Practice introduced by
The property date_end does not exist on Dolibarr\Code\Core\Classes\CommonInvoiceLine. Since you implemented __set, consider adding a @property annotation.
Loading history...
1308
                }
1309
            }
1310
1311
            $object->lines[$i]->ref_ext = ''; // Do not clone ref_ext
1312
        }
1313
1314
        // Create clone
1315
        $object->context['createfromclone'] = 'createfromclone';
1316
        $result = $object->create($user);
1317
        if ($result < 0) {
1318
            $error++;
1319
            $this->error = $object->error;
1320
            $this->errors = $object->errors;
1321
        } else {
1322
            // copy internal contacts
1323
            if ($object->copy_linked_contact($objFrom, 'internal') < 0) {
1324
                $error++;
1325
                $this->error = $object->error;
1326
                $this->errors = $object->errors;
1327
            } elseif ($object->socid == $objFrom->socid) {
1328
                // copy external contacts if same company
1329
                if ($object->copy_linked_contact($objFrom, 'external') < 0) {
1330
                    $error++;
1331
                    $this->error = $object->error;
1332
                    $this->errors = $object->errors;
1333
                }
1334
            }
1335
        }
1336
1337
        if (!$error) {
1338
            // Hook of thirdparty module
1339
            if (is_object($hookmanager)) {
1340
                $parameters = array('objFrom' => $objFrom);
1341
                $action = '';
1342
                $reshook = $hookmanager->executeHooks('createFrom', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
1343
                if ($reshook < 0) {
1344
                    $this->setErrorsFromObject($hookmanager);
1345
                    $error++;
1346
                }
1347
            }
1348
        }
1349
1350
        unset($object->context['createfromclone']);
1351
1352
        // End
1353
        if (!$error) {
1354
            $this->db->commit();
1355
            return $object->id;
1356
        } else {
1357
            $this->db->rollback();
1358
            return -1;
1359
        }
1360
    }
1361
1362
    /**
1363
     *  Load an object from an order and create a new invoice into database
1364
     *
1365
     *  @param      Object          $object             Object source
1366
     *  @param      User            $user               Object user
1367
     *  @return     int                                 Return integer <0 if KO, 0 if nothing done, 1 if OK
1368
     */
1369
    public function createFromOrder($object, User $user)
1370
    {
1371
        global $conf, $hookmanager;
1372
1373
        $error = 0;
1374
1375
        // Closed order
1376
        $this->date = dol_now();
1377
        $this->source = 0;
1378
1379
        $num = count($object->lines);
1380
        for ($i = 0; $i < $num; $i++) {
1381
            $line = new FactureLigne($this->db);
1382
1383
            $line->libelle          = $object->lines[$i]->libelle; // deprecated
1384
            $line->label            = $object->lines[$i]->label;
1385
            $line->desc             = $object->lines[$i]->desc;
1386
            $line->subprice         = $object->lines[$i]->subprice;
1387
            $line->total_ht         = $object->lines[$i]->total_ht;
1388
            $line->total_tva        = $object->lines[$i]->total_tva;
1389
            $line->total_localtax1  = $object->lines[$i]->total_localtax1;
1390
            $line->total_localtax2  = $object->lines[$i]->total_localtax2;
1391
            $line->total_ttc        = $object->lines[$i]->total_ttc;
1392
            $line->vat_src_code = $object->lines[$i]->vat_src_code;
1393
            $line->tva_tx = $object->lines[$i]->tva_tx;
1394
            $line->localtax1_tx     = $object->lines[$i]->localtax1_tx;
1395
            $line->localtax2_tx     = $object->lines[$i]->localtax2_tx;
1396
            $line->qty = $object->lines[$i]->qty;
1397
            $line->fk_remise_except = $object->lines[$i]->fk_remise_except;
1398
            $line->remise_percent = $object->lines[$i]->remise_percent;
1399
            $line->fk_product = $object->lines[$i]->fk_product;
1400
            $line->info_bits = $object->lines[$i]->info_bits;
1401
            $line->product_type     = $object->lines[$i]->product_type;
1402
            $line->rang = $object->lines[$i]->rang;
1403
            $line->special_code     = $object->lines[$i]->special_code;
1404
            $line->fk_parent_line = $object->lines[$i]->fk_parent_line;
1405
            $line->fk_unit = $object->lines[$i]->fk_unit;
1406
            $line->date_start = $object->lines[$i]->date_start;
1407
            $line->date_end = $object->lines[$i]->date_end;
1408
1409
            // Multicurrency
1410
            $line->fk_multicurrency = $object->lines[$i]->fk_multicurrency;
1411
            $line->multicurrency_code = $object->lines[$i]->multicurrency_code;
1412
            $line->multicurrency_subprice = $object->lines[$i]->multicurrency_subprice;
1413
            $line->multicurrency_total_ht = $object->lines[$i]->multicurrency_total_ht;
1414
            $line->multicurrency_total_tva = $object->lines[$i]->multicurrency_total_tva;
1415
            $line->multicurrency_total_ttc = $object->lines[$i]->multicurrency_total_ttc;
1416
1417
            $line->fk_fournprice = $object->lines[$i]->fk_fournprice;
1418
            $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);
1419
            $line->pa_ht            = $marginInfos[0];
1420
1421
            // get extrafields from original line
1422
            $object->lines[$i]->fetch_optionals();
1423
            foreach ($object->lines[$i]->array_options as $options_key => $value) {
1424
                $line->array_options[$options_key] = $value;
1425
            }
1426
1427
            $this->lines[$i] = $line;
1428
        }
1429
1430
        $this->socid                = $object->socid;
1431
        $this->fk_project           = $object->fk_project;
1432
        $this->fk_account = $object->fk_account;
1433
        $this->cond_reglement_id    = $object->cond_reglement_id;
1434
        $this->mode_reglement_id    = $object->mode_reglement_id;
1435
        $this->availability_id      = $object->availability_id;
1436
        $this->demand_reason_id     = $object->demand_reason_id;
1437
        $this->delivery_date        = $object->delivery_date;
1438
        $this->fk_delivery_address  = $object->fk_delivery_address; // deprecated
1439
        $this->contact_id           = $object->contact_id;
1440
        $this->ref_client           = $object->ref_client;
1441
1442
        if (!getDolGlobalString('MAIN_DISABLE_PROPAGATE_NOTES_FROM_ORIGIN')) {
1443
            $this->note_private = $object->note_private;
1444
            $this->note_public = $object->note_public;
1445
        }
1446
1447
        $this->module_source = $object->module_source;
1448
        $this->pos_source = $object->pos_source;
1449
1450
        $this->origin = $object->element;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

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

1450
        /** @scrutinizer ignore-deprecated */ $this->origin = $object->element;

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...
1451
        $this->origin_id = $object->id;
1452
1453
        $this->fk_user_author = $user->id;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Compta\Cla...acture::$fk_user_author has been deprecated: Use $user_creation_id ( Ignorable by Annotation )

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

1453
        /** @scrutinizer ignore-deprecated */ $this->fk_user_author = $user->id;

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...
1454
1455
        // get extrafields from original line
1456
        $object->fetch_optionals();
1457
        foreach ($object->array_options as $options_key => $value) {
1458
            $this->array_options[$options_key] = $value;
1459
        }
1460
1461
        // Possibility to add external linked objects with hooks
1462
        $this->linked_objects[$this->origin] = $this->origin_id;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

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

1462
        $this->linked_objects[/** @scrutinizer ignore-deprecated */ $this->origin] = $this->origin_id;

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...
1463
        if (!empty($object->other_linked_objects) && is_array($object->other_linked_objects)) {
1464
            $this->linked_objects = array_merge($this->linked_objects, $object->other_linked_objects);
1465
        }
1466
1467
        $ret = $this->create($user);
1468
1469
        if ($ret > 0) {
1470
            // Actions hooked (by external module)
1471
            $hookmanager->initHooks(array('invoicedao'));
1472
1473
            $parameters = array('objFrom' => $object);
1474
            $action = '';
1475
            $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1476
            if ($reshook < 0) {
1477
                $this->setErrorsFromObject($hookmanager);
1478
                $error++;
1479
            }
1480
1481
            if (!$error) {
1482
                return 1;
1483
            } else {
1484
                return -1;
1485
            }
1486
        } else {
1487
            return -1;
1488
        }
1489
    }
1490
1491
    /**
1492
     *  Load an object from an order and create a new invoice into database
1493
     *
1494
     *  @param      Object          $object             Object source
1495
     *  @param      User            $user               Object user
1496
     *  @param      array           $lines              Ids of lines to use for invoice. If empty, all lines will be used.
1497
     *  @return     int                                 Return integer <0 if KO, 0 if nothing done, 1 if OK
1498
     */
1499
    public function createFromContract($object, User $user, $lines = array())
1500
    {
1501
        global $conf, $hookmanager;
1502
1503
        $error = 0;
1504
1505
        // Closed order
1506
        $this->date = dol_now();
1507
        $this->source = 0;
1508
1509
        $use_all_lines = empty($lines);
1510
        $num = count($object->lines);
1511
        for ($i = 0; $i < $num; $i++) {
1512
            if (!$use_all_lines && !in_array($object->lines[$i]->id, $lines)) {
1513
                continue;
1514
            }
1515
1516
            $line = new FactureLigne($this->db);
1517
1518
            $line->libelle = $object->lines[$i]->libelle; // deprecated
1519
            $line->label            = $object->lines[$i]->label;
1520
            $line->desc             = $object->lines[$i]->desc;
1521
            $line->subprice         = $object->lines[$i]->subprice;
1522
            $line->total_ht         = $object->lines[$i]->total_ht;
1523
            $line->total_tva        = $object->lines[$i]->total_tva;
1524
            $line->total_localtax1  = $object->lines[$i]->total_localtax1;
1525
            $line->total_localtax2  = $object->lines[$i]->total_localtax2;
1526
            $line->total_ttc        = $object->lines[$i]->total_ttc;
1527
            $line->vat_src_code = $object->lines[$i]->vat_src_code;
1528
            $line->tva_tx = $object->lines[$i]->tva_tx;
1529
            $line->localtax1_tx     = $object->lines[$i]->localtax1_tx;
1530
            $line->localtax2_tx     = $object->lines[$i]->localtax2_tx;
1531
            $line->qty = $object->lines[$i]->qty;
1532
            $line->fk_remise_except = $object->lines[$i]->fk_remise_except;
1533
            $line->remise_percent = $object->lines[$i]->remise_percent;
1534
            $line->fk_product = $object->lines[$i]->fk_product;
1535
            $line->info_bits = $object->lines[$i]->info_bits;
1536
            $line->product_type     = $object->lines[$i]->product_type;
1537
            $line->rang = $object->lines[$i]->rang;
1538
            $line->special_code     = $object->lines[$i]->special_code;
1539
            $line->fk_parent_line = $object->lines[$i]->fk_parent_line;
1540
            $line->fk_unit = $object->lines[$i]->fk_unit;
1541
            $line->date_start = $object->lines[$i]->date_start;
1542
            $line->date_end = $object->lines[$i]->date_end;
1543
1544
            // Multicurrency
1545
            $line->fk_multicurrency = $object->lines[$i]->fk_multicurrency;
1546
            $line->multicurrency_code = $object->lines[$i]->multicurrency_code;
1547
            $line->multicurrency_subprice = $object->lines[$i]->multicurrency_subprice;
1548
            $line->multicurrency_total_ht = $object->lines[$i]->multicurrency_total_ht;
1549
            $line->multicurrency_total_tva = $object->lines[$i]->multicurrency_total_tva;
1550
            $line->multicurrency_total_ttc = $object->lines[$i]->multicurrency_total_ttc;
1551
1552
            $line->fk_fournprice = $object->lines[$i]->fk_fournprice;
1553
            $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);
1554
            $line->pa_ht            = $marginInfos[0];
1555
1556
            // get extrafields from original line
1557
            $object->lines[$i]->fetch_optionals();
1558
            foreach ($object->lines[$i]->array_options as $options_key => $value) {
1559
                $line->array_options[$options_key] = $value;
1560
            }
1561
1562
            $this->lines[$i] = $line;
1563
        }
1564
1565
        $this->socid                = $object->socid;
1566
        $this->fk_project           = $object->fk_project;
1567
        $this->fk_account = $object->fk_account;
1568
        $this->cond_reglement_id    = $object->cond_reglement_id;
1569
        $this->mode_reglement_id    = $object->mode_reglement_id;
1570
        $this->availability_id      = $object->availability_id;
1571
        $this->demand_reason_id     = $object->demand_reason_id;
1572
        $this->delivery_date        = $object->delivery_date;
1573
        $this->fk_delivery_address  = $object->fk_delivery_address; // deprecated
1574
        $this->contact_id           = $object->contact_id;
1575
        $this->ref_client           = $object->ref_client;
1576
1577
        if (!getDolGlobalString('MAIN_DISABLE_PROPAGATE_NOTES_FROM_ORIGIN')) {
1578
            $this->note_private = $object->note_private;
1579
            $this->note_public = $object->note_public;
1580
        }
1581
1582
        $this->module_source = $object->module_source;
1583
        $this->pos_source = $object->pos_source;
1584
1585
        $this->origin = $object->element;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

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

1585
        /** @scrutinizer ignore-deprecated */ $this->origin = $object->element;

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...
1586
        $this->origin_id = $object->id;
1587
1588
        $this->fk_user_author = $user->id;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Compta\Cla...acture::$fk_user_author has been deprecated: Use $user_creation_id ( Ignorable by Annotation )

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

1588
        /** @scrutinizer ignore-deprecated */ $this->fk_user_author = $user->id;

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...
1589
1590
        // get extrafields from original line
1591
        $object->fetch_optionals();
1592
        foreach ($object->array_options as $options_key => $value) {
1593
            $this->array_options[$options_key] = $value;
1594
        }
1595
1596
        // Possibility to add external linked objects with hooks
1597
        $this->linked_objects[$this->origin] = $this->origin_id;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

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

1597
        $this->linked_objects[/** @scrutinizer ignore-deprecated */ $this->origin] = $this->origin_id;

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...
1598
        if (!empty($object->other_linked_objects) && is_array($object->other_linked_objects)) {
1599
            $this->linked_objects = array_merge($this->linked_objects, $object->other_linked_objects);
1600
        }
1601
1602
        $ret = $this->create($user);
1603
1604
        if ($ret > 0) {
1605
            // Actions hooked (by external module)
1606
            $hookmanager->initHooks(array('invoicedao'));
1607
1608
            $parameters = array('objFrom' => $object);
1609
            $action = '';
1610
            $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1611
            if ($reshook < 0) {
1612
                $this->setErrorsFromObject($hookmanager);
1613
                $error++;
1614
            }
1615
1616
            if (!$error) {
1617
                return 1;
1618
            } else {
1619
                return -1;
1620
            }
1621
        } else {
1622
            return -1;
1623
        }
1624
    }
1625
1626
    /**
1627
     * Creates a deposit from a proposal or an order by grouping lines by VAT rates
1628
     *
1629
     * @param   Propal|Commande     $origin                 The original proposal or order
1630
     * @param   int                 $date                   Invoice date
1631
     * @param   int                 $payment_terms_id       Invoice payment terms
1632
     * @param   User                $user                   Object user
1633
     * @param   int                 $notrigger              1=Does not execute triggers, 0= execute triggers
1634
     * @param   bool                $autoValidateDeposit    Whether to aumatically validate the deposit created
1635
     * @param   array               $overrideFields         Array of fields to force values
1636
     * @return  Facture|null                                The deposit created, or null if error (populates $origin->error in this case)
1637
     */
1638
    public static function createDepositFromOrigin(CommonObject $origin, $date, $payment_terms_id, User $user, $notrigger = 0, $autoValidateDeposit = false, $overrideFields = array())
1639
    {
1640
        global $conf, $langs, $hookmanager, $action;
1641
1642
        if (! in_array($origin->element, array('propal', 'commande'))) {
1643
            $origin->error = 'ErrorCanOnlyAutomaticallyGenerateADepositFromProposalOrOrder';
1644
            return null;
1645
        }
1646
1647
        if (empty($date)) {
1648
            $origin->error = $langs->trans('ErrorFieldRequired', $langs->transnoentities('DateInvoice'));
1649
            return null;
1650
        }
1651
1652
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/date.lib.php';
1653
1654
        if ($date > (dol_get_last_hour(dol_now('tzuserrel')) + getDolGlobalInt('INVOICE_MAX_FUTURE_DELAY'))) {
1655
            $origin->error = 'ErrorDateIsInFuture';
1656
            return null;
1657
        }
1658
1659
        if ($payment_terms_id <= 0) {
1660
            $origin->error = $langs->trans('ErrorFieldRequired', $langs->transnoentities('PaymentConditionsShort'));
1661
            return null;
1662
        }
1663
1664
        $payment_conditions_deposit_percent = getDictionaryValue('c_payment_term', 'deposit_percent', $origin->cond_reglement_id);
1665
1666
        if (empty($payment_conditions_deposit_percent)) {
1667
            $origin->error = 'ErrorPaymentConditionsNotEligibleToDepositCreation';
1668
            return null;
1669
        }
1670
1671
        if (empty($origin->deposit_percent)) {
1672
            $origin->error = $langs->trans('ErrorFieldRequired', $langs->transnoentities('DepositPercent'));
1673
            return null;
1674
        }
1675
1676
        $deposit = new self($origin->db);
1677
        $deposit->socid = $origin->socid;
1678
        $deposit->type = self::TYPE_DEPOSIT;
1679
        $deposit->fk_project = $origin->fk_project;
1680
        $deposit->ref_client = $origin->ref_client;
0 ignored issues
show
Bug Best Practice introduced by
The property ref_client does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1681
        $deposit->date = $date;
1682
        $deposit->mode_reglement_id = $origin->mode_reglement_id;
1683
        $deposit->cond_reglement_id = $payment_terms_id;
1684
        $deposit->availability_id = $origin->availability_id;
0 ignored issues
show
Bug Best Practice introduced by
The property availability_id does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1685
        $deposit->demand_reason_id = $origin->demand_reason_id;
1686
        $deposit->fk_account = $origin->fk_account;
1687
        $deposit->fk_incoterms = $origin->fk_incoterms;
0 ignored issues
show
Bug Best Practice introduced by
The property fk_incoterms does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1688
        $deposit->location_incoterms = $origin->location_incoterms;
0 ignored issues
show
Bug Best Practice introduced by
The property location_incoterms does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1689
        $deposit->fk_multicurrency = $origin->fk_multicurrency;
1690
        $deposit->multicurrency_code = $origin->multicurrency_code;
1691
        $deposit->multicurrency_tx = $origin->multicurrency_tx;
1692
        $deposit->module_source = $origin->module_source;
0 ignored issues
show
Bug Best Practice introduced by
The property module_source does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1693
        $deposit->pos_source = $origin->pos_source;
0 ignored issues
show
Bug Best Practice introduced by
The property pos_source does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
1694
        $deposit->model_pdf = 'crabe';
1695
1696
        $modelByTypeConfName = 'FACTURE_ADDON_PDF_' . $deposit->type;
1697
1698
        if (getDolGlobalString($modelByTypeConfName)) {
1699
            $deposit->model_pdf = getDolGlobalString($modelByTypeConfName);
1700
        } elseif (getDolGlobalString('FACTURE_ADDON_PDF')) {
1701
            $deposit->model_pdf = getDolGlobalString('FACTURE_ADDON_PDF');
1702
        }
1703
1704
        if (!getDolGlobalString('MAIN_DISABLE_PROPAGATE_NOTES_FROM_ORIGIN')) {
1705
            $deposit->note_private = $origin->note_private;
1706
            $deposit->note_public = $origin->note_public;
1707
        }
1708
1709
        $deposit->origin = $origin->element;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

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

1709
        /** @scrutinizer ignore-deprecated */ $deposit->origin = $origin->element;

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...
1710
        $deposit->origin_id = $origin->id;
1711
1712
        $origin->fetch_optionals();
1713
1714
        foreach ($origin->array_options as $extrakey => $value) {
1715
            $deposit->array_options[$extrakey] = $value;
1716
        }
1717
1718
        $deposit->linked_objects[$deposit->origin] = $deposit->origin_id;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

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

1718
        $deposit->linked_objects[/** @scrutinizer ignore-deprecated */ $deposit->origin] = $deposit->origin_id;

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...
1719
1720
        foreach ($overrideFields as $key => $value) {
1721
            $deposit->$key = $value;
1722
        }
1723
1724
        $deposit->context['createdepositfromorigin'] = 'createdepositfromorigin';
1725
1726
        $origin->db->begin();
1727
1728
        // Facture::create() also imports contact from origin
1729
        $createReturn = $deposit->create($user, $notrigger);
1730
1731
        if ($createReturn <= 0) {
1732
            $origin->db->rollback();
1733
            $origin->error = $deposit->error;
1734
            $origin->errors = $deposit->errors;
1735
            return null;
1736
        }
1737
1738
        $amount_ttc_diff = 0;
1739
        $amountdeposit = array();
1740
        $descriptions = array();
1741
1742
        if (getDolGlobalString('MAIN_DEPOSIT_MULTI_TVA')) {
1743
            $amount = $origin->total_ttc * ($origin->deposit_percent / 100);
1744
1745
            $TTotalByTva = array();
1746
            foreach ($origin->lines as &$line) {
1747
                if (!empty($line->special_code)) {
1748
                    continue;
1749
                }
1750
                $TTotalByTva[$line->tva_tx] += $line->total_ttc;
1751
                $descriptions[$line->tva_tx] .= '<li>' . (!empty($line->product_ref) ? $line->product_ref . ' - ' : '');
1752
                $descriptions[$line->tva_tx] .= (!empty($line->product_label) ? $line->product_label . ' - ' : '');
1753
                $descriptions[$line->tva_tx] .= $langs->trans('Qty') . ' : ' . $line->qty;
1754
                $descriptions[$line->tva_tx] .= ' - ' . $langs->trans('TotalHT') . ' : ' . price($line->total_ht) . '</li>';
1755
            }
1756
1757
            foreach ($TTotalByTva as $tva => &$total) {
1758
                $coef = $total / $origin->total_ttc; // Calc coef
1759
                $am = $amount * $coef;
1760
                $amount_ttc_diff += $am;
1761
                $amountdeposit[$tva] += $am / (1 + $tva / 100); // Convert into HT for the addline
1762
            }
1763
        } else {
1764
            $totalamount = 0;
1765
            $lines = $origin->lines;
1766
            $numlines = count($lines);
1767
            for ($i = 0; $i < $numlines; $i++) {
1768
                if (empty($lines[$i]->qty)) {
1769
                    continue; // We discard qty=0, it is an option
1770
                }
1771
                if (!empty($lines[$i]->special_code)) {
1772
                    continue; // We discard special_code (frais port, ecotaxe, option, ...)
1773
                }
1774
1775
                $totalamount += $lines[$i]->total_ht; // Fixme : is it not for the customer ? Shouldn't we take total_ttc ?
1776
                $tva_tx = $lines[$i]->tva_tx;
1777
                $amountdeposit[$tva_tx] += ($lines[$i]->total_ht * $origin->deposit_percent) / 100;
1778
                $descriptions[$tva_tx] .= '<li>' . (!empty($lines[$i]->product_ref) ? $lines[$i]->product_ref . ' - ' : '');
1779
                $descriptions[$tva_tx] .= (!empty($lines[$i]->product_label) ? $lines[$i]->product_label . ' - ' : '');
1780
                $descriptions[$tva_tx] .= $langs->trans('Qty') . ' : ' . $lines[$i]->qty;
1781
                $descriptions[$tva_tx] .= ' - ' . $langs->trans('TotalHT') . ' : ' . price($lines[$i]->total_ht) . '</li>';
1782
            }
1783
1784
            if ($totalamount == 0) {
1785
                $amountdeposit[0] = 0;
1786
            }
1787
1788
            $amount_ttc_diff = $amountdeposit[0];
1789
        }
1790
1791
        foreach ($amountdeposit as $tva => $amount) {
1792
            if (empty($amount)) {
1793
                continue;
1794
            }
1795
1796
            $descline = '(DEPOSIT) (' . $origin->deposit_percent . '%) - ' . $origin->ref;
1797
1798
            // Hidden conf
1799
            if (getDolGlobalString('INVOICE_DEPOSIT_VARIABLE_MODE_DETAIL_LINES_IN_DESCRIPTION') && !empty($descriptions[$tva])) {
1800
                $descline .= '<ul>' . $descriptions[$tva] . '</ul>';
1801
            }
1802
1803
            $addlineResult = $deposit->addline(
1804
                $descline,
1805
                $amount, // subprice
1806
                1, // quantity
1807
                $tva, // vat rate
1808
                0, // localtax1_tx
1809
                0, // localtax2_tx
1810
                (!getDolGlobalString('INVOICE_PRODUCTID_DEPOSIT') ? 0 : $conf->global->INVOICE_PRODUCTID_DEPOSIT), // fk_product
1811
                0, // remise_percent
1812
                0, // date_start
1813
                0, // date_end
1814
                0,
1815
                0, // info_bits
1816
                0,
1817
                'HT',
1818
                0,
1819
                0, // product_type
1820
                1,
1821
                0, // special_code
1822
                $deposit->origin,
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

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

1822
                /** @scrutinizer ignore-deprecated */ $deposit->origin,

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...
1823
                0,
1824
                0,
1825
                0,
1826
                0
1827
                //,$langs->trans('Deposit') //Deprecated
1828
            );
1829
1830
            if ($addlineResult < 0) {
1831
                $origin->db->rollback();
1832
                $origin->error = $deposit->error;
1833
                $origin->errors = $deposit->errors;
1834
                return null;
1835
            }
1836
        }
1837
1838
        $diff = $deposit->total_ttc - $amount_ttc_diff;
1839
1840
        if (getDolGlobalString('MAIN_DEPOSIT_MULTI_TVA') && $diff != 0) {
1841
            $deposit->fetch_lines();
1842
            $subprice_diff = $deposit->lines[0]->subprice - $diff / (1 + $deposit->lines[0]->tva_tx / 100);
1843
1844
            $updatelineResult = $deposit->updateline(
1845
                $deposit->lines[0]->id,
1846
                $deposit->lines[0]->desc,
1847
                $subprice_diff,
1848
                $deposit->lines[0]->qty,
1849
                $deposit->lines[0]->remise_percent,
1850
                $deposit->lines[0]->date_start,
1851
                $deposit->lines[0]->date_end,
1852
                $deposit->lines[0]->tva_tx,
1853
                0,
1854
                0,
1855
                'HT',
1856
                $deposit->lines[0]->info_bits,
1857
                $deposit->lines[0]->product_type,
1858
                0,
1859
                0,
1860
                0,
1861
                $deposit->lines[0]->pa_ht,
1862
                $deposit->lines[0]->label,
1863
                0,
1864
                array(),
1865
                100
1866
            );
1867
1868
            if ($updatelineResult < 0) {
1869
                $origin->db->rollback();
1870
                $origin->error = $deposit->error;
1871
                $origin->errors = $deposit->errors;
1872
                return null;
1873
            }
1874
        }
1875
1876
        $hookmanager->initHooks(array('invoicedao'));
1877
1878
        $parameters = array('objFrom' => $origin);
1879
        $reshook = $hookmanager->executeHooks('createFrom', $parameters, $deposit, $action); // Note that $action and $object may have been
1880
        // modified by hook
1881
        if ($reshook < 0) {
1882
            $origin->db->rollback();
1883
            $origin->error = $hookmanager->error;
1884
            $origin->errors = $hookmanager->errors;
1885
            return null;
1886
        }
1887
1888
        if (!empty($autoValidateDeposit)) {
1889
            $validateReturn = $deposit->validate($user, '', 0, $notrigger);
1890
1891
            if ($validateReturn < 0) {
1892
                $origin->db->rollback();
1893
                $origin->error = $deposit->error;
1894
                $origin->errors = $deposit->errors;
1895
                return null;
1896
            }
1897
        }
1898
1899
        unset($deposit->context['createdepositfromorigin']);
1900
1901
        $origin->db->commit();
1902
1903
        return $deposit;
1904
    }
1905
1906
    /**
1907
     * getTooltipContentArray
1908
     *
1909
     * @param array $params ex option, infologin
1910
     * @since v18
1911
     * @return array
1912
     */
1913
    public function getTooltipContentArray($params)
1914
    {
1915
        global $conf, $langs, $mysoc, $user;
1916
1917
        $langs->load('bills');
1918
1919
        $datas = [];
1920
        $moretitle = $params['moretitle'] ?? '';
1921
1922
        $picto = $this->picto;
1923
        if ($this->type == self::TYPE_REPLACEMENT) {
1924
            $picto .= 'r'; // Replacement invoice
1925
        }
1926
        if ($this->type == self::TYPE_CREDIT_NOTE) {
1927
            $picto .= 'a'; // Credit note
1928
        }
1929
        if ($this->type == self::TYPE_DEPOSIT) {
1930
            $picto .= 'd'; // Deposit invoice
1931
        }
1932
1933
        if ($user->hasRight("facture", "read")) {
1934
            $datas['picto'] = img_picto('', $picto) . ' <u class="paddingrightonly">' . $langs->trans("Invoice") . '</u>';
1935
1936
            $datas['picto'] .= '&nbsp;' . $this->getLibType(1);
1937
1938
            // Complete datas
1939
            if (!empty($params['fromajaxtooltip']) && !isset($this->totalpaid)) {
1940
                // Load the totalpaid field
1941
                $this->totalpaid = $this->getSommePaiement(0);
1942
            }
1943
            if (isset($this->status) && isset($this->totalpaid)) {
1944
                $datas['picto'] .= ' ' . $this->getLibStatut(5, $this->totalpaid);
1945
            }
1946
            if ($moretitle) {
1947
                $datas['picto'] .= ' - ' . $moretitle;
1948
            }
1949
            if (!empty($this->ref)) {
1950
                $datas['ref'] = '<br><b>' . $langs->trans('Ref') . ':</b> ' . $this->ref;
1951
            }
1952
            if (!empty($this->ref_customer)) {
1953
                $datas['refcustomer'] = '<br><b>' . $langs->trans('RefCustomer') . ':</b> ' . $this->ref_customer;
1954
            }
1955
            if (!empty($this->date)) {
1956
                $datas['date'] = '<br><b>' . $langs->trans('Date') . ':</b> ' . dol_print_date($this->date, 'day');
1957
            }
1958
            if (!empty($this->total_ht)) {
1959
                $datas['amountht'] = '<br><b>' . $langs->trans('AmountHT') . ':</b> ' . price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
1960
            }
1961
            if (!empty($this->total_tva)) {
1962
                $datas['amountvat'] = '<br><b>' . $langs->trans('AmountVAT') . ':</b> ' . price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
1963
            }
1964
            if (!empty($this->revenuestamp) && $this->revenuestamp != 0) {
1965
                $datas['amountrevenustamp'] = '<br><b>' . $langs->trans('RevenueStamp') . ':</b> ' . price($this->revenuestamp, 0, $langs, 0, -1, -1, $conf->currency);
1966
            }
1967
            if (!empty($this->total_localtax1) && $this->total_localtax1 != 0) {
1968
                // We keep test != 0 because $this->total_localtax1 can be '0.00000000'
1969
                $datas['amountlt1'] = '<br><b>' . $langs->transcountry('AmountLT1', $mysoc->country_code) . ':</b> ' . price($this->total_localtax1, 0, $langs, 0, -1, -1, $conf->currency);
1970
            }
1971
            if (!empty($this->total_localtax2) && $this->total_localtax2 != 0) {
1972
                $datas['amountlt2'] = '<br><b>' . $langs->transcountry('AmountLT2', $mysoc->country_code) . ':</b> ' . price($this->total_localtax2, 0, $langs, 0, -1, -1, $conf->currency);
1973
            }
1974
            if (!empty($this->total_ttc)) {
1975
                $datas['amountttc'] = '<br><b>' . $langs->trans('AmountTTC') . ':</b> ' . price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
1976
            }
1977
        }
1978
1979
        return $datas;
1980
    }
1981
1982
    /**
1983
     *  Return clicable link of object (with eventually picto)
1984
     *
1985
     *  @param  int     $withpicto                  Add picto into link
1986
     *  @param  string  $option                     Where point the link
1987
     *  @param  int     $max                        Maxlength of ref
1988
     *  @param  int     $short                      1=Return just URL
1989
     *  @param  string  $moretitle                  Add more text to title tooltip
1990
     *  @param  int     $notooltip                  1=Disable tooltip
1991
     *  @param  int     $addlinktonotes             1=Add link to notes
1992
     *  @param  int     $save_lastsearch_value      -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
1993
     *  @param  string  $target                     Target of link ('', '_self', '_blank', '_parent', '_backoffice', ...)
1994
     *  @return string                              String with URL
1995
     */
1996
    public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $moretitle = '', $notooltip = 0, $addlinktonotes = 0, $save_lastsearch_value = -1, $target = '')
1997
    {
1998
        global $langs, $conf, $user;
1999
2000
        if (!empty($conf->dol_no_mouse_hover)) {
2001
            $notooltip = 1; // Force disable tooltips
2002
        }
2003
2004
        $result = '';
2005
2006
        if ($option == 'withdraw') {
2007
            $url = constant('BASE_URL') . '/compta/facture/prelevement.php?facid=' . $this->id;
2008
        } else {
2009
            $url = constant('BASE_URL') . '/compta/facture/card.php?id=' . $this->id;
2010
        }
2011
2012
        if (!$user->hasRight("facture", "read")) {
2013
            $option = 'nolink';
2014
        }
2015
2016
        if ($option !== 'nolink') {
2017
            // Add param to save lastsearch_values or not
2018
            $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
2019
            if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
2020
                $add_save_lastsearch_values = 1;
2021
            }
2022
            if ($add_save_lastsearch_values) {
2023
                $url .= '&save_lastsearch_values=1';
2024
            }
2025
        }
2026
2027
        if ($short) {
2028
            return $url;
2029
        }
2030
2031
        $picto = $this->picto;
2032
        if ($this->type == self::TYPE_REPLACEMENT) {
2033
            $picto .= 'r'; // Replacement invoice
2034
        }
2035
        if ($this->type == self::TYPE_CREDIT_NOTE) {
2036
            $picto .= 'a'; // Credit note
2037
        }
2038
        if ($this->type == self::TYPE_DEPOSIT) {
2039
            $picto .= 'd'; // Deposit invoice
2040
        }
2041
2042
        $params = [
2043
            'id' => $this->id,
2044
            'objecttype' => $this->element,
2045
            'moretitle' => $moretitle,
2046
            'option' => $option,
2047
        ];
2048
        $classfortooltip = 'classfortooltip';
2049
        $dataparams = '';
2050
        if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
2051
            $classfortooltip = 'classforajaxtooltip';
2052
            $dataparams = ' data-params="' . dol_escape_htmltag(json_encode($params)) . '"';
2053
            $label = '';
2054
        } else {
2055
            $label = implode($this->getTooltipContentArray($params));
2056
        }
2057
2058
        $linkclose = ($target ? ' target="' . $target . '"' : '');
2059
        if (empty($notooltip) && $user->hasRight("facture", "read")) {
2060
            if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2061
                $label = $langs->trans("Invoice");
2062
                $linkclose .= ' alt="' . dol_escape_htmltag($label, 1) . '"';
2063
            }
2064
            $linkclose .= ($label ? ' title="' . dol_escape_htmltag($label, 1) . '"' : ' title="tocomplete"');
2065
            $linkclose .= $dataparams . ' class="' . $classfortooltip . '"';
2066
        }
2067
2068
        $linkstart = '<a href="' . $url . '"';
2069
        $linkstart .= $linkclose . '>';
2070
        $linkend = '</a>';
2071
2072
        if ($option == 'nolink') {
2073
            $linkstart = '';
2074
            $linkend = '';
2075
        }
2076
2077
        $result .= $linkstart;
2078
        if ($withpicto) {
2079
            $result .= img_object(($notooltip ? '' : $label), ($picto ? $picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="' . (($withpicto != 2) ? 'paddingright ' : '') . '"'), 0, 0, $notooltip ? 0 : 1);
2080
        }
2081
        if ($withpicto != 2) {
2082
            $result .= ($max ? dol_trunc($this->ref, $max) : $this->ref);
2083
        }
2084
        $result .= $linkend;
2085
2086
        if ($addlinktonotes) {
2087
            $txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
2088
            if ($txttoshow) {
2089
                //$notetoshow = $langs->trans("ViewPrivateNote").':<br>'.dol_string_nohtmltag($txttoshow, 1);
2090
                $notetoshow = $langs->trans("ViewPrivateNote") . ':<br>' . $txttoshow;
2091
                $result .= ' <span class="note inline-block">';
2092
                $result .= '<a href="' . constant('BASE_URL') . '/compta/facture/note.php?id=' . $this->id . '" class="classfortooltip" title="' . dol_escape_htmltag($notetoshow, 1, 1) . '">';
2093
                $result .= img_picto('', 'note');
2094
                $result .= '</a>';
2095
                //$result.=img_picto($langs->trans("ViewNote"),'object_generic');
2096
                //$result.='</a>';
2097
                $result .= '</span>';
2098
            }
2099
        }
2100
2101
        global $action, $hookmanager;
2102
        $hookmanager->initHooks(array('invoicedao'));
2103
        $parameters = array('id' => $this->id, 'getnomurl' => &$result, 'notooltip' => $notooltip, 'addlinktonotes' => $addlinktonotes, 'save_lastsearch_value' => $save_lastsearch_value, 'target' => $target);
2104
        $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2105
        if ($reshook > 0) {
2106
            $result = $hookmanager->resPrint;
2107
        } else {
2108
            $result .= $hookmanager->resPrint;
2109
        }
2110
2111
        return $result;
2112
    }
2113
2114
    /**
2115
     *  Get object from database. Get also lines.
2116
     *
2117
     *  @param      int     $rowid              Id of object to load
2118
     *  @param      string  $ref                Reference of invoice
2119
     *  @param      string  $ref_ext            External reference of invoice
2120
     *  @param      int     $notused            Not used
2121
     *  @param      bool    $fetch_situation    Load also the previous and next situation invoice into $tab_previous_situation_invoice and $tab_next_situation_invoice
2122
     *  @return     int                         >0 if OK, <0 if KO, 0 if not found
2123
     */
2124
    public function fetch($rowid, $ref = '', $ref_ext = '', $notused = 0, $fetch_situation = false)
2125
    {
2126
        if (empty($rowid) && empty($ref) && empty($ref_ext)) {
2127
            return -1;
2128
        }
2129
2130
        $sql = 'SELECT f.rowid, f.entity, f.ref, f.ref_client, f.ref_ext, f.type, f.subtype, f.fk_soc';
2131
        $sql .= ', f.total_tva, f.localtax1, f.localtax2, f.total_ht, f.total_ttc, f.revenuestamp';
2132
        $sql .= ', f.datef as df, f.date_pointoftax';
2133
        $sql .= ', f.date_lim_reglement as dlr';
2134
        $sql .= ', f.datec as datec';
2135
        $sql .= ', f.date_valid as datev';
2136
        $sql .= ', f.tms as datem';
2137
        $sql .= ', f.note_private, f.note_public, f.fk_statut as status, f.paye, f.close_code, f.close_note, f.fk_user_author, f.fk_user_valid, f.fk_user_modif, f.model_pdf, f.last_main_doc';
2138
        $sql .= ', f.fk_facture_source, f.fk_fac_rec_source';
2139
        $sql .= ', f.fk_mode_reglement, f.fk_cond_reglement, f.fk_projet as fk_project, f.extraparams';
2140
        $sql .= ', f.situation_cycle_ref, f.situation_counter, f.situation_final';
2141
        $sql .= ', f.fk_account';
2142
        $sql .= ", f.fk_multicurrency, f.multicurrency_code, f.multicurrency_tx, f.multicurrency_total_ht, f.multicurrency_total_tva, f.multicurrency_total_ttc";
2143
        $sql .= ', p.code as mode_reglement_code, p.libelle as mode_reglement_libelle';
2144
        $sql .= ', c.code as cond_reglement_code, c.libelle as cond_reglement_libelle, c.libelle_facture as cond_reglement_libelle_doc';
2145
        $sql .= ', f.fk_incoterms, f.location_incoterms';
2146
        $sql .= ', f.module_source, f.pos_source';
2147
        $sql .= ", i.libelle as label_incoterms";
2148
        $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";
2149
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'facture as f';
2150
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_payment_term as c ON f.fk_cond_reglement = c.rowid';
2151
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_paiement as p ON f.fk_mode_reglement = p.id';
2152
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_incoterms as i ON f.fk_incoterms = i.rowid';
2153
2154
        if ($rowid) {
2155
            $sql .= " WHERE f.rowid = " . ((int) $rowid);
2156
        } else {
2157
            $sql .= ' WHERE f.entity IN (' . getEntity('invoice') . ')'; // Don't use entity if you use rowid
2158
            if ($ref) {
2159
                $sql .= " AND f.ref = '" . $this->db->escape($ref) . "'";
2160
            }
2161
            if ($ref_ext) {
2162
                $sql .= " AND f.ref_ext = '" . $this->db->escape($ref_ext) . "'";
2163
            }
2164
        }
2165
2166
        dol_syslog(get_only_class($this) . "::fetch", LOG_DEBUG);
2167
        $resql = $this->db->query($sql);
2168
        if ($resql) {
2169
            if ($this->db->num_rows($resql)) {
2170
                $obj = $this->db->fetch_object($resql);
2171
2172
                $this->id = $obj->rowid;
2173
                $this->entity = $obj->entity;
2174
2175
                $this->ref                  = $obj->ref;
2176
                $this->ref_client           = $obj->ref_client;
2177
                $this->ref_customer         = $obj->ref_client;
2178
                $this->ref_ext              = $obj->ref_ext;
2179
                $this->type                 = $obj->type;
2180
                $this->subtype              = $obj->subtype;
2181
                $this->date                 = $this->db->jdate($obj->df);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->db->jdate($obj->df) can also be of type string. However, the property $date is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
2182
                $this->date_pointoftax      = $this->db->jdate($obj->date_pointoftax);
2183
                $this->date_creation        = $this->db->jdate($obj->datec);
2184
                $this->date_validation      = $this->db->jdate($obj->datev);
2185
                $this->date_modification    = $this->db->jdate($obj->datem);
2186
                $this->datem                = $this->db->jdate($obj->datem);
2187
                $this->total_ht             = $obj->total_ht;
2188
                $this->total_tva            = $obj->total_tva;
2189
                $this->total_localtax1      = $obj->localtax1;
2190
                $this->total_localtax2      = $obj->localtax2;
2191
                $this->total_ttc            = $obj->total_ttc;
2192
                $this->revenuestamp         = $obj->revenuestamp;
2193
                $this->paye                 = $obj->paye;
2194
                $this->close_code           = $obj->close_code;
2195
                $this->close_note           = $obj->close_note;
2196
2197
                $this->socid = $obj->fk_soc;
2198
                $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
2199
2200
                $this->fk_project = $obj->fk_project;
2201
                $this->project = null; // Clear if another value was already set by fetch_projet
2202
2203
                $this->statut = $obj->status;   // deprecated
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

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

2203
                /** @scrutinizer ignore-deprecated */ $this->statut = $obj->status;   // deprecated

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...
2204
                $this->status = $obj->status;
2205
2206
                $this->date_lim_reglement = $this->db->jdate($obj->dlr);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->db->jdate($obj->dlr) can also be of type string. However, the property $date_lim_reglement is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
2207
                $this->mode_reglement_id    = $obj->fk_mode_reglement;
2208
                $this->mode_reglement_code  = $obj->mode_reglement_code;
2209
                $this->mode_reglement       = $obj->mode_reglement_libelle;
2210
                $this->cond_reglement_id    = $obj->fk_cond_reglement;
2211
                $this->cond_reglement_code  = $obj->cond_reglement_code;
2212
                $this->cond_reglement       = $obj->cond_reglement_libelle;
0 ignored issues
show
Bug Best Practice introduced by
The property $cond_reglement is declared private in Dolibarr\Core\Base\CommonObject. Since you implement __set, consider adding a @property or @property-write.
Loading history...
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$cond_reglement has been deprecated: Use $cond_reglement_id instead - Kept for compatibility ( Ignorable by Annotation )

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

2212
                /** @scrutinizer ignore-deprecated */ $this->cond_reglement       = $obj->cond_reglement_libelle;

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...
2213
                $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
2214
                $this->fk_account = ($obj->fk_account > 0) ? $obj->fk_account : null;
2215
                $this->fk_facture_source    = $obj->fk_facture_source;
2216
                $this->fk_fac_rec_source    = $obj->fk_fac_rec_source;
2217
                $this->note = $obj->note_private; // deprecated
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$note has been deprecated: Use $note_private instead. ( Ignorable by Annotation )

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

2217
                /** @scrutinizer ignore-deprecated */ $this->note = $obj->note_private; // deprecated

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...
2218
                $this->note_private = $obj->note_private;
2219
                $this->note_public          = $obj->note_public;
2220
                $this->user_creation_id     = $obj->fk_user_author;
2221
                $this->user_validation_id   = $obj->fk_user_valid;
2222
                $this->user_modification_id = $obj->fk_user_modif;
2223
                $this->fk_user_author       = $obj->fk_user_author;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Compta\Cla...acture::$fk_user_author has been deprecated: Use $user_creation_id ( Ignorable by Annotation )

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

2223
                /** @scrutinizer ignore-deprecated */ $this->fk_user_author       = $obj->fk_user_author;

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...
2224
                $this->fk_user_valid        = $obj->fk_user_valid;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Compta\Cla...Facture::$fk_user_valid has been deprecated: Use $user_validation_id ( Ignorable by Annotation )

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

2224
                /** @scrutinizer ignore-deprecated */ $this->fk_user_valid        = $obj->fk_user_valid;

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...
2225
                $this->fk_user_modif        = $obj->fk_user_modif;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Compta\Cla...Facture::$fk_user_modif has been deprecated: Use $user_modification_id ( Ignorable by Annotation )

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

2225
                /** @scrutinizer ignore-deprecated */ $this->fk_user_modif        = $obj->fk_user_modif;

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...
2226
                $this->model_pdf = $obj->model_pdf;
2227
                $this->last_main_doc = $obj->last_main_doc;
2228
                $this->situation_cycle_ref  = $obj->situation_cycle_ref;
2229
                $this->situation_counter    = $obj->situation_counter;
2230
                $this->situation_final      = $obj->situation_final;
2231
                $this->retained_warranty    = $obj->retained_warranty;
2232
                $this->retained_warranty_date_limit         = $this->db->jdate($obj->retained_warranty_date_limit);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->db->jdate($obj->r...ed_warranty_date_limit) can also be of type string. However, the property $retained_warranty_date_limit is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
2233
                $this->retained_warranty_fk_cond_reglement  = $obj->retained_warranty_fk_cond_reglement;
2234
2235
                $this->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
2236
2237
                //Incoterms
2238
                $this->fk_incoterms         = $obj->fk_incoterms;
2239
                $this->location_incoterms   = $obj->location_incoterms;
2240
                $this->label_incoterms = $obj->label_incoterms;
2241
2242
                $this->module_source = $obj->module_source;
2243
                $this->pos_source = $obj->pos_source;
2244
2245
                // Multicurrency
2246
                $this->fk_multicurrency         = $obj->fk_multicurrency;
2247
                $this->multicurrency_code = $obj->multicurrency_code;
2248
                $this->multicurrency_tx         = $obj->multicurrency_tx;
2249
                $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
2250
                $this->multicurrency_total_tva  = $obj->multicurrency_total_tva;
2251
                $this->multicurrency_total_ttc  = $obj->multicurrency_total_ttc;
2252
2253
                if (($this->type == self::TYPE_SITUATION || ($this->type == self::TYPE_CREDIT_NOTE && $this->situation_cycle_ref > 0)) && $fetch_situation) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($this->type == self::TY... 0) && $fetch_situation, Probably Intended Meaning: $this->type == self::TYP... 0 && $fetch_situation)
Loading history...
2254
                    $this->fetchPreviousNextSituationInvoice();
2255
                }
2256
2257
                // Retrieve all extrafield
2258
                // fetch optionals attributes and labels
2259
                $this->fetch_optionals();
2260
2261
                // Lines
2262
                $this->lines = array();
2263
2264
                $result = $this->fetch_lines();
2265
                if ($result < 0) {
2266
                    $this->error = $this->db->error();
2267
                    return -3;
2268
                }
2269
2270
                $this->db->free($resql);
2271
2272
                return 1;
2273
            } else {
2274
                $this->error = 'Invoice with id=' . $rowid . ' or ref=' . $ref . ' or ref_ext=' . $ref_ext . ' not found';
2275
2276
                dol_syslog(__METHOD__ . $this->error, LOG_WARNING);
2277
                return 0;
2278
            }
2279
        } else {
2280
            $this->error = $this->db->lasterror();
2281
            return -1;
2282
        }
2283
    }
2284
2285
2286
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2287
    /**
2288
     *  Load all detailed lines into this->lines
2289
     *
2290
     *  @param      int     $only_product   Return only physical products
2291
     *  @param      int     $loadalsotranslation    Return translation for products
2292
     *
2293
     *  @return     int         1 if OK, < 0 if KO
2294
     */
2295
    public function fetch_lines($only_product = 0, $loadalsotranslation = 0)
2296
    {
2297
		// phpcs:enable
2298
        $this->lines = array();
2299
2300
        $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,';
2301
        $sql .= ' l.localtax1_tx, l.localtax2_tx, l.localtax1_type, l.localtax2_type, l.remise_percent, l.fk_remise_except, l.subprice, l.ref_ext,';
2302
        $sql .= ' l.situation_percent, l.fk_prev_id,';
2303
        $sql .= ' l.rang, l.special_code, l.batch, l.fk_warehouse,';
2304
        $sql .= ' l.date_start as date_start, l.date_end as date_end,';
2305
        $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,';
2306
        $sql .= ' l.fk_unit,';
2307
        $sql .= ' l.fk_multicurrency, l.multicurrency_code, l.multicurrency_subprice, l.multicurrency_total_ht, l.multicurrency_total_tva, l.multicurrency_total_ttc,';
2308
        $sql .= ' p.ref as product_ref, p.fk_product_type as fk_product_type, p.label as product_label, p.description as product_desc, p.barcode as product_barcode';
2309
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'facturedet as l';
2310
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'product as p ON l.fk_product = p.rowid';
2311
        $sql .= ' WHERE l.fk_facture = ' . ((int) $this->id);
2312
        $sql .= ' ORDER BY l.rang, l.rowid';
2313
2314
        dol_syslog(get_only_class($this) . '::fetch_lines', LOG_DEBUG);
2315
        $result = $this->db->query($sql);
2316
        if ($result) {
2317
            $num = $this->db->num_rows($result);
2318
            $i = 0;
2319
            while ($i < $num) {
2320
                $objp = $this->db->fetch_object($result);
2321
                $line = new FactureLigne($this->db);
2322
2323
                $line->id               = $objp->rowid;
2324
                $line->rowid = $objp->rowid; // deprecated
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\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

2324
                /** @scrutinizer ignore-deprecated */ $line->rowid = $objp->rowid; // deprecated

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...
2325
                $line->fk_facture       = $objp->fk_facture;
2326
                $line->label            = $objp->custom_label; // deprecated
2327
                $line->desc             = $objp->description; // Description line
2328
                $line->description      = $objp->description; // Description line
2329
                $line->product_type     = $objp->product_type; // Type of line
2330
                $line->ref              = $objp->product_ref; // Ref product
2331
                $line->product_ref      = $objp->product_ref; // Ref product
2332
                $line->libelle          = $objp->product_label; // deprecated
2333
                $line->product_label    = $objp->product_label; // Label product
2334
                $line->product_barcode  = $objp->product_barcode; // Barcode number product
2335
                $line->product_desc     = $objp->product_desc; // Description product
2336
                $line->fk_product_type  = $objp->fk_product_type; // Type of product
2337
                $line->qty              = $objp->qty;
2338
                $line->subprice         = $objp->subprice;
2339
                $line->ref_ext          = $objp->ref_ext; // line external ref
2340
2341
                $line->vat_src_code = $objp->vat_src_code;
2342
                $line->tva_tx           = $objp->tva_tx;
2343
                $line->localtax1_tx     = $objp->localtax1_tx;
2344
                $line->localtax2_tx     = $objp->localtax2_tx;
2345
                $line->localtax1_type   = $objp->localtax1_type;
2346
                $line->localtax2_type   = $objp->localtax2_type;
2347
                $line->remise_percent   = $objp->remise_percent;
2348
                $line->fk_remise_except = $objp->fk_remise_except;
2349
                $line->fk_product       = $objp->fk_product;
2350
                $line->date_start       = $this->db->jdate($objp->date_start);
2351
                $line->date_end         = $this->db->jdate($objp->date_end);
2352
                $line->info_bits        = $objp->info_bits;
2353
                $line->total_ht         = $objp->total_ht;
2354
                $line->total_tva        = $objp->total_tva;
2355
                $line->total_localtax1  = $objp->total_localtax1;
2356
                $line->total_localtax2  = $objp->total_localtax2;
2357
                $line->total_ttc        = $objp->total_ttc;
2358
2359
                $line->fk_fournprice = $objp->fk_fournprice;
2360
                $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
2361
                $line->pa_ht = $marginInfos[0];
2362
                $line->marge_tx         = $marginInfos[1];
2363
                $line->marque_tx        = $marginInfos[2];
2364
                $line->rang = $objp->rang;
2365
                $line->special_code = $objp->special_code;
2366
                $line->fk_parent_line = $objp->fk_parent_line;
2367
                $line->situation_percent = $objp->situation_percent;
2368
                $line->fk_prev_id = $objp->fk_prev_id;
2369
                $line->fk_unit = $objp->fk_unit;
2370
2371
                $line->batch = $objp->batch;
2372
                $line->fk_warehouse = $objp->fk_warehouse;
2373
2374
                // Accountancy
2375
                $line->fk_accounting_account = $objp->fk_code_ventilation;
2376
2377
                // Multicurrency
2378
                $line->fk_multicurrency = $objp->fk_multicurrency;
2379
                $line->multicurrency_code = $objp->multicurrency_code;
2380
                $line->multicurrency_subprice   = $objp->multicurrency_subprice;
2381
                $line->multicurrency_total_ht   = $objp->multicurrency_total_ht;
2382
                $line->multicurrency_total_tva  = $objp->multicurrency_total_tva;
2383
                $line->multicurrency_total_ttc  = $objp->multicurrency_total_ttc;
2384
2385
                $line->fetch_optionals();
2386
2387
                // multilangs
2388
                if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
2389
                    $tmpproduct = new Product($this->db);
2390
                    $tmpproduct->fetch($objp->fk_product);
2391
                    $tmpproduct->getMultiLangs();
2392
2393
                    $line->multilangs = $tmpproduct->multilangs;
2394
                }
2395
2396
                $this->lines[$i] = $line;
2397
2398
                $i++;
2399
            }
2400
            $this->db->free($result);
2401
            return 1;
2402
        } else {
2403
            $this->error = $this->db->error();
2404
            return -3;
2405
        }
2406
    }
2407
2408
    /**
2409
     * Fetch previous and next situations invoices.
2410
     * Return all previous and next invoices (both standard and credit notes).
2411
     *
2412
     * @return  void
2413
     */
2414
    public function fetchPreviousNextSituationInvoice()
2415
    {
2416
        global $conf;
2417
2418
        $this->tab_previous_situation_invoice = array();
2419
        $this->tab_next_situation_invoice = array();
2420
2421
        $sql = 'SELECT rowid, type, situation_cycle_ref, situation_counter FROM ' . MAIN_DB_PREFIX . 'facture';
2422
        $sql .= " WHERE rowid <> " . ((int) $this->id);
2423
        $sql .= ' AND entity = ' . ((int) $this->entity);
2424
        $sql .= ' AND situation_cycle_ref = ' . (int) $this->situation_cycle_ref;
2425
        $sql .= ' ORDER BY situation_counter ASC';
2426
2427
        dol_syslog(get_only_class($this) . '::fetchPreviousNextSituationInvoice ', LOG_DEBUG);
2428
        $result = $this->db->query($sql);
2429
        if ($result && $this->db->num_rows($result) > 0) {
2430
            while ($objp = $this->db->fetch_object($result)) {
2431
                $invoice = new Facture($this->db);
2432
                if ($invoice->fetch($objp->rowid) > 0) {
2433
                    if (
2434
                        $objp->situation_counter < $this->situation_counter
2435
                        || ($objp->situation_counter == $this->situation_counter && $objp->rowid < $this->id) // This case appear when there are credit notes
2436
                    ) {
2437
                        $this->tab_previous_situation_invoice[] = $invoice;
2438
                    } else {
2439
                        $this->tab_next_situation_invoice[] = $invoice;
2440
                    }
2441
                }
2442
            }
2443
        }
2444
    }
2445
2446
    /**
2447
     *      Update database
2448
     *
2449
     *      @param      User    $user           User that modify
2450
     *      @param      int     $notrigger      0=launch triggers after, 1=disable triggers
2451
     *      @return     int                     Return integer <0 if KO, >0 if OK
2452
     */
2453
    public function update(User $user, $notrigger = 0)
2454
    {
2455
        $error = 0;
2456
2457
        // Clean parameters
2458
        if (empty($this->type)) {
2459
            $this->type = self::TYPE_STANDARD;
2460
        }
2461
        if (isset($this->subtype)) {
2462
            $this->subtype = (int) trim((string) $this->subtype);
2463
        }
2464
        if (isset($this->ref)) {
2465
            $this->ref = trim($this->ref);
2466
        }
2467
        if (isset($this->ref_ext)) {
2468
            $this->ref_ext = trim($this->ref_ext);
2469
        }
2470
        if (isset($this->ref_client)) {
2471
            $this->ref_client = trim($this->ref_client);
2472
        }
2473
        if (isset($this->increment)) {
2474
            $this->increment = trim($this->increment);
2475
        }
2476
        if (isset($this->close_code)) {
2477
            $this->close_code = trim($this->close_code);
2478
        }
2479
        if (isset($this->close_note)) {
2480
            $this->close_note = trim($this->close_note);
2481
        }
2482
        if (isset($this->note) || isset($this->note_private)) {
2483
            $this->note = (isset($this->note) ? trim($this->note) : trim($this->note_private)); // deprecated
2484
        }
2485
        if (isset($this->note) || isset($this->note_private)) {
2486
            $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
2487
        }
2488
        if (isset($this->note_public)) {
2489
            $this->note_public = trim($this->note_public);
2490
        }
2491
        if (isset($this->model_pdf)) {
2492
            $this->model_pdf = trim($this->model_pdf);
2493
        }
2494
        if (isset($this->import_key)) {
2495
            $this->import_key = trim($this->import_key);
2496
        }
2497
        if (isset($this->retained_warranty)) {
2498
            $this->retained_warranty = (float) $this->retained_warranty;
2499
        }
2500
2501
2502
        // Check parameters
2503
        // Put here code to add control on parameters values
2504
2505
        // Update request
2506
        $sql = "UPDATE " . MAIN_DB_PREFIX . "facture SET";
2507
        $sql .= " ref=" . (isset($this->ref) ? "'" . $this->db->escape($this->ref) . "'" : "null") . ",";
2508
        $sql .= " ref_ext=" . (isset($this->ref_ext) ? "'" . $this->db->escape($this->ref_ext) . "'" : "null") . ",";
2509
        $sql .= " type=" . (isset($this->type) ? $this->db->escape($this->type) : "null") . ",";
2510
        $sql .= " subtype=" . (isset($this->subtype) ? $this->db->escape($this->subtype) : "null") . ",";
2511
        $sql .= " ref_client=" . (isset($this->ref_client) ? "'" . $this->db->escape($this->ref_client) . "'" : "null") . ",";
2512
        $sql .= " increment=" . (isset($this->increment) ? "'" . $this->db->escape($this->increment) . "'" : "null") . ",";
2513
        $sql .= " fk_soc=" . (isset($this->socid) ? $this->db->escape($this->socid) : "null") . ",";
2514
        $sql .= " datec=" . (strval($this->date_creation) != '' ? "'" . $this->db->idate($this->date_creation) . "'" : 'null') . ",";
2515
        $sql .= " datef=" . (strval($this->date) != '' ? "'" . $this->db->idate($this->date) . "'" : 'null') . ",";
2516
        $sql .= " date_pointoftax=" . (strval($this->date_pointoftax) != '' ? "'" . $this->db->idate($this->date_pointoftax) . "'" : 'null') . ",";
2517
        $sql .= " date_valid=" . (strval($this->date_validation) != '' ? "'" . $this->db->idate($this->date_validation) . "'" : 'null') . ",";
2518
        $sql .= " paye=" . (isset($this->paye) ? $this->db->escape($this->paye) : 0) . ",";
2519
        $sql .= " close_code=" . (isset($this->close_code) ? "'" . $this->db->escape($this->close_code) . "'" : "null") . ",";
2520
        $sql .= " close_note=" . (isset($this->close_note) ? "'" . $this->db->escape($this->close_note) . "'" : "null") . ",";
2521
        $sql .= " total_tva=" . (isset($this->total_tva) ? (float) $this->total_tva : "null") . ",";
2522
        $sql .= " localtax1=" . (isset($this->total_localtax1) ? (float) $this->total_localtax1 : "null") . ",";
2523
        $sql .= " localtax2=" . (isset($this->total_localtax2) ? (float) $this->total_localtax2 : "null") . ",";
2524
        $sql .= " total_ht=" . (isset($this->total_ht) ? (float) $this->total_ht : "null") . ",";
2525
        $sql .= " total_ttc=" . (isset($this->total_ttc) ? (float) $this->total_ttc : "null") . ",";
2526
        $sql .= " revenuestamp=" . ((isset($this->revenuestamp) && $this->revenuestamp != '') ? (int) $this->revenuestamp : "null") . ",";
2527
        $sql .= " fk_statut=" . (isset($this->status) ? (int) $this->status : "null") . ",";
2528
        $sql .= " fk_user_valid=" . (isset($this->fk_user_valid) ? (int) $this->fk_user_valid : "null") . ",";
2529
        $sql .= " fk_facture_source=" . (isset($this->fk_facture_source) ? (int) $this->fk_facture_source : "null") . ",";
2530
        $sql .= " fk_projet=" . (isset($this->fk_project) ? (int) $this->fk_project : "null") . ",";
2531
        $sql .= " fk_cond_reglement=" . (isset($this->cond_reglement_id) ? (int) $this->cond_reglement_id : "null") . ",";
2532
        $sql .= " fk_mode_reglement=" . (isset($this->mode_reglement_id) ? (int) $this->mode_reglement_id : "null") . ",";
2533
        $sql .= " date_lim_reglement=" . (strval($this->date_lim_reglement) != '' ? "'" . $this->db->idate($this->date_lim_reglement) . "'" : 'null') . ",";
2534
        $sql .= " note_private=" . (isset($this->note_private) ? "'" . $this->db->escape($this->note_private) . "'" : "null") . ",";
2535
        $sql .= " note_public=" . (isset($this->note_public) ? "'" . $this->db->escape($this->note_public) . "'" : "null") . ",";
2536
        $sql .= " model_pdf=" . (isset($this->model_pdf) ? "'" . $this->db->escape($this->model_pdf) . "'" : "null") . ",";
2537
        $sql .= " import_key=" . (isset($this->import_key) ? "'" . $this->db->escape($this->import_key) . "'" : "null") . ",";
2538
        $sql .= " situation_cycle_ref=" . (empty($this->situation_cycle_ref) ? "null" : (int) $this->situation_cycle_ref) . ",";
2539
        $sql .= " situation_counter=" . (empty($this->situation_counter) ? "null" : (int) $this->situation_counter) . ",";
2540
        $sql .= " situation_final=" . (empty($this->situation_final) ? "0" : (int) $this->situation_final) . ",";
2541
        $sql .= " retained_warranty=" . (empty($this->retained_warranty) ? "0" : (float) $this->retained_warranty) . ",";
2542
        $sql .= " retained_warranty_date_limit=" . (strval($this->retained_warranty_date_limit) != '' ? "'" . $this->db->idate($this->retained_warranty_date_limit) . "'" : 'null') . ",";
2543
        $sql .= " retained_warranty_fk_cond_reglement=" . (isset($this->retained_warranty_fk_cond_reglement) ? (int) $this->retained_warranty_fk_cond_reglement : "null");
2544
        $sql .= " WHERE rowid=" . ((int) $this->id);
2545
2546
        $this->db->begin();
2547
2548
        dol_syslog(get_only_class($this) . "::update", LOG_DEBUG);
2549
        $resql = $this->db->query($sql);
2550
        if (!$resql) {
2551
            $error++;
2552
            $this->errors[] = "Error " . $this->db->lasterror();
2553
        }
2554
2555
        if (!$error) {
2556
            $result = $this->insertExtraFields();
2557
            if ($result < 0) {
2558
                $error++;
2559
            }
2560
        }
2561
2562
        if (!$error && !$notrigger) {
2563
            // Call trigger
2564
            $result = $this->call_trigger('BILL_MODIFY', $user);
2565
            if ($result < 0) {
2566
                $error++;
2567
            }
2568
            // End call triggers
2569
        }
2570
2571
        // Commit or rollback
2572
        if ($error) {
2573
            foreach ($this->errors as $errmsg) {
2574
                dol_syslog(get_only_class($this) . "::update " . $errmsg, LOG_ERR);
2575
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2576
            }
2577
            $this->db->rollback();
2578
            return -1 * $error;
2579
        } else {
2580
            $this->db->commit();
2581
            return 1;
2582
        }
2583
    }
2584
2585
2586
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2587
    /**
2588
     *    Add a discount line into an invoice (as an invoice line) using an existing absolute discount (Consume the discount)
2589
     *
2590
     *    @param     int    $idremise   Id of absolute discount
2591
     *    @return    int                >0 if OK, <0 if KO
2592
     */
2593
    public function insert_discount($idremise)
2594
    {
2595
		// phpcs:enable
2596
        global $conf, $langs;
2597
2598
        include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
2599
        include_once DOL_DOCUMENT_ROOT . '/core/class/discount.class.php';
2600
2601
        $this->db->begin();
2602
2603
        $remise = new DiscountAbsolute($this->db);
2604
        $result = $remise->fetch($idremise);
2605
2606
        if ($result > 0) {
2607
            if ($remise->fk_facture) {  // Protection against multiple submission
2608
                $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
2609
                $this->db->rollback();
2610
                return -5;
2611
            }
2612
2613
            $facligne = new FactureLigne($this->db);
2614
            $facligne->fk_facture = $this->id;
2615
            $facligne->fk_remise_except = $remise->id;
2616
            $facligne->desc = $remise->description; // Description ligne
2617
            $facligne->vat_src_code = $remise->vat_src_code;
2618
            $facligne->tva_tx = $remise->tva_tx;
2619
            $facligne->subprice = -$remise->amount_ht;
2620
            $facligne->fk_product = 0; // Id produit predefini
2621
            $facligne->qty = 1;
2622
            $facligne->remise_percent = 0;
2623
            $facligne->rang = -1;
2624
            $facligne->info_bits = 2;
2625
2626
            if (getDolGlobalString('MAIN_ADD_LINE_AT_POSITION')) {
2627
                $facligne->rang = 1;
2628
                $linecount = count($this->lines);
2629
                for ($ii = 1; $ii <= $linecount; $ii++) {
2630
                    $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
2631
                }
2632
            }
2633
2634
            // Get buy/cost price of invoice that is source of discount
2635
            if ($remise->fk_facture_source > 0) {
2636
                $srcinvoice = new Facture($this->db);
2637
                $srcinvoice->fetch($remise->fk_facture_source);
2638
                include_once DOL_DOCUMENT_ROOT . '/core/class/html.formmargin.class.php'; // TODO Move this into commonobject
2639
                $formmargin = new FormMargin($this->db);
2640
                $arraytmp = $formmargin->getMarginInfosArray($srcinvoice, false);
2641
                $facligne->pa_ht = $arraytmp['pa_total'];
2642
            }
2643
2644
            $facligne->total_ht  = -$remise->amount_ht;
2645
            $facligne->total_tva = -$remise->amount_tva;
2646
            $facligne->total_ttc = -$remise->amount_ttc;
2647
2648
            $facligne->multicurrency_subprice = -$remise->multicurrency_subprice;
2649
            $facligne->multicurrency_total_ht = -$remise->multicurrency_amount_ht;
2650
            $facligne->multicurrency_total_tva = -$remise->multicurrency_amount_tva;
2651
            $facligne->multicurrency_total_ttc = -$remise->multicurrency_amount_ttc;
2652
2653
            $lineid = $facligne->insert();
2654
            if ($lineid > 0) {
2655
                $result = $this->update_price(1);
2656
                if ($result > 0) {
2657
                    // Create link between discount and invoice line
2658
                    $result = $remise->link_to_invoice($lineid, 0);
2659
                    if ($result < 0) {
2660
                        $this->error = $remise->error;
2661
                        $this->db->rollback();
2662
                        return -4;
2663
                    }
2664
2665
                    $this->db->commit();
2666
                    return 1;
2667
                } else {
2668
                    $this->error = $facligne->error;
2669
                    $this->db->rollback();
2670
                    return -1;
2671
                }
2672
            } else {
2673
                $this->error = $facligne->error;
2674
                $this->db->rollback();
2675
                return -2;
2676
            }
2677
        } else {
2678
            $this->db->rollback();
2679
            return -3;
2680
        }
2681
    }
2682
2683
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2684
    /**
2685
     *  Set customer ref
2686
     *
2687
     *  @param      string  $ref_client     Customer ref
2688
     *  @param      int     $notrigger      1=Does not execute triggers, 0= execute triggers
2689
     *  @return     int                     Return integer <0 if KO, >0 if OK
2690
     */
2691
    public function set_ref_client($ref_client, $notrigger = 0)
2692
    {
2693
		// phpcs:enable
2694
        global $user;
2695
2696
        $error = 0;
2697
2698
        $this->db->begin();
2699
2700
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture';
2701
        if (empty($ref_client)) {
2702
            $sql .= ' SET ref_client = NULL';
2703
        } else {
2704
            $sql .= ' SET ref_client = \'' . $this->db->escape($ref_client) . '\'';
2705
        }
2706
        $sql .= " WHERE rowid = " . ((int) $this->id);
2707
2708
        dol_syslog(__METHOD__ . ' this->id=' . $this->id . ', ref_client=' . $ref_client, LOG_DEBUG);
2709
        $resql = $this->db->query($sql);
2710
        if (!$resql) {
2711
            $this->errors[] = $this->db->error();
2712
            $error++;
2713
        }
2714
2715
        if (!$error) {
2716
            $this->ref_client = $ref_client;
2717
        }
2718
2719
        if (!$notrigger && empty($error)) {
2720
            // Call trigger
2721
            $result = $this->call_trigger('BILL_MODIFY', $user);
2722
            if ($result < 0) {
2723
                $error++;
2724
            }
2725
            // End call triggers
2726
        }
2727
2728
        if (!$error) {
2729
            $this->ref_client = $ref_client;
2730
2731
            $this->db->commit();
2732
            return 1;
2733
        } else {
2734
            foreach ($this->errors as $errmsg) {
2735
                dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2736
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2737
            }
2738
            $this->db->rollback();
2739
            return -1 * $error;
2740
        }
2741
    }
2742
2743
    /**
2744
     *  Delete invoice
2745
     *
2746
     *  @param      User    $user           User making the deletion.
2747
     *  @param      int     $notrigger      1=Does not execute triggers, 0= execute triggers
2748
     *  @param      int     $idwarehouse    Id warehouse to use for stock change.
2749
     *  @return     int                     Return integer <0 if KO, 0=Refused, >0 if OK
2750
     */
2751
    public function delete($user, $notrigger = 0, $idwarehouse = -1)
2752
    {
2753
        global $langs, $conf;
2754
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
2755
2756
        $rowid = $this->id;
2757
2758
        dol_syslog(get_only_class($this) . "::delete rowid=" . $rowid . ", ref=" . $this->ref . ", thirdparty=" . (empty($this->thirdparty) ? '' : $this->thirdparty->name), LOG_DEBUG);
2759
2760
        // Test to avoid invoice deletion (allowed if draft)
2761
        $result = $this->is_erasable();
2762
2763
        if ($result <= 0) {
2764
            return 0;
2765
        }
2766
2767
        $error = 0;
2768
2769
        $this->db->begin();
2770
2771
        if (!$error && !$notrigger) {
2772
            // Call trigger
2773
            $result = $this->call_trigger('BILL_DELETE', $user);
2774
            if ($result < 0) {
2775
                $error++;
2776
            }
2777
            // End call triggers
2778
        }
2779
2780
        // Removed extrafields
2781
        if (!$error) {
2782
            $result = $this->deleteExtraFields();
2783
            if ($result < 0) {
2784
                $error++;
2785
                dol_syslog(get_only_class($this) . "::delete error deleteExtraFields " . $this->error, LOG_ERR);
2786
            }
2787
        }
2788
2789
        if (!$error) {
2790
            // Delete linked object
2791
            $res = $this->deleteObjectLinked();
2792
            if ($res < 0) {
2793
                $error++;
2794
            }
2795
        }
2796
2797
        if (!$error) {
2798
            // If invoice was converted into a discount not yet consumed, we remove discount
2799
            $sql = 'DELETE FROM ' . MAIN_DB_PREFIX . 'societe_remise_except';
2800
            $sql .= ' WHERE fk_facture_source = ' . ((int) $rowid);
2801
            $sql .= ' AND fk_facture_line IS NULL';
2802
            $resql = $this->db->query($sql);
2803
2804
            // If invoice has consumed discounts
2805
            $this->fetch_lines();
2806
            $list_rowid_det = array();
2807
            foreach ($this->lines as $key => $invoiceline) {
2808
                $list_rowid_det[] = $invoiceline->id;
2809
            }
2810
2811
            // Consumed discounts are freed
2812
            if (count($list_rowid_det)) {
2813
                $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'societe_remise_except';
2814
                $sql .= ' SET fk_facture = NULL, fk_facture_line = NULL';
2815
                $sql .= ' WHERE fk_facture_line IN (' . $this->db->sanitize(implode(',', $list_rowid_det)) . ')';
2816
2817
                if (!$this->db->query($sql)) {
2818
                    $this->error = $this->db->error() . " sql=" . $sql;
2819
                    $this->errors[] = $this->error;
2820
                    $this->db->rollback();
2821
                    return -5;
2822
                }
2823
            }
2824
2825
            // Remove other links to the deleted invoice
2826
2827
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'eventorganization_conferenceorboothattendee';
2828
            $sql .= ' SET fk_invoice = NULL';
2829
            $sql .= ' WHERE fk_invoice = ' . ((int) $rowid);
2830
2831
            if (!$this->db->query($sql)) {
2832
                $this->error = $this->db->error() . " sql=" . $sql;
2833
                $this->errors[] = $this->error;
2834
                $this->db->rollback();
2835
                return -5;
2836
            }
2837
2838
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'element_time';
2839
            $sql .= ' SET invoice_id = NULL, invoice_line_id = NULL';
2840
            $sql .= ' WHERE invoice_id = ' . ((int) $rowid);
2841
2842
            if (!$this->db->query($sql)) {
2843
                $this->error = $this->db->error() . " sql=" . $sql;
2844
                $this->errors[] = $this->error;
2845
                $this->db->rollback();
2846
                return -5;
2847
            }
2848
2849
            // If we decrease stock on invoice validation, we increase back if a warehouse id was provided
2850
            if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_BILL') && $idwarehouse != -1) {
2851
                $langs->load("agenda");
2852
2853
                $num = count($this->lines);
2854
                for ($i = 0; $i < $num; $i++) {
2855
                    if ($this->lines[$i]->fk_product > 0) {
2856
                        $mouvP = new MouvementStock($this->db);
2857
                        $mouvP->origin = &$this;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

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

2857
                        /** @scrutinizer ignore-deprecated */ $mouvP->origin = &$this;

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...
2858
                        $mouvP->setOrigin($this->element, $this->id);
2859
                        // We decrease stock for product
2860
                        if ($this->type == self::TYPE_CREDIT_NOTE) {
2861
                            $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceDeleteDolibarr", $this->ref));
2862
                        } else {
2863
                            $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
2864
                        }
2865
                    }
2866
                }
2867
            }
2868
2869
            // Invoice line extrafileds
2870
            $main = MAIN_DB_PREFIX . 'facturedet';
2871
            $ef = $main . "_extrafields";
2872
            $sqlef = "DELETE FROM " . $ef . " WHERE fk_object IN (SELECT rowid FROM " . $main . " WHERE fk_facture = " . ((int) $rowid) . ")";
2873
            // Delete invoice line
2874
            $sql = 'DELETE FROM ' . MAIN_DB_PREFIX . 'facturedet WHERE fk_facture = ' . ((int) $rowid);
2875
2876
            if ($this->db->query($sqlef) && $this->db->query($sql) && $this->delete_linked_contact() >= 0) {
2877
                $sql = 'DELETE FROM ' . MAIN_DB_PREFIX . 'facture WHERE rowid = ' . ((int) $rowid);
2878
2879
                $resql = $this->db->query($sql);
2880
                if ($resql) {
2881
                    // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
2882
                    $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
2883
                    $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
2884
2885
                    // On efface le repertoire de pdf provisoire
2886
                    $ref = dol_sanitizeFileName($this->ref);
2887
                    if ($conf->facture->dir_output && !empty($this->ref)) {
2888
                        $dir = $conf->facture->dir_output . "/" . $ref;
2889
                        $file = $conf->facture->dir_output . "/" . $ref . "/" . $ref . ".pdf";
2890
                        if (file_exists($file)) {   // We must delete all files before deleting directory
2891
                            $ret = dol_delete_preview($this);
2892
2893
                            if (!dol_delete_file($file, 0, 0, 0, $this)) { // For triggers
2894
                                $langs->load("errors");
2895
                                $this->error = $langs->trans("ErrorFailToDeleteFile", $file);
2896
                                $this->errors[] = $this->error;
2897
                                $this->db->rollback();
2898
                                return 0;
2899
                            }
2900
                        }
2901
                        if (file_exists($dir)) {
2902
                            if (!dol_delete_dir_recursive($dir)) { // For remove dir and meta
2903
                                $langs->load("errors");
2904
                                $this->error = $langs->trans("ErrorFailToDeleteDir", $dir);
2905
                                $this->errors[] = $this->error;
2906
                                $this->db->rollback();
2907
                                return 0;
2908
                            }
2909
                        }
2910
                    }
2911
2912
                    $this->db->commit();
2913
                    return 1;
2914
                } else {
2915
                    $this->error = $this->db->lasterror() . " sql=" . $sql;
2916
                    $this->errors[] = $this->error;
2917
                    $this->db->rollback();
2918
                    return -6;
2919
                }
2920
            } else {
2921
                $this->error = $this->db->lasterror() . " sql=" . $sql;
2922
                $this->errors[] = $this->error;
2923
                $this->db->rollback();
2924
                return -4;
2925
            }
2926
        } else {
2927
            $this->db->rollback();
2928
            return -2;
2929
        }
2930
    }
2931
2932
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2933
    /**
2934
     *  Tag the invoice as paid completely (if close_code is filled) => this->fk_statut=2, this->paye=1
2935
     *  or partially (if close_code filled) + appel trigger BILL_PAYED => this->fk_statut=2, this->paye stay 0
2936
     *
2937
     *  @deprecated
2938
     *  @see setPaid()
2939
     *  @param  User    $user       Object user that modify
2940
     *  @param  string  $close_code Code set when forcing to set the invoice as fully paid while in practice it is incomplete (because of a discount (fr:escompte) for instance)
2941
     *  @param  string  $close_note Comment set when forcing to set the invoice as fully paid while in practice it is incomplete (because of a discount (fr:escompte) for instance)
2942
     *  @return int                 Return integer <0 if KO, >0 if OK
2943
     */
2944
    public function set_paid($user, $close_code = '', $close_note = '')
2945
    {
2946
		// phpcs:enable
2947
        dol_syslog(get_only_class($this) . "::set_paid is deprecated, use setPaid instead", LOG_NOTICE);
2948
        return $this->setPaid($user, $close_code, $close_note);
2949
    }
2950
2951
    /**
2952
     *  Tag the invoice as :
2953
     *  - paid completely (if close_code is not filled) => this->fk_statut=2, this->paye=1
2954
     *  - or partially (if close_code filled) + appel trigger BILL_PAYED => this->fk_statut=2, this->paye stay 0
2955
     *
2956
     *  @param  User    $user       Object user that modify
2957
     *  @param  string  $close_code Code set when forcing to set the invoice as fully paid while in practice it is incomplete (because of a discount (fr:escompte) for instance)
2958
     *  @param  string  $close_note Comment set when forcing to set the invoice as fully paid while in practice it is incomplete (because of a discount (fr:escompte) for instance)
2959
     *  @return int                 Return integer <0 if KO, >0 if OK
2960
     */
2961
    public function setPaid($user, $close_code = '', $close_note = '')
2962
    {
2963
        $error = 0;
2964
2965
        if ($this->paye != 1) {
2966
            $this->db->begin();
2967
2968
            $now = dol_now();
2969
2970
            dol_syslog(get_only_class($this) . "::setPaid rowid=" . ((int)$this->id), LOG_DEBUG);
2971
2972
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture SET';
2973
            $sql .= ' fk_statut=' . self::STATUS_CLOSED;
2974
            if (!$close_code) {
2975
                $sql .= ', paye=1';
2976
            }
2977
            if ($close_code) {
2978
                $sql .= ", close_code='" . $this->db->escape($close_code) . "'";
2979
            }
2980
            if ($close_note) {
2981
                $sql .= ", close_note='" . $this->db->escape($close_note) . "'";
2982
            }
2983
            $sql .= ', fk_user_closing = ' . ((int) $user->id);
2984
            $sql .= ", date_closing = '" . $this->db->idate($now) . "'";
2985
            $sql .= " WHERE rowid = " . ((int) $this->id);
2986
2987
            $resql = $this->db->query($sql);
2988
            if ($resql) {
2989
                // Call trigger
2990
                $result = $this->call_trigger('BILL_PAYED', $user);
2991
                if ($result < 0) {
2992
                    $error++;
2993
                }
2994
                // End call triggers
2995
            } else {
2996
                $error++;
2997
                $this->error = $this->db->lasterror();
2998
            }
2999
3000
            if (!$error) {
3001
                $this->db->commit();
3002
                return 1;
3003
            } else {
3004
                $this->db->rollback();
3005
                return -1;
3006
            }
3007
        } else {
3008
            return 0;
3009
        }
3010
    }
3011
3012
3013
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3014
    /**
3015
     *  Tags the invoice as incompletely paid and call the trigger BILL_UNPAYED
3016
     *  This method is used when a direct debit (fr:prelevement) is refused
3017
     *  or when a canceled invoice is reopened.
3018
     *
3019
     *  @deprecated
3020
     *  @see setUnpaid()
3021
     *  @param  User    $user       Object user that change status
3022
     *  @return int                 Return integer <0 if KO, >0 if OK
3023
     */
3024
    public function set_unpaid($user)
3025
    {
3026
		// phpcs:enable
3027
        dol_syslog(get_only_class($this) . "::set_unpaid is deprecated, use setUnpaid instead", LOG_NOTICE);
3028
        return $this->setUnpaid($user);
3029
    }
3030
3031
    /**
3032
     *  Tag the invoice as incompletely paid and call the trigger BILL_UNPAYED
3033
     *  This method is used when a direct debit (fr:prelevement) is refused
3034
     *  or when a canceled invoice is reopened.
3035
     *
3036
     *  @param  User    $user       Object user that change status
3037
     *  @return int                 Return integer <0 if KO, >0 if OK
3038
     */
3039
    public function setUnpaid($user)
3040
    {
3041
        $error = 0;
3042
3043
        $this->db->begin();
3044
3045
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture';
3046
        $sql .= ' SET paye=0, fk_statut=' . self::STATUS_VALIDATED . ', close_code=null, close_note=null,';
3047
        $sql .= ' date_closing=null,';
3048
        $sql .= ' fk_user_closing=null';
3049
        $sql .= " WHERE rowid = " . ((int) $this->id);
3050
3051
        dol_syslog(get_only_class($this) . "::setUnpaid", LOG_DEBUG);
3052
        $resql = $this->db->query($sql);
3053
        if ($resql) {
3054
            // Call trigger
3055
            $result = $this->call_trigger('BILL_UNPAYED', $user);
3056
            if ($result < 0) {
3057
                $error++;
3058
            }
3059
            // End call triggers
3060
        } else {
3061
            $error++;
3062
            $this->error = $this->db->error();
3063
            dol_print_error($this->db);
3064
        }
3065
3066
        if (!$error) {
3067
            $this->db->commit();
3068
            return 1;
3069
        } else {
3070
            $this->db->rollback();
3071
            return -1;
3072
        }
3073
    }
3074
3075
3076
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3077
    /**
3078
     *  Tag invoice as canceled, with no payment on it (example for replacement invoice or payment never received) + call trigger BILL_CANCEL
3079
     *  Warning, if option to decrease stock on invoice was set, this function does not change stock (it might be a cancel because
3080
     *  of no payment even if merchandises were sent).
3081
     *
3082
     *  @deprecated
3083
     *  @see setCanceled()
3084
     *  @param  User    $user           Object user making change
3085
     *  @param  string  $close_code     Code of closing invoice (CLOSECODE_REPLACED, CLOSECODE_...)
3086
     *  @param  string  $close_note     Comment
3087
     *  @return int                     Return integer <0 if KO, >0 if OK
3088
     */
3089
    public function set_canceled($user, $close_code = '', $close_note = '')
3090
    {
3091
		// phpcs:enable
3092
        dol_syslog(get_only_class($this) . "::set_canceled is deprecated, use setCanceled instead", LOG_NOTICE);
3093
        return $this->setCanceled($user, $close_code, $close_note);
3094
    }
3095
3096
    /**
3097
     *  Tag invoice as canceled, with no payment on it (example for replacement invoice or payment never received) + call trigger BILL_CANCEL
3098
     *  Warning, if option to decrease stock on invoice was set, this function does not change stock (it might be a cancel because
3099
     *  of no payment even if merchandises were sent).
3100
     *
3101
     *  @param  User    $user           Object user making change
3102
     *  @param  string  $close_code     Code of closing invoice (CLOSECODE_REPLACED, CLOSECODE_...)
3103
     *  @param  string  $close_note     Comment
3104
     *  @return int                     Return integer <0 if KO, >0 if OK
3105
     */
3106
    public function setCanceled($user, $close_code = '', $close_note = '')
3107
    {
3108
        dol_syslog(get_only_class($this) . "::setCanceled rowid=" . ((int)$this->id), LOG_DEBUG);
3109
3110
        $this->db->begin();
3111
        $now = dol_now();
3112
3113
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture SET';
3114
        $sql .= ' fk_statut=' . self::STATUS_ABANDONED;
3115
        if ($close_code) {
3116
            $sql .= ", close_code='" . $this->db->escape($close_code) . "'";
3117
        }
3118
        if ($close_note) {
3119
            $sql .= ", close_note='" . $this->db->escape($close_note) . "'";
3120
        }
3121
        $sql .= ', fk_user_closing = ' . ((int) $user->id);
3122
        $sql .= ", date_closing = '" . $this->db->idate($now) . "'";
3123
        $sql .= " WHERE rowid = " . ((int) $this->id);
3124
3125
        $resql = $this->db->query($sql);
3126
        if ($resql) {
3127
            // Bound discounts are deducted from the invoice
3128
            // as they have not been used since the invoice is abandoned.
3129
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'societe_remise_except';
3130
            $sql .= ' SET fk_facture = NULL';
3131
            $sql .= ' WHERE fk_facture = ' . ((int) $this->id);
3132
3133
            $resql = $this->db->query($sql);
3134
            if ($resql) {
3135
                // Call trigger
3136
                $result = $this->call_trigger('BILL_CANCEL', $user);
3137
                if ($result < 0) {
3138
                    $this->db->rollback();
3139
                    return -1;
3140
                }
3141
                // End call triggers
3142
3143
                $this->db->commit();
3144
                return 1;
3145
            } else {
3146
                $this->error = $this->db->error() . " sql=" . $sql;
3147
                $this->db->rollback();
3148
                return -1;
3149
            }
3150
        } else {
3151
            $this->error = $this->db->error() . " sql=" . $sql;
3152
            $this->db->rollback();
3153
            return -2;
3154
        }
3155
    }
3156
3157
    /**
3158
     * Tag invoice as validated + call trigger BILL_VALIDATE
3159
     * Object must have lines loaded with fetch_lines
3160
     *
3161
     * @param   User    $user           Object user that validate
3162
     * @param   string  $force_number   Reference to force on invoice
3163
     * @param   int     $idwarehouse    Id of warehouse to use for stock decrease if option to decrease on stock is on (0=no decrease)
3164
     * @param   int     $notrigger      1=Does not execute triggers, 0= execute triggers
3165
     * @param   int     $batch_rule     0=do not decrement batch, else batch rule to use, 1=take in batches ordered by sellby and eatby dates
3166
     * @return  int                     Return integer <0 if KO, 0=Nothing done because invoice is not a draft, >0 if OK
3167
     */
3168
    public function validate($user, $force_number = '', $idwarehouse = 0, $notrigger = 0, $batch_rule = 0)
3169
    {
3170
        global $conf, $langs, $mysoc;
3171
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
3172
3173
        $productStatic = null;
3174
        $warehouseStatic = null;
3175
        if ($batch_rule > 0) {
3176
            $productStatic = new Product($this->db);
3177
            $warehouseStatic = new Entrepot($this->db);
3178
            $productbatch = new Productbatch($this->db);
3179
        }
3180
3181
        $now = dol_now();
3182
3183
        $error = 0;
3184
        dol_syslog(get_only_class($this) . '::validate user=' . $user->id . ', force_number=' . $force_number . ', idwarehouse=' . $idwarehouse);
3185
3186
        // Force to have object complete for checks
3187
        $this->fetch_thirdparty();
3188
        $this->fetch_lines();
3189
3190
        // Check parameters
3191
        if ($this->status != self::STATUS_DRAFT) {
3192
            dol_syslog(get_only_class($this) . "::validate Current status is not draft. operation canceled.", LOG_WARNING);
3193
            return 0;
3194
        }
3195
        if (count($this->lines) <= 0) {
3196
            $langs->load("errors");
3197
            $this->error = $langs->trans("ErrorObjectMustHaveLinesToBeValidated", $this->ref);
3198
            return -1;
3199
        }
3200
        if (
3201
            (!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && !$user->hasRight('facture', 'creer'))
3202
            || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && !$user->hasRight('facture', 'invoice_advance', 'validate'))
3203
        ) {
3204
            $this->error = 'Permission denied';
3205
            dol_syslog(get_only_class($this) . "::validate " . $this->error . ' MAIN_USE_ADVANCED_PERMS=' . getDolGlobalString('MAIN_USE_ADVANCED_PERMS'), LOG_ERR);
3206
            return -1;
3207
        }
3208
        if (
3209
            (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref)) &&   // empty should not happened, but when it occurs, the test save life
3210
            getDolGlobalString('FAC_FORCE_DATE_VALIDATION')                     // If option enabled, we force invoice date
3211
        ) {
3212
            $this->date = dol_now();
3213
            $this->date_lim_reglement = $this->calculate_date_lim_reglement();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->calculate_date_lim_reglement() can also be of type string. However, the property $date_lim_reglement is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
3214
        }
3215
        if (getDolGlobalString('INVOICE_CHECK_POSTERIOR_DATE')) {
3216
            $last_of_type = $this->willBeLastOfSameType(true);
3217
            if (!$last_of_type[0]) {
3218
                $this->error = $langs->transnoentities("ErrorInvoiceIsNotLastOfSameType", $this->ref, dol_print_date($this->date, 'day'), dol_print_date($last_of_type[1], 'day'));
3219
                return -1;
3220
            }
3221
        }
3222
3223
        // Check for mandatory fields in thirdparty (defined into setup)
3224
        if (!empty($this->thirdparty) && is_object($this->thirdparty)) {
3225
            $array_to_check = array('IDPROF1', 'IDPROF2', 'IDPROF3', 'IDPROF4', 'IDPROF5', 'IDPROF6', 'EMAIL', 'ACCOUNTANCY_CODE_CUSTOMER');
3226
            foreach ($array_to_check as $key) {
3227
                $keymin = strtolower($key);
3228
                if (!property_exists($this->thirdparty, $keymin)) {
3229
                    continue;
3230
                }
3231
                $vallabel = $this->thirdparty->$keymin;
3232
3233
                $i = (int) preg_replace('/[^0-9]/', '', $key);
3234
                if ($i > 0) {
3235
                    if ($this->thirdparty->isACompany()) {
3236
                        // Check for mandatory prof id (but only if country is other than ours)
3237
                        if ($mysoc->country_id > 0 && $this->thirdparty->country_id == $mysoc->country_id) {
3238
                            $idprof_mandatory = 'SOCIETE_' . $key . '_INVOICE_MANDATORY';
3239
                            if (!$vallabel && !empty($conf->global->$idprof_mandatory)) {
3240
                                $langs->load("errors");
3241
                                $this->error = $langs->trans('ErrorProdIdIsMandatory', $langs->transcountry('ProfId' . $i, $this->thirdparty->country_code)) . ' (' . $langs->trans("ForbiddenBySetupRules") . ') [' . $langs->trans('Company') . ' : ' . $this->thirdparty->name . ']';
3242
                                dol_syslog(__METHOD__ . ' ' . $this->error, LOG_ERR);
3243
                                return -1;
3244
                            }
3245
                        }
3246
                    }
3247
                } else {
3248
                    if ($key == 'EMAIL') {
3249
                        // Check for mandatory
3250
                        if (getDolGlobalString('SOCIETE_EMAIL_INVOICE_MANDATORY') && !isValidEmail($this->thirdparty->email)) {
3251
                            $langs->load("errors");
3252
                            $this->error = $langs->trans("ErrorBadEMail", $this->thirdparty->email) . ' (' . $langs->trans("ForbiddenBySetupRules") . ') [' . $langs->trans('Company') . ' : ' . $this->thirdparty->name . ']';
3253
                            dol_syslog(__METHOD__ . ' ' . $this->error, LOG_ERR);
3254
                            return -1;
3255
                        }
3256
                    }
3257
                    if ($key == 'ACCOUNTANCY_CODE_CUSTOMER') {
3258
                        // Check for mandatory
3259
                        if (getDolGlobalString('SOCIETE_ACCOUNTANCY_CODE_CUSTOMER_INVOICE_MANDATORY') && empty($this->thirdparty->code_compta)) {
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Societe\Cl...s\Societe::$code_compta has been deprecated: Use $code_compta_client ( Ignorable by Annotation )

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

3259
                        if (getDolGlobalString('SOCIETE_ACCOUNTANCY_CODE_CUSTOMER_INVOICE_MANDATORY') && empty(/** @scrutinizer ignore-deprecated */ $this->thirdparty->code_compta)) {

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...
3260
                            $langs->load("errors");
3261
                            $this->error = $langs->trans("ErrorAccountancyCodeCustomerIsMandatory", $this->thirdparty->name) . ' (' . $langs->trans("ForbiddenBySetupRules") . ')';
3262
                            dol_syslog(__METHOD__ . ' ' . $this->error, LOG_ERR);
3263
                            return -1;
3264
                        }
3265
                    }
3266
                }
3267
            }
3268
        }
3269
3270
        // Check for mandatory fields in $this
3271
        $array_to_check = array('REF_CLIENT' => 'RefCustomer');
3272
        foreach ($array_to_check as $key => $val) {
3273
            $keymin = strtolower($key);
3274
            $vallabel = $this->$keymin;
3275
3276
            // Check for mandatory
3277
            $keymandatory = 'INVOICE_' . $key . '_MANDATORY_FOR_VALIDATION';
3278
            if (!$vallabel && getDolGlobalString($keymandatory)) {
3279
                $langs->load("errors");
3280
                $error++;
3281
                setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv($val)), null, 'errors');
3282
            }
3283
        }
3284
3285
        $this->db->begin();
3286
3287
        // Check parameters
3288
        if ($this->type == self::TYPE_REPLACEMENT) {        // if this is a replacement invoice
3289
            // Check that source invoice is known
3290
            if ($this->fk_facture_source <= 0) {
3291
                $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("InvoiceReplacement"));
3292
                $this->db->rollback();
3293
                return -10;
3294
            }
3295
3296
            // Load source invoice that has been replaced
3297
            $facreplaced = new Facture($this->db);
3298
            $result = $facreplaced->fetch($this->fk_facture_source);
3299
            if ($result <= 0) {
3300
                $this->error = $langs->trans("ErrorBadInvoice");
3301
                $this->db->rollback();
3302
                return -11;
3303
            }
3304
3305
            // Check that source invoice not already replaced by another one.
3306
            $idreplacement = $facreplaced->getIdReplacingInvoice('validated');
3307
            if ($idreplacement && $idreplacement != $this->id) {
3308
                $facreplacement = new Facture($this->db);
3309
                $facreplacement->fetch($idreplacement);
3310
                $this->error = $langs->trans("ErrorInvoiceAlreadyReplaced", $facreplaced->ref, $facreplacement->ref);
3311
                $this->db->rollback();
3312
                return -12;
3313
            }
3314
3315
            $result = $facreplaced->setCanceled($user, self::CLOSECODE_REPLACED, '');
3316
            if ($result < 0) {
3317
                $this->error = $facreplaced->error;
3318
                $this->db->rollback();
3319
                return -13;
3320
            }
3321
        }
3322
3323
        // Define new ref
3324
        if ($force_number) {
3325
            $num = $force_number;
3326
        } elseif (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
3327
            if (getDolGlobalString('FAC_FORCE_DATE_VALIDATION')) {  // If option enabled, we force invoice date
3328
                $this->date = dol_now();
3329
                $this->date_lim_reglement = $this->calculate_date_lim_reglement();
3330
            }
3331
            $num = $this->getNextNumRef($this->thirdparty);
3332
        } else {
3333
            $num = $this->ref;
3334
        }
3335
3336
        $this->newref = dol_sanitizeFileName($num);
3337
3338
        if ($num) {
3339
            $this->update_price(1);
3340
3341
            // Validate
3342
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture';
3343
            $sql .= " SET ref = '" . $this->db->escape($num) . "', fk_statut = " . self::STATUS_VALIDATED . ", fk_user_valid = " . ($user->id > 0 ? $user->id : "null") . ", date_valid = '" . $this->db->idate($now) . "'";
3344
            if (getDolGlobalString('FAC_FORCE_DATE_VALIDATION')) {  // If option enabled, we force invoice date
3345
                $sql .= ", datef='" . $this->db->idate($this->date) . "'";
3346
                $sql .= ", date_lim_reglement='" . $this->db->idate($this->date_lim_reglement) . "'";
3347
            }
3348
            $sql .= " WHERE rowid = " . ((int) $this->id);
3349
3350
            dol_syslog(get_only_class($this) . "::validate", LOG_DEBUG);
3351
            $resql = $this->db->query($sql);
3352
            if (!$resql) {
3353
                dol_print_error($this->db);
3354
                $error++;
3355
            }
3356
3357
            // We check if the invoice was provisional
3358
            if (!$error && (preg_match('/^[\(]?PROV/i', $this->ref))) {
3359
                // La verif qu'une remise n'est pas utilisee 2 fois est faite au moment de l'insertion de ligne
3360
            }
3361
3362
            if (!$error) {
3363
                // Define third party as a customer
3364
                $result = $this->thirdparty->setAsCustomer();
3365
3366
                // If active we decrement the main product and its components at invoice validation
3367
                if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_BILL') && $idwarehouse > 0) {
3368
                    $langs->load("agenda");
3369
3370
                    // Loop on each line
3371
                    $cpt = count($this->lines);
3372
                    for ($i = 0; $i < $cpt; $i++) {
3373
                        if ($this->lines[$i]->fk_product > 0) {
3374
                            $mouvP = new MouvementStock($this->db);
3375
                            $mouvP->origin = &$this;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

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

3375
                            /** @scrutinizer ignore-deprecated */ $mouvP->origin = &$this;

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...
3376
                            $mouvP->setOrigin($this->element, $this->id);
3377
3378
                            // TODO If warehouseid has been set into invoice line, we should use this value in priority
3379
                            // $idwarehouse = $this->lines[$i]->fk_warehouse;
3380
3381
                            // We decrease stock for product
3382
                            if ($this->type == self::TYPE_CREDIT_NOTE) {
3383
                                $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, 0, $langs->trans("InvoiceValidatedInDolibarr", $num));
3384
                                if ($result < 0) {
3385
                                    $error++;
3386
                                    $this->error = $mouvP->error;
3387
                                }
3388
                            } else {
3389
                                $is_batch_line = false;
3390
                                if ($batch_rule > 0) {
3391
                                    $productStatic->fetch($this->lines[$i]->fk_product);
3392
                                    if ($productStatic->hasbatch()) {
3393
                                        $is_batch_line = true;
3394
                                        $product_qty_remain = $this->lines[$i]->qty;
3395
3396
                                        $sortfield = null;
3397
                                        $sortorder = null;
3398
                                        // find all batch order by sellby (DLC) and eatby dates (DLUO) first
3399
                                        if ($batch_rule == Productbatch::BATCH_RULE_SELLBY_EATBY_DATES_FIRST) {
3400
                                            $sortfield = 'pl.sellby,pl.eatby,pb.qty,pl.rowid';
3401
                                            $sortorder = 'ASC,ASC,ASC,ASC';
3402
                                        }
3403
3404
                                        $resBatchList = $productbatch->findAllForProduct($productStatic->id, $idwarehouse, (getDolGlobalInt('STOCK_ALLOW_NEGATIVE_TRANSFER') ? null : 0), $sortfield, $sortorder);
3405
                                        if (!is_array($resBatchList)) {
3406
                                            $error++;
3407
                                            $this->error = $this->db->lasterror();
3408
                                        }
3409
3410
                                        if (!$error) {
3411
                                            $batchList = $resBatchList;
3412
                                            if (empty($batchList)) {
3413
                                                $error++;
3414
                                                $langs->load('errors');
3415
                                                $warehouseStatic->fetch($idwarehouse);
3416
                                                $this->error = $langs->trans('ErrorBatchNoFoundForProductInWarehouse', $productStatic->label, $warehouseStatic->ref);
3417
                                                dol_syslog(__METHOD__ . ' Error: ' . $langs->transnoentitiesnoconv('ErrorBatchNoFoundForProductInWarehouse', $productStatic->label, $warehouseStatic->ref), LOG_ERR);
3418
                                            }
3419
3420
                                            foreach ($batchList as $batch) {
3421
                                                if ($batch->qty <= 0) {
3422
                                                    continue; // try to decrement only batches have positive quantity first
3423
                                                }
3424
3425
                                                // enough quantity in this batch
3426
                                                if ($batch->qty >= $product_qty_remain) {
3427
                                                    $product_batch_qty = $product_qty_remain;
3428
                                                } else {
3429
                                                    // not enough (take all in batch)
3430
                                                    $product_batch_qty = $batch->qty;
3431
                                                }
3432
                                                $result = $mouvP->livraison($user, $productStatic->id, $idwarehouse, $product_batch_qty, $this->lines[$i]->subprice, $langs->trans('InvoiceValidatedInDolibarr', $num), '', '', '', $batch->batch);
3433
                                                if ($result < 0) {
3434
                                                    $error++;
3435
                                                    $this->error = $mouvP->error;
3436
                                                    $this->errors = $mouvP->errors;
3437
                                                    break;
3438
                                                }
3439
3440
                                                $product_qty_remain -= $product_batch_qty;
3441
                                                // all product quantity was decremented
3442
                                                if ($product_qty_remain <= 0) {
3443
                                                    break;
3444
                                                }
3445
                                            }
3446
3447
                                            if (!$error && $product_qty_remain > 0) {
3448
                                                if (getDolGlobalInt('STOCK_ALLOW_NEGATIVE_TRANSFER')) {
3449
                                                    // take in the first batch
3450
                                                    $batch = $batchList[0];
3451
                                                    $result = $mouvP->livraison($user, $productStatic->id, $idwarehouse, $product_qty_remain, $this->lines[$i]->subprice, $langs->trans('InvoiceValidatedInDolibarr', $num), '', '', '', $batch->batch);
3452
                                                    if ($result < 0) {
3453
                                                        $error++;
3454
                                                        $this->error = $mouvP->error;
3455
                                                        $this->errors = $mouvP->errors;
3456
                                                    }
3457
                                                } else {
3458
                                                    $error++;
3459
                                                    $langs->load('errors');
3460
                                                    $warehouseStatic->fetch($idwarehouse);
3461
                                                    $this->error = $langs->trans('ErrorBatchNoFoundEnoughQuantityForProductInWarehouse', $productStatic->label, $warehouseStatic->ref);
3462
                                                    dol_syslog(__METHOD__ . ' Error: ' . $langs->transnoentitiesnoconv('ErrorBatchNoFoundEnoughQuantityForProductInWarehouse', $productStatic->label, $warehouseStatic->ref), LOG_ERR);
3463
                                                }
3464
                                            }
3465
                                        }
3466
                                    }
3467
                                }
3468
3469
                                if (!$is_batch_line) {
3470
                                    $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceValidatedInDolibarr", $num));
3471
                                    if ($result < 0) {
3472
                                        $error++;
3473
                                        $this->error = $mouvP->error;
3474
                                        $this->errors = $mouvP->errors;
3475
                                    }
3476
                                }
3477
                            }
3478
                        }
3479
                    }
3480
                }
3481
            }
3482
3483
            /*
3484
             * Set situation_final to 0 if is a credit note and the invoice source is a invoice situation (case when invoice situation is at 100%)
3485
             * So we can continue to create new invoice situation
3486
             */
3487
            if (!$error && $this->type == self::TYPE_CREDIT_NOTE && $this->fk_facture_source > 0) {
3488
                $invoice_situation = new Facture($this->db);
3489
                $result = $invoice_situation->fetch($this->fk_facture_source);
3490
                if ($result > 0 && $invoice_situation->type == self::TYPE_SITUATION && $invoice_situation->situation_final == 1) {
3491
                    $invoice_situation->situation_final = 0;
3492
                    // Disable triggers because module can force situation_final to 1 by triggers (ex: SubTotal)
3493
                    $result = $invoice_situation->setFinal($user, 1);
3494
                }
3495
                if ($result < 0) {
3496
                    $this->error = $invoice_situation->error;
3497
                    $this->errors = $invoice_situation->errors;
3498
                    $error++;
3499
                }
3500
            }
3501
3502
            // Trigger calls
3503
            if (!$error && !$notrigger) {
3504
                // Call trigger
3505
                $result = $this->call_trigger('BILL_VALIDATE', $user);
3506
                if ($result < 0) {
3507
                    $error++;
3508
                }
3509
                // End call triggers
3510
            }
3511
3512
            if (!$error) {
3513
                $this->oldref = $this->ref;
3514
3515
                // Rename directory if dir was a temporary ref
3516
                if (preg_match('/^[\(]?PROV/i', $this->ref)) {
3517
                    // Now we rename also files into index
3518
                    $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) . "'";
3519
                    $sql .= " WHERE filename LIKE '" . $this->db->escape($this->ref) . "%' AND filepath = 'facture/" . $this->db->escape($this->ref) . "' and entity = " . $conf->entity;
3520
                    $resql = $this->db->query($sql);
3521
                    if (!$resql) {
3522
                        $error++;
3523
                        $this->error = $this->db->lasterror();
3524
                    }
3525
                    $sql = 'UPDATE ' . MAIN_DB_PREFIX . "ecm_files set filepath = 'facture/" . $this->db->escape($this->newref) . "'";
3526
                    $sql .= " WHERE filepath = 'facture/" . $this->db->escape($this->ref) . "' and entity = " . $conf->entity;
3527
                    $resql = $this->db->query($sql);
3528
                    if (!$resql) {
3529
                        $error++;
3530
                        $this->error = $this->db->lasterror();
3531
                    }
3532
3533
                    // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
3534
                    $oldref = dol_sanitizeFileName($this->ref);
3535
                    $newref = dol_sanitizeFileName($num);
3536
                    $dirsource = $conf->facture->dir_output . '/' . $oldref;
3537
                    $dirdest = $conf->facture->dir_output . '/' . $newref;
3538
                    if (!$error && file_exists($dirsource)) {
3539
                        dol_syslog(get_only_class($this) . "::validate rename dir " . $dirsource . " into " . $dirdest);
3540
3541
                        if (@rename($dirsource, $dirdest)) {
3542
                            dol_syslog("Rename ok");
3543
                            // Rename docs starting with $oldref with $newref
3544
                            $listoffiles = dol_dir_list($conf->facture->dir_output . '/' . $newref, 'files', 1, '^' . preg_quote($oldref, '/'));
3545
                            foreach ($listoffiles as $fileentry) {
3546
                                $dirsource = $fileentry['name'];
3547
                                $dirdest = preg_replace('/^' . preg_quote($oldref, '/') . '/', $newref, $dirsource);
3548
                                $dirsource = $fileentry['path'] . '/' . $dirsource;
3549
                                $dirdest = $fileentry['path'] . '/' . $dirdest;
3550
                                @rename($dirsource, $dirdest);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for rename(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

3550
                                /** @scrutinizer ignore-unhandled */ @rename($dirsource, $dirdest);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
3551
                            }
3552
                        }
3553
                    }
3554
                }
3555
            }
3556
3557
            if (!$error && !$this->is_last_in_cycle()) {
3558
                if (!$this->updatePriceNextInvoice($langs)) {
3559
                    $error++;
3560
                }
3561
            }
3562
3563
            // Set new ref and define current status
3564
            if (!$error) {
3565
                $this->ref = $num;
3566
                $this->statut = self::STATUS_VALIDATED; // deprecated
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

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

3566
                /** @scrutinizer ignore-deprecated */ $this->statut = self::STATUS_VALIDATED; // deprecated

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...
3567
                $this->status = self::STATUS_VALIDATED;
3568
                $this->date_validation = $now;
3569
                $i = 0;
3570
3571
                if (getDolGlobalString('INVOICE_USE_SITUATION')) {
3572
                    $final = true;
3573
                    $nboflines = count($this->lines);
3574
                    while (($i < $nboflines) && $final) {
3575
                        if (getDolGlobalInt('INVOICE_USE_SITUATION') == 2) {
3576
                            $previousprogress = $this->lines[$i]->get_allprev_progress($this->lines[$i]->fk_facture);
3577
                            $current_progress = floatval($this->lines[$i]->situation_percent);
3578
                            $full_progress = $previousprogress + $current_progress;
3579
                            $final = ($full_progress == 100);
3580
                        } else {
3581
                            $final = ($this->lines[$i]->situation_percent == 100);
3582
                        }
3583
                        $i++;
3584
                    }
3585
3586
                    if (empty($final)) {
3587
                        $this->situation_final = 0;
3588
                    } else {
3589
                        $this->situation_final = 1;
3590
                    }
3591
3592
                    $this->setFinal($user);
3593
                }
3594
            }
3595
        } else {
3596
            $error++;
3597
        }
3598
3599
        if (!$error) {
3600
            $this->db->commit();
3601
            return 1;
3602
        } else {
3603
            $this->db->rollback();
3604
            return -1;
3605
        }
3606
    }
3607
3608
    /**
3609
     * Update price of next invoice
3610
     *
3611
     * @param   Translate   $langs  Translate object
3612
     * @return  bool                false if KO, true if OK
3613
     */
3614
    public function updatePriceNextInvoice(&$langs)
3615
    {
3616
        foreach ($this->tab_next_situation_invoice as $next_invoice) {
3617
            $is_last = $next_invoice->is_last_in_cycle();
3618
3619
            if ($next_invoice->status == self::STATUS_DRAFT && $is_last != 1) {
3620
                $this->error = $langs->trans('updatePriceNextInvoiceErrorUpdateline', $next_invoice->ref);
3621
                return false;
3622
            }
3623
3624
            foreach ($next_invoice->lines as $line) {
3625
                $result = $next_invoice->updateline(
3626
                    $line->id,
3627
                    $line->desc,
3628
                    $line->subprice,
3629
                    $line->qty,
3630
                    $line->remise_percent,
3631
                    $line->date_start,
3632
                    $line->date_end,
3633
                    $line->tva_tx,
3634
                    $line->localtax1_tx,
3635
                    $line->localtax2_tx,
3636
                    'HT',
3637
                    $line->info_bits,
3638
                    $line->product_type,
3639
                    $line->fk_parent_line,
3640
                    0,
3641
                    $line->fk_fournprice,
3642
                    $line->pa_ht,
3643
                    $line->label,
3644
                    $line->special_code,
3645
                    $line->array_options,
3646
                    $line->situation_percent,
3647
                    $line->fk_unit
3648
                );
3649
3650
                if ($result < 0) {
3651
                    $this->error = $langs->trans('updatePriceNextInvoiceErrorUpdateline', $next_invoice->ref);
3652
                    return false;
3653
                }
3654
            }
3655
3656
            break; // Only the next invoice and not each next invoice
3657
        }
3658
3659
        return true;
3660
    }
3661
3662
    /**
3663
     *  Set draft status
3664
     *
3665
     *  @param  User    $user           Object user that modify
3666
     *  @param  int     $idwarehouse    Id warehouse to use for stock change.
3667
     *  @return int                     Return integer <0 if KO, >0 if OK
3668
     */
3669
    public function setDraft($user, $idwarehouse = -1)
3670
    {
3671
        // phpcs:enable
3672
        global $conf, $langs;
3673
3674
        $error = 0;
3675
3676
        if ($this->status == self::STATUS_DRAFT) {
3677
            dol_syslog(__METHOD__ . " already draft status", LOG_WARNING);
3678
            return 0;
3679
        }
3680
3681
        dol_syslog(__METHOD__, LOG_DEBUG);
3682
3683
        $this->db->begin();
3684
3685
        $sql = "UPDATE " . MAIN_DB_PREFIX . "facture";
3686
        $sql .= " SET fk_statut = " . self::STATUS_DRAFT;
3687
        $sql .= " WHERE rowid = " . ((int) $this->id);
3688
3689
        $result = $this->db->query($sql);
3690
        if ($result) {
3691
            if (!$error) {
3692
                $this->oldcopy = clone $this;
3693
            }
3694
3695
            // If we decrease stock on invoice validation, we increase back
3696
            if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_BILL')) {
3697
                $langs->load("agenda");
3698
3699
                $num = count($this->lines);
3700
                for ($i = 0; $i < $num; $i++) {
3701
                    if ($this->lines[$i]->fk_product > 0) {
3702
                        $mouvP = new MouvementStock($this->db);
3703
                        $mouvP->origin = &$this;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$origin has been deprecated: Use $origin_type and $origin_id instead. ( Ignorable by Annotation )

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

3703
                        /** @scrutinizer ignore-deprecated */ $mouvP->origin = &$this;

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...
3704
                        $mouvP->setOrigin($this->element, $this->id);
3705
                        // We decrease stock for product
3706
                        if ($this->type == self::TYPE_CREDIT_NOTE) {
3707
                            $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceBackToDraftInDolibarr", $this->ref));
3708
                        } else {
3709
                            $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
3710
                        }
3711
                    }
3712
                }
3713
            }
3714
3715
            if ($error == 0) {
3716
                $old_statut = $this->status;
3717
                $this->statut = self::STATUS_DRAFT; // deprecated
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

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

3717
                /** @scrutinizer ignore-deprecated */ $this->statut = self::STATUS_DRAFT; // deprecated

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...
3718
                $this->status = self::STATUS_DRAFT;
3719
3720
                // Call trigger
3721
                $result = $this->call_trigger('BILL_UNVALIDATE', $user);
3722
                if ($result < 0) {
3723
                    $error++;
3724
                    $this->statut = $old_statut; // deprecated
3725
                    $this->status = $old_statut;
3726
                }
3727
                // End call triggers
3728
            } else {
3729
                $this->db->rollback();
3730
                return -1;
3731
            }
3732
3733
            if ($error == 0) {
3734
                $this->db->commit();
3735
                return 1;
3736
            } else {
3737
                $this->db->rollback();
3738
                return -1;
3739
            }
3740
        } else {
3741
            $this->error = $this->db->error();
3742
            $this->db->rollback();
3743
            return -1;
3744
        }
3745
    }
3746
3747
3748
    /**
3749
     *  Add an invoice line into database (linked to product/service or not).
3750
     *  Note: ->thirdparty must be defined.
3751
     *  Les parameters sont deja cense etre juste et avec valeurs finales a l'appel
3752
     *  de cette method. Aussi, pour le taux tva, il doit deja avoir ete defini
3753
     *  par l'appelant par la method get_default_tva(societe_vendeuse,societe_acheteuse,produit)
3754
     *  et le desc doit deja avoir la bonne valeur (a l'appelant de gerer le multilangue)
3755
     *
3756
     *  @param      string      $desc               Description of line
3757
     *  @param      double      $pu_ht              Unit price without tax (> 0 even for credit note)
3758
     *  @param      double      $qty                Quantity
3759
     *  @param      double      $txtva              Force Vat rate, -1 for auto (Can contain the vat_src_code too with syntax '9.9 (CODE)')
3760
     *  @param      double      $txlocaltax1        Local tax 1 rate (deprecated, use instead txtva with code inside)
3761
     *  @param      double      $txlocaltax2        Local tax 2 rate (deprecated, use instead txtva with code inside)
3762
     *  @param      int         $fk_product         Id of predefined product/service
3763
     *  @param      double      $remise_percent     Percent of discount on line
3764
     *  @param      int|string  $date_start         Date start of service
3765
     *  @param      int|string  $date_end           Date end of service
3766
     *  @param      int         $fk_code_ventilation    Code of dispatching into accountancy
3767
     *  @param      int         $info_bits          Bits of type of lines
3768
     *  @param      int         $fk_remise_except   Id discount used
3769
     *  @param      string      $price_base_type    'HT' or 'TTC'
3770
     *  @param      double      $pu_ttc             Unit price with tax (> 0 even for credit note)
3771
     *  @param      int         $type               Type of line (0=product, 1=service). Not used if fk_product is defined, the type of product is used.
3772
     *  @param      int         $rang               Position of line (-1 means last value + 1)
3773
     *  @param      int         $special_code       Special code (also used by externals modules!)
3774
     *  @param      string      $origin             Depend on global conf MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION can be 'orderdet', 'propaldet'..., else 'order','propal,'....
3775
     *  @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
3776
     *  @param      int         $fk_parent_line     Id of parent line
3777
     *  @param      int         $fk_fournprice      Supplier price id (to calculate margin) or ''
3778
     *  @param      int         $pa_ht              Buying price of line (to calculate margin) or ''
3779
     *  @param      string      $label              Label of the line (deprecated, do not use)
3780
     *  @param      array       $array_options      extrafields array
3781
     *  @param      int         $situation_percent  Situation advance percentage
3782
     *  @param      int         $fk_prev_id         Previous situation line id reference
3783
     *  @param      int|null    $fk_unit            Code of the unit to use. Null to use the default one
3784
     *  @param      double      $pu_ht_devise       Unit price in foreign currency
3785
     *  @param      string      $ref_ext            External reference of the line
3786
     *  @param      int         $noupdateafterinsertline    No update after insert of line
3787
     *  @return     int                             Return integer <0 if KO, Id of line if OK
3788
     */
3789
    public function addline(
3790
        $desc,
3791
        $pu_ht,
3792
        $qty,
3793
        $txtva,
3794
        $txlocaltax1 = 0,
3795
        $txlocaltax2 = 0,
3796
        $fk_product = 0,
3797
        $remise_percent = 0,
3798
        $date_start = '',
3799
        $date_end = '',
3800
        $fk_code_ventilation = 0,
3801
        $info_bits = 0,
3802
        $fk_remise_except = 0,
3803
        $price_base_type = 'HT',
3804
        $pu_ttc = 0,
3805
        $type = 0,
3806
        $rang = -1,
3807
        $special_code = 0,
3808
        $origin = '',
3809
        $origin_id = 0,
3810
        $fk_parent_line = 0,
3811
        $fk_fournprice = null,
3812
        $pa_ht = 0,
3813
        $label = '',
3814
        $array_options = array(),
3815
        $situation_percent = 100,
3816
        $fk_prev_id = 0,
3817
        $fk_unit = null,
3818
        $pu_ht_devise = 0,
3819
        $ref_ext = '',
3820
        $noupdateafterinsertline = 0
3821
    ) {
3822
        // Deprecation warning
3823
        if ($label) {
3824
            dol_syslog(__METHOD__ . ": using line label is deprecated", LOG_WARNING);
3825
            //var_dump(debug_backtrace(false));exit;
3826
        }
3827
3828
        global $mysoc, $conf, $langs;
3829
3830
        dol_syslog(get_only_class($this) . "::addline id=$this->id, pu_ht=$pu_ht, qty=$qty, txtva=$txtva, txlocaltax1=$txlocaltax1, txlocaltax2=$txlocaltax2, fk_product=$fk_product, remise_percent=$remise_percent, date_start=$date_start, date_end=$date_end, fk_code_ventilation=$fk_code_ventilation, info_bits=$info_bits, fk_remise_except=$fk_remise_except, price_base_type=$price_base_type, pu_ttc=$pu_ttc, type=$type, fk_unit=$fk_unit, desc=" . dol_trunc($desc, 25), LOG_DEBUG);
3831
3832
        if ($this->status == self::STATUS_DRAFT) {
3833
            include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
3834
3835
            // Clean parameters
3836
            if (empty($remise_percent)) {
3837
                $remise_percent = 0;
3838
            }
3839
            if (empty($qty)) {
3840
                $qty = 0;
3841
            }
3842
            if (empty($info_bits)) {
3843
                $info_bits = 0;
3844
            }
3845
            if (empty($rang)) {
3846
                $rang = 0;
3847
            }
3848
            if (empty($fk_code_ventilation)) {
3849
                $fk_code_ventilation = 0;
3850
            }
3851
            if (empty($txtva)) {
3852
                $txtva = 0;
3853
            }
3854
            if (empty($txlocaltax1)) {
3855
                $txlocaltax1 = 0;
3856
            }
3857
            if (empty($txlocaltax2)) {
3858
                $txlocaltax2 = 0;
3859
            }
3860
            if (empty($fk_parent_line) || $fk_parent_line < 0) {
3861
                $fk_parent_line = 0;
3862
            }
3863
            if (empty($fk_prev_id)) {
3864
                $fk_prev_id = 'null';
3865
            }
3866
            if (!isset($situation_percent) || $situation_percent > 100 || (string) $situation_percent == '') {
3867
                $situation_percent = 100;
3868
            }
3869
            if (empty($ref_ext)) {
3870
                $ref_ext = '';
3871
            }
3872
3873
            $remise_percent = (float) price2num($remise_percent);
3874
            $qty = price2num($qty);
3875
            $pu_ht = price2num($pu_ht);
3876
            $pu_ht_devise = price2num($pu_ht_devise);
3877
            $pu_ttc = price2num($pu_ttc);
3878
            $pa_ht = price2num($pa_ht);
3879
            if (!preg_match('/\((.*)\)/', (string) $txtva)) {
3880
                $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
3881
            }
3882
            $txlocaltax1 = price2num($txlocaltax1);
3883
            $txlocaltax2 = price2num($txlocaltax2);
3884
3885
            if ($price_base_type == 'HT') {
3886
                $pu = $pu_ht;
3887
            } else {
3888
                $pu = $pu_ttc;
3889
            }
3890
3891
            // Check parameters
3892
            if ($type < 0) {
3893
                return -1;
3894
            }
3895
3896
            if ($date_start && $date_end && $date_start > $date_end) {
3897
                $langs->load("errors");
3898
                $this->error = $langs->trans('ErrorStartDateGreaterEnd');
3899
                return -1;
3900
            }
3901
3902
            $this->db->begin();
3903
3904
            $product_type = $type;
3905
            if (!empty($fk_product) && $fk_product > 0) {
3906
                $product = new Product($this->db);
3907
                $result = $product->fetch($fk_product);
3908
                $product_type = $product->type;
3909
3910
                if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_INVOICE') && $product_type == 0 && $product->stock_reel < $qty) {
3911
                    $langs->load("errors");
3912
                    $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnInvoice', $product->ref);
3913
                    $this->db->rollback();
3914
                    return -3;
3915
                }
3916
            }
3917
3918
            $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
3919
3920
            // Clean vat code
3921
            $reg = array();
3922
            $vat_src_code = '';
3923
            if (preg_match('/\((.*)\)/', $txtva, $reg)) {
3924
                $vat_src_code = $reg[1];
3925
                $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
3926
            }
3927
3928
            // Calcul du total TTC et de la TVA pour la ligne a partir de
3929
            // qty, pu, remise_percent et txtva
3930
            // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
3931
            // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
3932
3933
            $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);
3934
3935
            $total_ht  = $tabprice[0];
3936
            $total_tva = $tabprice[1];
3937
            $total_ttc = $tabprice[2];
3938
            $total_localtax1 = $tabprice[9];
3939
            $total_localtax2 = $tabprice[10];
3940
            $pu_ht = $tabprice[3];
3941
3942
            // MultiCurrency
3943
            $multicurrency_total_ht = $tabprice[16];
3944
            $multicurrency_total_tva = $tabprice[17];
3945
            $multicurrency_total_ttc = $tabprice[18];
3946
            $pu_ht_devise = $tabprice[19];
3947
3948
            // Rank to use
3949
            $ranktouse = $rang;
3950
            if ($ranktouse == -1) {
3951
                $rangmax = $this->line_max($fk_parent_line);
3952
                $ranktouse = $rangmax + 1;
3953
            }
3954
3955
            // Insert line
3956
            $this->line = new FactureLigne($this->db);
3957
3958
            $this->line->context = $this->context;
3959
3960
            $this->line->fk_facture = $this->id;
3961
            $this->line->label = $label; // deprecated
3962
            $this->line->desc = $desc;
3963
            $this->line->ref_ext = $ref_ext;
3964
3965
            $this->line->qty = ($this->type == self::TYPE_CREDIT_NOTE ? abs((float) $qty) : $qty); // For credit note, quantity is always positive and unit price negative
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->type == self::TYP...bs((double)$qty) : $qty can also be of type string. However, the property $qty is declared as type double. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
3966
            $this->line->subprice = ($this->type == self::TYPE_CREDIT_NOTE ? -abs($pu_ht) : $pu_ht); // For credit note, unit price always negative, always positive otherwise
3967
3968
            $this->line->vat_src_code = $vat_src_code;
3969
            $this->line->tva_tx = $txtva;
3970
            $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
3971
            $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
3972
            $this->line->localtax1_type = empty($localtaxes_type[0]) ? 0 : $localtaxes_type[0];
3973
            $this->line->localtax2_type = empty($localtaxes_type[2]) ? 0 : $localtaxes_type[2];
3974
3975
            $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
3976
            $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
3977
            $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
3978
            $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
3979
            $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
3980
3981
            $this->line->fk_product = $fk_product;
3982
            $this->line->product_type = $product_type;
3983
            $this->line->remise_percent = $remise_percent;
3984
            $this->line->date_start = $date_start;
3985
            $this->line->date_end = $date_end;
3986
            $this->line->fk_code_ventilation = $fk_code_ventilation;
3987
            $this->line->rang = $ranktouse;
3988
            $this->line->info_bits = $info_bits;
3989
            $this->line->fk_remise_except = $fk_remise_except;
3990
3991
            $this->line->special_code = $special_code;
3992
            $this->line->fk_parent_line = $fk_parent_line;
3993
            $this->line->origin = $origin;
3994
            $this->line->origin_id = $origin_id;
3995
            $this->line->situation_percent = $situation_percent;
3996
            $this->line->fk_prev_id = $fk_prev_id;
0 ignored issues
show
Documentation Bug introduced by
It seems like $fk_prev_id can also be of type string. However, the property $fk_prev_id is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
3997
            $this->line->fk_unit = $fk_unit;
3998
3999
            // infos margin
4000
            $this->line->fk_fournprice = $fk_fournprice;
4001
            $this->line->pa_ht = $pa_ht;
4002
4003
            // Multicurrency
4004
            $this->line->fk_multicurrency = $this->fk_multicurrency;
4005
            $this->line->multicurrency_code = $this->multicurrency_code;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->multicurrency_code can also be of type string[]. However, the property $multicurrency_code is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
4006
            $this->line->multicurrency_subprice = ($this->type == self::TYPE_CREDIT_NOTE ? -abs($pu_ht_devise) : $pu_ht_devise); // For credit note, unit price always negative, always positive otherwise
4007
4008
            $this->line->multicurrency_total_ht = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($multicurrency_total_ht) : $multicurrency_total_ht); // For credit note and if qty is negative, total is negative
4009
            $this->line->multicurrency_total_tva = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($multicurrency_total_tva) : $multicurrency_total_tva); // For credit note and if qty is negative, total is negative
4010
            $this->line->multicurrency_total_ttc = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($multicurrency_total_ttc) : $multicurrency_total_ttc); // For credit note and if qty is negative, total is negative
4011
4012
            if (is_array($array_options) && count($array_options) > 0) {
4013
                $this->line->array_options = $array_options;
4014
            }
4015
4016
            $result = $this->line->insert();
4017
            if ($result > 0) {
4018
                // Reorder if child line
4019
                if (!empty($fk_parent_line)) {
4020
                    $this->line_order(true, 'DESC');
4021
                } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) { // Update all rank of all other lines
4022
                    $linecount = count($this->lines);
4023
                    for ($ii = $ranktouse; $ii <= $linecount; $ii++) {
4024
                        $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
4025
                    }
4026
                }
4027
4028
                // Mise a jour information denormalisees au niveau de la facture meme
4029
                if (empty($noupdateafterinsertline)) {
4030
                    $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.
4031
                }
4032
4033
                if ($result > 0) {
4034
                    $this->db->commit();
4035
                    return $this->line->id;
4036
                } else {
4037
                    $this->error = $this->db->lasterror();
4038
                    $this->db->rollback();
4039
                    return -1;
4040
                }
4041
            } else {
4042
                $this->error = $this->line->error;
4043
                $this->errors = $this->line->errors;
4044
                $this->db->rollback();
4045
                return -2;
4046
            }
4047
        } else {
4048
            $this->errors[] = 'status of invoice must be Draft to allow use of ->addline()';
4049
            dol_syslog(get_only_class($this) . "::addline status of invoice must be Draft to allow use of ->addline()", LOG_ERR);
4050
            return -3;
4051
        }
4052
    }
4053
4054
    /**
4055
     *  Update a detail line
4056
     *
4057
     *  @param      int         $rowid              Id of line to update
4058
     *  @param      string      $desc               Description of line
4059
     *  @param      double      $pu                 Prix unitaire (HT ou TTC selon price_base_type) (> 0 even for credit note lines)
4060
     *  @param      double      $qty                Quantity
4061
     *  @param      double      $remise_percent     Percentage discount of the line
4062
     *  @param      int         $date_start         Date de debut de validite du service
4063
     *  @param      int         $date_end           Date de fin de validite du service
4064
     *  @param      double      $txtva              VAT Rate (Can be '8.5', '8.5 (ABC)')
4065
     *  @param      double      $txlocaltax1        Local tax 1 rate
4066
     *  @param      double      $txlocaltax2        Local tax 2 rate
4067
     *  @param      string      $price_base_type    HT or TTC
4068
     *  @param      int         $info_bits          Miscellaneous information
4069
     *  @param      int         $type               Type of line (0=product, 1=service)
4070
     *  @param      int         $fk_parent_line     Id of parent line (0 in most cases, used by modules adding sublevels into lines).
4071
     *  @param      int         $skip_update_total  Keep fields total_xxx to 0 (used for special lines by some modules)
4072
     *  @param      int         $fk_fournprice      Id of origin supplier price
4073
     *  @param      int         $pa_ht              Price (without tax) of product when it was bought
4074
     *  @param      string      $label              Label of the line (deprecated, do not use)
4075
     *  @param      int         $special_code       Special code (also used by externals modules!)
4076
     *  @param      array       $array_options      extrafields array
4077
     *  @param      int         $situation_percent  Situation advance percentage
4078
     *  @param      int|null    $fk_unit            Code of the unit to use. Null to use the default one
4079
     *  @param      double      $pu_ht_devise       Unit price in currency
4080
     *  @param      int         $notrigger          disable line update trigger
4081
     *  @param      string      $ref_ext            External reference of the line
4082
     *  @param      integer     $rang               rank of line
4083
     *  @return     int                             Return integer < 0 if KO, > 0 if OK
4084
     */
4085
    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 = array(), $situation_percent = 100, $fk_unit = null, $pu_ht_devise = 0, $notrigger = 0, $ref_ext = '', $rang = 0)
4086
    {
4087
        global $conf, $user;
4088
        // Deprecation warning
4089
        if ($label) {
4090
            dol_syslog(__METHOD__ . ": using line label is deprecated", LOG_WARNING);
4091
        }
4092
4093
        include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
4094
4095
        global $mysoc, $langs;
4096
4097
        dol_syslog(get_only_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);
4098
4099
        if ($this->status == self::STATUS_DRAFT) {
4100
            if (!$this->is_last_in_cycle() && empty($this->error)) {
4101
                if (!$this->checkProgressLine($rowid, $situation_percent)) {
4102
                    if (!$this->error) {
4103
                        $this->error = $langs->trans('invoiceLineProgressError');
4104
                    }
4105
                    return -3;
4106
                }
4107
            }
4108
4109
            if ($date_start && $date_end && $date_start > $date_end) {
4110
                $langs->load("errors");
4111
                $this->error = $langs->trans('ErrorStartDateGreaterEnd');
4112
                return -1;
4113
            }
4114
4115
            $this->db->begin();
4116
4117
            // Clean parameters
4118
            if (empty($qty)) {
4119
                $qty = 0;
4120
            }
4121
            if (empty($fk_parent_line) || $fk_parent_line < 0) {
4122
                $fk_parent_line = 0;
4123
            }
4124
            if (empty($special_code) || $special_code == 3) {
4125
                $special_code = 0;
4126
            }
4127
            if (!isset($situation_percent) || $situation_percent > 100 || (string) $situation_percent == '') {
4128
                $situation_percent = 100;
4129
            }
4130
            if (empty($ref_ext)) {
4131
                $ref_ext = '';
4132
            }
4133
4134
            $remise_percent = (float) price2num($remise_percent);
4135
            $qty            = price2num($qty);
4136
            $pu             = price2num($pu);
4137
            $pu_ht_devise = price2num($pu_ht_devise);
4138
            $pa_ht = price2num($pa_ht);
4139
            if (!preg_match('/\((.*)\)/', (string) $txtva)) {
4140
                $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
4141
            }
4142
            $txlocaltax1    = (float) price2num($txlocaltax1);
4143
            $txlocaltax2    = (float) price2num($txlocaltax2);
4144
4145
            // Check parameters
4146
            if ($type < 0) {
4147
                return -1;
4148
            }
4149
4150
            // Calculate total with, without tax and tax from qty, pu, remise_percent and txtva
4151
            // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
4152
            // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
4153
4154
            $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
4155
4156
            // Clean vat code
4157
            $reg = array();
4158
            $vat_src_code = '';
4159
            if (preg_match('/\((.*)\)/', $txtva, $reg)) {
4160
                $vat_src_code = $reg[1];
4161
                $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
4162
            }
4163
4164
            $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);
4165
4166
            $total_ht  = $tabprice[0];
4167
            $total_tva = $tabprice[1];
4168
            $total_ttc = $tabprice[2];
4169
            $total_localtax1 = $tabprice[9];
4170
            $total_localtax2 = $tabprice[10];
4171
            $pu_ht  = $tabprice[3];
4172
            $pu_tva = $tabprice[4];
4173
            $pu_ttc = $tabprice[5];
4174
4175
            // MultiCurrency
4176
            $multicurrency_total_ht = $tabprice[16];
4177
            $multicurrency_total_tva = $tabprice[17];
4178
            $multicurrency_total_ttc = $tabprice[18];
4179
            $pu_ht_devise = $tabprice[19];
4180
4181
            // Old properties: $price, $remise (deprecated)
4182
            $price = $pu;
4183
            $remise = 0;
4184
            if ($remise_percent > 0) {
4185
                $remise = round(((float) $pu * (float) $remise_percent / 100), 2);
4186
                $price = ((float) $pu - $remise);
4187
            }
4188
            $price = price2num($price);
4189
4190
            //Fetch current line from the database and then clone the object and set it in $oldline property
4191
            $line = new FactureLigne($this->db);
4192
            $line->fetch($rowid);
4193
            $line->fetch_optionals();
4194
4195
            if (!empty($line->fk_product)) {
4196
                $product = new Product($this->db);
4197
                $result = $product->fetch($line->fk_product);
4198
                $product_type = $product->type;
4199
4200
                if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_INVOICE') && $product_type == 0 && $product->stock_reel < $qty) {
4201
                    $langs->load("errors");
4202
                    $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnInvoice', $product->ref);
4203
                    $this->db->rollback();
4204
                    return -3;
4205
                }
4206
            }
4207
4208
            $staticline = clone $line;
4209
4210
            $line->oldline = $staticline;
4211
            $this->line = $line;
4212
            $this->line->context = $this->context;
4213
            $this->line->rang = $rang;
4214
4215
            // Reorder if fk_parent_line change
4216
            if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) {
4217
                $rangmax = $this->line_max($fk_parent_line);
4218
                $this->line->rang = $rangmax + 1;
4219
            }
4220
4221
            $this->line->id = $rowid;
4222
            $this->line->rowid = $rowid;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\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

4222
            /** @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...
4223
            $this->line->label = $label;
4224
            $this->line->desc = $desc;
4225
            $this->line->ref_ext = $ref_ext;
4226
            $this->line->qty = ($this->type == self::TYPE_CREDIT_NOTE ? abs((float) $qty) : $qty); // For credit note, quantity is always positive and unit price negative
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->type == self::TYP...bs((double)$qty) : $qty can also be of type string. However, the property $qty is declared as type double. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
4227
4228
            $this->line->vat_src_code = $vat_src_code;
4229
            $this->line->tva_tx = $txtva;
4230
            $this->line->localtax1_tx       = $txlocaltax1;
4231
            $this->line->localtax2_tx       = $txlocaltax2;
4232
            $this->line->localtax1_type     = empty($localtaxes_type[0]) ? 0 : $localtaxes_type[0];
4233
            $this->line->localtax2_type     = empty($localtaxes_type[2]) ? 0 : $localtaxes_type[2];
4234
4235
            $this->line->remise_percent     = $remise_percent;
4236
            $this->line->subprice           = ($this->type == self::TYPE_CREDIT_NOTE ? -abs($pu_ht) : $pu_ht); // For credit note, unit price always negative, always positive otherwise
4237
            $this->line->date_start = $date_start;
4238
            $this->line->date_end           = $date_end;
4239
            $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
4240
            $this->line->total_tva          = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($total_tva) : $total_tva);
4241
            $this->line->total_localtax1    = $total_localtax1;
4242
            $this->line->total_localtax2    = $total_localtax2;
4243
            $this->line->total_ttc          = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($total_ttc) : $total_ttc);
4244
            $this->line->info_bits          = $info_bits;
4245
            $this->line->special_code       = $special_code;
4246
            $this->line->product_type       = $type;
4247
            $this->line->fk_parent_line = $fk_parent_line;
4248
            $this->line->skip_update_total = $skip_update_total;
4249
            $this->line->situation_percent = $situation_percent;
4250
            $this->line->fk_unit = $fk_unit;
4251
4252
            $this->line->fk_fournprice = $fk_fournprice;
4253
            $this->line->pa_ht = $pa_ht;
4254
4255
            // Multicurrency
4256
            $this->line->multicurrency_subprice     = ($this->type == self::TYPE_CREDIT_NOTE ? -abs($pu_ht_devise) : $pu_ht_devise); // For credit note, unit price always negative, always positive otherwise
4257
            $this->line->multicurrency_total_ht     = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($multicurrency_total_ht) : $multicurrency_total_ht); // For credit note and if qty is negative, total is negative
4258
            $this->line->multicurrency_total_tva    = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($multicurrency_total_tva) : $multicurrency_total_tva);
4259
            $this->line->multicurrency_total_ttc    = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($multicurrency_total_ttc) : $multicurrency_total_ttc);
4260
4261
            if (is_array($array_options) && count($array_options) > 0) {
4262
                // We replace values in this->line->array_options only for entries defined into $array_options
4263
                foreach ($array_options as $key => $value) {
4264
                    $this->line->array_options[$key] = $array_options[$key];
4265
                }
4266
            }
4267
4268
            $result = $this->line->update($user, $notrigger);
4269
            if ($result > 0) {
4270
                // Reorder if child line
4271
                if (!empty($fk_parent_line)) {
4272
                    $this->line_order(true, 'DESC');
4273
                }
4274
4275
                // Mise a jour info denormalisees au niveau facture
4276
                $this->update_price(1, 'auto');
4277
                $this->db->commit();
4278
                return $result;
4279
            } else {
4280
                $this->error = $this->line->error;
4281
                $this->db->rollback();
4282
                return -1;
4283
            }
4284
        } else {
4285
            $this->error = "Invoice statut makes operation forbidden";
4286
            return -2;
4287
        }
4288
    }
4289
4290
    /**
4291
     * Check if the percent edited is lower of next invoice line
4292
     *
4293
     * @param   int     $idline             id of line to check
4294
     * @param   float   $situation_percent  progress percentage need to be test
4295
     * @return  bool                        false if KO, true if OK
4296
     */
4297
    public function checkProgressLine($idline, $situation_percent)
4298
    {
4299
        $sql = 'SELECT fd.situation_percent FROM ' . MAIN_DB_PREFIX . 'facturedet fd
4300
				INNER JOIN ' . MAIN_DB_PREFIX . 'facture f ON (fd.fk_facture = f.rowid)
4301
				WHERE fd.fk_prev_id = ' . ((int) $idline) . ' AND f.fk_statut <> 0';
4302
4303
        $result = $this->db->query($sql);
4304
        if (!$result) {
4305
            $this->error = $this->db->error();
4306
            return false;
4307
        }
4308
4309
        $obj = $this->db->fetch_object($result);
4310
4311
        if ($obj === null) {
4312
            return true;
4313
        } else {
4314
            return ($situation_percent < $obj->situation_percent);
4315
        }
4316
    }
4317
4318
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4319
    /**
4320
     * Update invoice line with percentage
4321
     *
4322
     * @param  FactureLigne $line           Invoice line
4323
     * @param  int          $percent        Percentage
4324
     * @param  boolean      $update_price   Update object price
4325
     * @return void
4326
     */
4327
    public function update_percent($line, $percent, $update_price = true)
4328
    {
4329
		// phpcs:enable
4330
        global $mysoc, $user;
4331
4332
        // Progress should never be changed for discount lines
4333
        if (($line->info_bits & 2) == 2) {
4334
            return;
4335
        }
4336
4337
        include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
4338
4339
        // Cap percentages to 100
4340
        if ($percent > 100) {
4341
            $percent = 100;
4342
        }
4343
        if (getDolGlobalInt('INVOICE_USE_SITUATION') == 2) {
4344
            $previous_progress = $line->get_allprev_progress($line->fk_facture);
4345
            $current_progress = $percent - $previous_progress;
4346
            $line->situation_percent = $current_progress;
4347
            $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, '', $current_progress);
4348
        } else {
4349
            $line->situation_percent = $percent;
4350
            $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);
4351
        }
4352
        $line->total_ht = $tabprice[0];
4353
        $line->total_tva = $tabprice[1];
4354
        $line->total_ttc = $tabprice[2];
4355
        $line->total_localtax1 = $tabprice[9];
4356
        $line->total_localtax2 = $tabprice[10];
4357
        $line->multicurrency_total_ht  = $tabprice[16];
4358
        $line->multicurrency_total_tva = $tabprice[17];
4359
        $line->multicurrency_total_ttc = $tabprice[18];
4360
        $line->update($user);
4361
4362
        // sometimes it is better to not update price for each line, ie when updating situation on all lines
4363
        if ($update_price) {
4364
            $this->update_price(1);
4365
        }
4366
    }
4367
4368
    /**
4369
     *  Delete line in database
4370
     *
4371
     *  @param      int     $rowid      Id of line to delete
4372
     *  @param      int     $id         Id of object (for a check)
4373
     *  @return     int                 Return integer <0 if KO, >0 if OK
4374
     */
4375
    public function deleteLine($rowid, $id = 0)
4376
    {
4377
        global $user;
4378
4379
        dol_syslog(get_only_class($this) . "::deleteline rowid=" . ((int)$rowid), LOG_DEBUG);
4380
4381
        if ($this->status != self::STATUS_DRAFT) {
4382
            $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
4383
            return -1;
4384
        }
4385
4386
        $line = new FactureLigne($this->db);
4387
4388
        $line->context = $this->context;
4389
4390
        // Load line
4391
        $result = $line->fetch($rowid);
4392
        if (!($result > 0)) {
4393
            dol_print_error($this->db, $line->error, $line->errors);
4394
            return -1;
4395
        }
4396
4397
        if ($id > 0 && $line->fk_facture != $id) {
4398
            $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
4399
            return -1;
4400
        }
4401
4402
        $this->db->begin();
4403
4404
        // Memorize previous line for triggers
4405
        $staticline = clone $line;
4406
        $line->oldline = $staticline;
4407
4408
        if ($line->delete($user) > 0) {
4409
            $result = $this->update_price(1);
4410
4411
            if ($result > 0) {
4412
                $this->db->commit();
4413
                return 1;
4414
            } else {
4415
                $this->db->rollback();
4416
                $this->error = $this->db->lasterror();
4417
                return -1;
4418
            }
4419
        } else {
4420
            $this->db->rollback();
4421
            $this->error = $line->error;
4422
            return -1;
4423
        }
4424
    }
4425
4426
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4427
    /**
4428
     *  Set percent discount
4429
     *
4430
     *  @deprecated
4431
     *  @see setDiscount()
4432
     *  @param      User    $user       User that set discount
4433
     *  @param      double  $remise     Discount
4434
     *  @param      int     $notrigger  1=Does not execute triggers, 0= execute triggers
4435
     *  @return     int                 Return integer <0 if KO, >0 if OK
4436
     */
4437
    public function set_remise($user, $remise, $notrigger = 0)
4438
    {
4439
		// phpcs:enable
4440
        dol_syslog(get_only_class($this) . "::set_remise is deprecated, use setDiscount instead", LOG_NOTICE);
4441
        // @phan-suppress-next-line PhanDeprecatedFunction
4442
        return $this->setDiscount($user, $remise, $notrigger);
4443
    }
4444
4445
    /**
4446
     *  Set percent discount
4447
     *
4448
     *  @param      User    $user       User that set discount
4449
     *  @param      float   $remise     Discount
4450
     *  @param      int     $notrigger  1=Does not execute triggers, 0= execute triggers
4451
     *  @return     int                 Return integer <0 if KO, >0 if OK
4452
     */
4453
    public function setDiscount($user, $remise, $notrigger = 0)
4454
    {
4455
        // Clean parameters
4456
        if (empty($remise)) {
4457
            $remise = 0;
4458
        }
4459
4460
        if ($user->hasRight('facture', 'creer')) {
4461
            $remise = (float) price2num($remise, 2);
4462
4463
            $error = 0;
4464
4465
            $this->db->begin();
4466
4467
            $sql = "UPDATE " . MAIN_DB_PREFIX . "facture";
4468
            $sql .= " SET remise_percent = " . ((float) $remise);
4469
            $sql .= " WHERE rowid = " . ((int) $this->id);
4470
            $sql .= " AND fk_statut = " . ((int) self::STATUS_DRAFT);
4471
4472
            dol_syslog(__METHOD__, LOG_DEBUG);
4473
            $resql = $this->db->query($sql);
4474
            if (!$resql) {
4475
                $this->errors[] = $this->db->error();
4476
                $error++;
4477
            }
4478
4479
            if (!$notrigger && empty($error)) {
4480
                // Call trigger
4481
                $result = $this->call_trigger('BILL_MODIFY', $user);
4482
                if ($result < 0) {
4483
                    $error++;
4484
                }
4485
                // End call triggers
4486
            }
4487
4488
            if (!$error) {
4489
                $this->remise_percent = $remise;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Compta\Cla...acture::$remise_percent has been deprecated: The discount percent is on line level now ( Ignorable by Annotation )

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

4489
                /** @scrutinizer ignore-deprecated */ $this->remise_percent = $remise;

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...
4490
                $this->update_price(1);
4491
4492
                $this->db->commit();
4493
                return 1;
4494
            } else {
4495
                foreach ($this->errors as $errmsg) {
4496
                    dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
4497
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
4498
                }
4499
                $this->db->rollback();
4500
                return -1 * $error;
4501
            }
4502
        }
4503
4504
        return 0;
4505
    }
4506
4507
4508
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4509
    /**
4510
     *  Set absolute discount
4511
     *
4512
     *  @param      User    $user       User that set discount
4513
     *  @param      double  $remise     Discount
4514
     *  @param      int     $notrigger  1=Does not execute triggers, 0= execute triggers
4515
     *  @return     int                 Return integer <0 if KO, >0 if OK
4516
     */
4517
    /*
4518
    public function set_remise_absolue($user, $remise, $notrigger = 0)
4519
    {
4520
		// phpcs:enable
4521
        if (empty($remise)) {
4522
            $remise = 0;
4523
        }
4524
4525
        if ($user->hasRight('facture', 'creer')) {
4526
            $error = 0;
4527
4528
            $this->db->begin();
4529
4530
            $remise = price2num($remise);
4531
4532
            $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture';
4533
            $sql .= ' SET remise_absolue = '.((float) $remise);
4534
            $sql .= " WHERE rowid = ".((int) $this->id);
4535
            $sql .= ' AND fk_statut = '.self::STATUS_DRAFT;
4536
4537
            dol_syslog(__METHOD__, LOG_DEBUG);
4538
            $resql = $this->db->query($sql);
4539
            if (!$resql) {
4540
                $this->errors[] = $this->db->error();
4541
                $error++;
4542
            }
4543
4544
            if (!$error) {
4545
                $this->oldcopy = clone $this;
4546
                $this->remise_absolue = $remise;
4547
                $this->update_price(1);
4548
            }
4549
4550
            if (!$notrigger && empty($error)) {
4551
                // Call trigger
4552
                $result = $this->call_trigger('BILL_MODIFY', $user);
4553
                if ($result < 0) {
4554
                    $error++;
4555
                }
4556
                // End call triggers
4557
            }
4558
4559
            if (!$error) {
4560
                $this->db->commit();
4561
                return 1;
4562
            } else {
4563
                foreach ($this->errors as $errmsg) {
4564
                    dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
4565
                    $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
4566
                }
4567
                $this->db->rollback();
4568
                return -1 * $error;
4569
            }
4570
        }
4571
4572
        return 0;
4573
    }
4574
    */
4575
4576
    /**
4577
     *      Return next reference of customer invoice not already used (or last reference)
4578
     *      according to numbering module defined into constant FACTURE_ADDON
4579
     *
4580
     *      @param     Societe      $soc        object company
4581
     *      @param     string       $mode       'next' for next value or 'last' for last value
4582
     *      @return    string                   free ref or last ref
4583
     */
4584
    public function getNextNumRef($soc, $mode = 'next')
4585
    {
4586
        global $conf, $langs;
4587
4588
        if ($this->module_source == 'takepos') {
4589
            $langs->load('cashdesk');
4590
4591
            $moduleName = 'takepos';
4592
            $moduleSourceName = 'Takepos';
4593
            $addonConstName = 'TAKEPOS_REF_ADDON';
4594
4595
            // Clean parameters (if not defined or using deprecated value)
4596
            if (!getDolGlobalString('TAKEPOS_REF_ADDON')) {
4597
                $conf->global->TAKEPOS_REF_ADDON = 'mod_takepos_ref_simple';
4598
            }
4599
4600
            $addon = getDolGlobalString('TAKEPOS_REF_ADDON');
4601
        } else {
4602
            $langs->load('bills');
4603
4604
            $moduleName = 'facture';
4605
            $moduleSourceName = 'Invoice';
4606
            $addonConstName = 'FACTURE_ADDON';
4607
4608
            // Clean parameters (if not defined or using deprecated value)
4609
            if (!getDolGlobalString('FACTURE_ADDON')) {
4610
                $conf->global->FACTURE_ADDON = 'mod_facture_terre';
4611
            } elseif (getDolGlobalString('FACTURE_ADDON') == 'terre') {
4612
                $conf->global->FACTURE_ADDON = 'mod_facture_terre';
4613
            } elseif (getDolGlobalString('FACTURE_ADDON') == 'mercure') {
4614
                $conf->global->FACTURE_ADDON = 'mod_facture_mercure';
4615
            }
4616
4617
            $addon = getDolGlobalString('FACTURE_ADDON');
4618
        }
4619
4620
        if (!empty($addon)) {
4621
            dol_syslog("Call getNextNumRef with " . $addonConstName . " = " . getDolGlobalString('FACTURE_ADDON') . ", thirdparty=" . $soc->name . ", type=" . $soc->typent_code . ", mode=" . $mode, LOG_DEBUG);
4622
4623
            $mybool = false;
4624
4625
            $file = $addon . '.php';
4626
            $classname = $addon;
4627
4628
4629
            // Include file with class
4630
            $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
4631
            foreach ($dirmodels as $reldir) {
4632
                $dir = dol_buildpath($reldir . 'core/modules/' . $moduleName . '/');
4633
4634
                // Load file with numbering class (if found)
4635
                if (is_file($dir . $file) && is_readable($dir . $file)) {
4636
                    $mybool = ((bool) include_once $dir . $file) || $mybool;
4637
                }
4638
            }
4639
4640
            // For compatibility
4641
            if (!$mybool) {
4642
                $file = $addon . '/' . $addon . '.modules.php';
4643
                $classname = 'mod_' . $moduleName . '_' . $addon;
4644
                $classname = preg_replace('/\-.*$/', '', $classname);
4645
                // Include file with class
4646
                foreach ($conf->file->dol_document_root as $dirroot) {
4647
                    $dir = $dirroot . '/core/modules/' . $moduleName . '/';
4648
4649
                    // Load file with numbering class (if found)
4650
                    if (is_file($dir . $file) && is_readable($dir . $file)) {
4651
                        $mybool = (include_once $dir . $file) || $mybool;
4652
                    }
4653
                }
4654
            }
4655
4656
            if (!$mybool) {
4657
                dol_print_error(null, 'Failed to include file ' . $file);
4658
                return '';
4659
            }
4660
4661
            $obj = new $classname();
4662
            '@phan-var-force CommonNumRefGenerator $obj';
4663
4664
            $numref = $obj->getNextValue($soc, $this, $mode);
4665
4666
4667
            /**
4668
             * $numref can be empty in case we ask for the last value because if there is no invoice created with the
4669
             * set up mask.
4670
             */
4671
            if ($mode != 'last' && !$numref) {
4672
                $this->error = $obj->error;
4673
                return '';
4674
            }
4675
4676
            return $numref;
4677
        } else {
4678
            $langs->load('errors');
4679
            print $langs->trans('Error') . ' ' . $langs->trans('ErrorModuleSetupNotComplete', $langs->transnoentitiesnoconv($moduleSourceName));
4680
            return '';
4681
        }
4682
    }
4683
4684
    /**
4685
     *  Load miscellaneous information for tab "Info"
4686
     *
4687
     *  @param  int     $id     Id of object to load
4688
     *  @return void
4689
     */
4690
    public function info($id)
4691
    {
4692
        $sql = 'SELECT c.rowid, datec, date_valid as datev, tms as datem,';
4693
        $sql .= ' date_closing as dateclosing,';
4694
        $sql .= ' fk_user_author, fk_user_valid, fk_user_closing';
4695
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'facture as c';
4696
        $sql .= ' WHERE c.rowid = ' . ((int) $id);
4697
4698
        $result = $this->db->query($sql);
4699
        if ($result) {
4700
            if ($this->db->num_rows($result)) {
4701
                $obj = $this->db->fetch_object($result);
4702
4703
                $this->id = $obj->rowid;
4704
                $this->user_creation_id = $obj->fk_user_author;
4705
                $this->user_validation_id = $obj->fk_user_valid;
4706
                $this->user_closing_id = $obj->fk_user_closing;
4707
4708
                $this->date_creation     = $this->db->jdate($obj->datec);
4709
                $this->date_modification = $this->db->jdate($obj->datem);
4710
                $this->date_validation   = $this->db->jdate($obj->datev);
4711
                $this->date_closing      = $this->db->jdate($obj->dateclosing);
4712
            }
4713
            $this->db->free($result);
4714
        } else {
4715
            dol_print_error($this->db);
4716
        }
4717
    }
4718
4719
4720
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4721
    /**
4722
     *  Return list of invoices (eventually filtered on a user) into an array
4723
     *
4724
     *  @param      int     $shortlist      0=Return array[id]=ref, 1=Return array[](id=>id,ref=>ref,name=>name)
4725
     *  @param      int     $draft          0=not draft, 1=draft
4726
     *  @param      User    $excluser       Object user to exclude
4727
     *  @param      int     $socid          Id third party
4728
     *  @param      int     $limit          For pagination
4729
     *  @param      int     $offset         For pagination
4730
     *  @param      string  $sortfield      Sort criteria
4731
     *  @param      string  $sortorder      Sort order
4732
     *  @return     array|int               -1 if KO, array with result if OK
4733
     */
4734
    public function liste_array($shortlist = 0, $draft = 0, $excluser = null, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'f.datef,f.rowid', $sortorder = 'DESC')
4735
    {
4736
		// phpcs:enable
4737
        global $user;
4738
4739
        $ga = array();
4740
4741
        $sql = "SELECT s.rowid, s.nom as name, s.client,";
4742
        $sql .= " f.rowid as fid, f.ref as ref, f.datef as df";
4743
        $sql .= " FROM " . MAIN_DB_PREFIX . "societe as s, " . MAIN_DB_PREFIX . "facture as f";
4744
        $sql .= " WHERE f.entity IN (" . getEntity('invoice') . ")";
4745
        $sql .= " AND f.fk_soc = s.rowid";
4746
        if ($draft) {
4747
            $sql .= " AND f.fk_statut = " . self::STATUS_DRAFT;
4748
        }
4749
        if (is_object($excluser)) {
4750
            $sql .= " AND f.fk_user_author <> " . ((int) $excluser->id);
4751
        }
4752
        // If the internal user must only see his customers, force searching by him
4753
        $search_sale = 0;
4754
        if (!$user->hasRight('societe', 'client', 'voir')) {
4755
            $search_sale = $user->id;
4756
        }
4757
        // Search on sale representative
4758
        if ($search_sale && $search_sale != '-1') {
4759
            if ($search_sale == -2) {
4760
                $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM " . MAIN_DB_PREFIX . "societe_commerciaux as sc WHERE sc.fk_soc = f.fk_soc)";
4761
            } elseif ($search_sale > 0) {
4762
                $sql .= " AND EXISTS (SELECT sc.fk_soc FROM " . MAIN_DB_PREFIX . "societe_commerciaux as sc WHERE sc.fk_soc = f.fk_soc AND sc.fk_user = " . ((int) $search_sale) . ")";
4763
            }
4764
        }
4765
        // Search on socid
4766
        if ($socid) {
4767
            $sql .= " AND f.fk_soc = " . ((int) $socid);
4768
        }
4769
        $sql .= $this->db->order($sortfield, $sortorder);
4770
        $sql .= $this->db->plimit($limit, $offset);
4771
4772
        $result = $this->db->query($sql);
4773
        if ($result) {
4774
            $numc = $this->db->num_rows($result);
4775
            if ($numc) {
4776
                $i = 0;
4777
                while ($i < $numc) {
4778
                    $obj = $this->db->fetch_object($result);
4779
4780
                    if ($shortlist == 1) {
4781
                        $ga[$obj->fid] = $obj->ref;
4782
                    } elseif ($shortlist == 2) {
4783
                        $ga[$obj->fid] = $obj->ref . ' (' . $obj->name . ')';
4784
                    } else {
4785
                        $ga[$i]['id'] = $obj->fid;
4786
                        $ga[$i]['ref']  = $obj->ref;
4787
                        $ga[$i]['name'] = $obj->name;
4788
                    }
4789
                    $i++;
4790
                }
4791
            }
4792
            return $ga;
4793
        } else {
4794
            dol_print_error($this->db);
4795
            return -1;
4796
        }
4797
    }
4798
4799
4800
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4801
    /**
4802
     *  Return list of invoices qualified to be replaced by another invoice.
4803
     *  Invoices matching the following rules are returned:
4804
     *  (Status validated or abandoned for a reason 'other') + not paid + no payment at all + not already replaced
4805
     *
4806
     *  @param      int         $socid      Id thirdparty
4807
     *  @return     array|int               Array of invoices ('id'=>id, 'ref'=>ref, 'status'=>status, 'paymentornot'=>0/1)
4808
     */
4809
    public function list_replacable_invoices($socid = 0)
4810
    {
4811
		// phpcs:enable
4812
        global $conf;
4813
4814
        $return = array();
4815
4816
        $sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut as status, f.paye as paid,";
4817
        $sql .= " ff.rowid as rowidnext";
4818
        //$sql .= ", SUM(pf.amount) as alreadypaid";
4819
        $sql .= " FROM " . MAIN_DB_PREFIX . "facture as f";
4820
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "paiement_facture as pf ON f.rowid = pf.fk_facture";
4821
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "facture as ff ON f.rowid = ff.fk_facture_source";
4822
        $sql .= " WHERE (f.fk_statut = " . self::STATUS_VALIDATED . " OR (f.fk_statut = " . self::STATUS_ABANDONED . " AND f.close_code = '" . self::CLOSECODE_ABANDONED . "'))";
4823
        $sql .= " AND f.entity IN (" . getEntity('invoice') . ")";
4824
        $sql .= " AND f.paye = 0"; // Not paid completely
4825
        $sql .= " AND pf.fk_paiement IS NULL"; // No payment already done
4826
        $sql .= " AND ff.fk_statut IS NULL"; // Return true if it is not a replacement invoice
4827
        if ($socid > 0) {
4828
            $sql .= " AND f.fk_soc = " . ((int) $socid);
4829
        }
4830
        //$sql .= " GROUP BY f.rowid, f.ref, f.fk_statut, f.paye, ff.rowid";
4831
        $sql .= " ORDER BY f.ref";
4832
4833
        dol_syslog(get_only_class($this) . "::list_replacable_invoices", LOG_DEBUG);
4834
        $resql = $this->db->query($sql);
4835
        if ($resql) {
4836
            while ($obj = $this->db->fetch_object($resql)) {
4837
                $return[$obj->rowid] = array(
4838
                    'id' => $obj->rowid,
4839
                    'ref' => $obj->ref,
4840
                    'status' => $obj->status,
4841
                    'paid' => $obj->paid,
4842
                    'alreadypaid' => 0
4843
                );
4844
            }
4845
            //print_r($return);
4846
            return $return;
4847
        } else {
4848
            $this->error = $this->db->error();
4849
            return -1;
4850
        }
4851
    }
4852
4853
4854
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4855
    /**
4856
     *  Return list of invoices qualified to be corrected by a credit note.
4857
     *  Invoices matching the following rules are returned:
4858
     *  (validated + payment on process) or classified (paid completely or paid partiely) + not already replaced + not already a credit note
4859
     *
4860
     *  @param      int         $socid      Id thirdparty
4861
     *  @return     array|int               Array of invoices ($id => array('ref'=>,'paymentornot'=>,'status'=>,'paye'=>)
4862
     */
4863
    public function list_qualified_avoir_invoices($socid = 0)
4864
    {
4865
		// phpcs:enable
4866
        global $conf;
4867
4868
        $return = array();
4869
4870
4871
        $sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut, f.type, f.subtype, f.paye, pf.fk_paiement";
4872
        $sql .= " FROM " . MAIN_DB_PREFIX . "facture as f";
4873
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "paiement_facture as pf ON f.rowid = pf.fk_facture";
4874
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "facture as ff ON (f.rowid = ff.fk_facture_source AND ff.type=" . self::TYPE_REPLACEMENT . ")";
4875
        $sql .= " WHERE f.entity IN (" . getEntity('invoice') . ")";
4876
        $sql .= " AND f.fk_statut in (" . self::STATUS_VALIDATED . "," . self::STATUS_CLOSED . ")";
4877
        //  $sql.= " WHERE f.fk_statut >= 1";
4878
        //  $sql.= " AND (f.paye = 1";              // Classee payee completement
4879
        //  $sql.= " OR f.close_code IS NOT NULL)"; // Classee payee partiellement
4880
        $sql .= " AND ff.type IS NULL"; // Renvoi vrai si pas facture de replacement
4881
        $sql .= " AND f.type <> " . self::TYPE_CREDIT_NOTE; // Exclude credit note invoices from selection
4882
4883
        if (getDolGlobalString('INVOICE_USE_SITUATION_CREDIT_NOTE')) {
4884
            // Keep invoices that are not situation invoices or that are the last in series if it is a situation invoice
4885
            $sql .= " AND (f.type <> " . self::TYPE_SITUATION . " OR f.rowid IN ";
4886
            $sql .= '(SELECT MAX(fs.rowid)'; // This select returns several ID because of the group by later
4887
            $sql .= " FROM " . MAIN_DB_PREFIX . "facture as fs";
4888
            $sql .= " WHERE fs.entity IN (" . getEntity('invoice') . ")";
4889
            $sql .= " AND fs.type = " . self::TYPE_SITUATION;
4890
            $sql .= " AND fs.fk_statut IN (" . self::STATUS_VALIDATED . "," . self::STATUS_CLOSED . ")";
4891
            if ($socid > 0) {
4892
                $sql .= " AND fs.fk_soc = " . ((int) $socid);
4893
            }
4894
            $sql .= " GROUP BY fs.situation_cycle_ref)"; // For each situation_cycle_ref, we take the higher rowid
4895
            $sql .= ")";
4896
        } else {
4897
            $sql .= " AND f.type <> " . self::TYPE_SITUATION; // Keep invoices that are not situation invoices
4898
        }
4899
4900
        if ($socid > 0) {
4901
            $sql .= " AND f.fk_soc = " . ((int) $socid);
4902
        }
4903
        $sql .= " ORDER BY f.ref";
4904
4905
        dol_syslog(get_only_class($this) . "::list_qualified_avoir_invoices", LOG_DEBUG);
4906
        $resql = $this->db->query($sql);
4907
        if ($resql) {
4908
            while ($obj = $this->db->fetch_object($resql)) {
4909
                $qualified = 0;
4910
                if ($obj->fk_statut == self::STATUS_VALIDATED) {
4911
                    $qualified = 1;
4912
                }
4913
                if ($obj->fk_statut == self::STATUS_CLOSED) {
4914
                    $qualified = 1;
4915
                }
4916
                if ($qualified) {
4917
                    //$ref=$obj->ref;
4918
                    $paymentornot = ($obj->fk_paiement ? 1 : 0);
4919
                    $return[$obj->rowid] = array('ref' => $obj->ref, 'status' => $obj->fk_statut, 'type' => $obj->type, 'paye' => $obj->paye, 'paymentornot' => $paymentornot);
4920
                }
4921
            }
4922
4923
            return $return;
4924
        } else {
4925
            $this->error = $this->db->error();
4926
            return -1;
4927
        }
4928
    }
4929
4930
4931
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
4932
    /**
4933
     *  Load indicators for dashboard (this->nbtodo and this->nbtodolate)
4934
     *
4935
     *  @param  User                    $user       Object user
4936
     *  @return WorkboardResponse|int               Return integer <0 if KO, WorkboardResponse if OK
4937
     */
4938
    public function load_board($user)
4939
    {
4940
		// phpcs:enable
4941
        global $conf, $langs;
4942
4943
        $clause = " WHERE";
4944
4945
        $sql = "SELECT f.rowid, f.date_lim_reglement as datefin, f.fk_statut as status, f.total_ht";
4946
        $sql .= " FROM " . MAIN_DB_PREFIX . "facture as f";
4947
        if (!$user->hasRight('societe', 'client', 'voir')) {
4948
            $sql .= " JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON f.fk_soc = sc.fk_soc";
4949
            $sql .= " WHERE sc.fk_user = " . ((int) $user->id);
4950
            $clause = " AND";
4951
        }
4952
        $sql .= $clause . " f.paye=0";
4953
        $sql .= " AND f.entity IN (" . getEntity('invoice') . ")";
4954
        $sql .= " AND f.fk_statut = " . self::STATUS_VALIDATED;
4955
        if ($user->socid) {
4956
            $sql .= " AND f.fk_soc = " . ((int) $user->socid);
4957
        }
4958
4959
        $resql = $this->db->query($sql);
4960
        if ($resql) {
4961
            $langs->load("bills");
4962
            $now = dol_now();
4963
4964
            $response = new WorkboardResponse();
4965
            $response->warning_delay = $conf->facture->client->warning_delay / 60 / 60 / 24;
4966
            $response->label = $langs->trans("CustomerBillsUnpaid");
4967
            $response->labelShort = $langs->trans("Unpaid");
4968
            $response->url = constant('BASE_URL') . '/compta/facture/list.php?search_status=1&mainmenu=billing&leftmenu=customers_bills';
4969
            $response->img = img_object('', "bill");
4970
4971
            $generic_facture = new Facture($this->db);
4972
4973
            while ($obj = $this->db->fetch_object($resql)) {
4974
                $generic_facture->date_lim_reglement = $this->db->jdate($obj->datefin);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->db->jdate($obj->datefin) can also be of type string. However, the property $date_lim_reglement is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
4975
                $generic_facture->statut = $obj->status;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$statut has been deprecated: Use $status instead. ( Ignorable by Annotation )

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

4975
                /** @scrutinizer ignore-deprecated */ $generic_facture->statut = $obj->status;

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...
4976
                $generic_facture->status = $obj->status;
4977
4978
                $response->nbtodo++;
4979
                $response->total += $obj->total_ht;
4980
4981
                if ($generic_facture->hasDelay()) {
4982
                    $response->nbtodolate++;
4983
                    $response->url_late = constant('BASE_URL') . '/compta/facture/list.php?search_option=late&mainmenu=billing&leftmenu=customers_bills';
4984
                }
4985
            }
4986
4987
            $this->db->free($resql);
4988
            return $response;
4989
        } else {
4990
            dol_print_error($this->db);
4991
            $this->error = $this->db->error();
4992
            return -1;
4993
        }
4994
    }
4995
4996
4997
    /* gestion des contacts d'une facture */
4998
4999
    /**
5000
     *  Retourne id des contacts clients de facturation
5001
     *
5002
     *  @return     array       Liste des id contacts facturation
5003
     */
5004
    public function getIdBillingContact()
5005
    {
5006
        return $this->getIdContact('external', 'BILLING');
5007
    }
5008
5009
    /**
5010
     *  Retourne id des contacts clients de livraison
5011
     *
5012
     *  @return     array       Liste des id contacts livraison
5013
     */
5014
    public function getIdShippingContact()
5015
    {
5016
        return $this->getIdContact('external', 'SHIPPING');
5017
    }
5018
5019
5020
    /**
5021
     *  Initialise an instance with random values.
5022
     *  Used to build previews or test instances.
5023
     *  id must be 0 if object instance is a specimen.
5024
     *
5025
     *  @param  string      $option     ''=Create a specimen invoice with lines, 'nolines'=No lines
5026
     *  @return int
5027
     */
5028
    public function initAsSpecimen($option = '')
5029
    {
5030
        global $conf, $langs, $user;
5031
5032
        $now = dol_now();
5033
        $arraynow = dol_getdate($now);
5034
        $nownotime = dol_mktime(0, 0, 0, $arraynow['mon'], $arraynow['mday'], $arraynow['year']);
5035
5036
        // Load array of products prodids
5037
        $num_prods = 0;
5038
        $prodids = array();
5039
        $sql = "SELECT rowid";
5040
        $sql .= " FROM " . MAIN_DB_PREFIX . "product";
5041
        $sql .= " WHERE entity IN (" . getEntity('product') . ")";
5042
        $sql .= $this->db->plimit(100);
5043
5044
        $resql = $this->db->query($sql);
5045
        if ($resql) {
5046
            $num_prods = $this->db->num_rows($resql);
5047
            $i = 0;
5048
            while ($i < $num_prods) {
5049
                $i++;
5050
                $row = $this->db->fetch_row($resql);
5051
                $prodids[$i] = $row[0];
5052
            }
5053
        }
5054
        //Avoid php warning Warning: mt_rand(): max(0) is smaller than min(1) when no product exists
5055
        if (empty($num_prods)) {
5056
            $num_prods = 1;
5057
        }
5058
5059
        // Initialize parameters
5060
        $this->id = 0;
5061
        $this->entity = 1;
5062
        $this->ref = 'SPECIMEN';
5063
        $this->specimen = 1;
5064
        $this->socid = 1;
5065
        $this->date = $nownotime;
0 ignored issues
show
Documentation Bug introduced by
It seems like $nownotime can also be of type string. However, the property $date is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
5066
        $this->date_lim_reglement = $nownotime + 3600 * 24 * 30;
5067
        $this->cond_reglement_id   = 1;
5068
        $this->cond_reglement_code = 'RECEP';
5069
        $this->date_lim_reglement = $this->calculate_date_lim_reglement();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->calculate_date_lim_reglement() can also be of type string. However, the property $date_lim_reglement is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
5070
        $this->mode_reglement_id   = 0; // Not forced to show payment mode CHQ + VIR
5071
        $this->mode_reglement_code = ''; // Not forced to show payment mode CHQ + VIR
5072
5073
        $this->note_public = 'This is a comment (public)';
5074
        $this->note_private = 'This is a comment (private)';
5075
        $this->note = 'This is a comment (private)';
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$note has been deprecated: Use $note_private instead. ( Ignorable by Annotation )

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

5075
        /** @scrutinizer ignore-deprecated */ $this->note = 'This is a comment (private)';

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...
5076
5077
        $this->fk_user_author = $user->id;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Compta\Cla...acture::$fk_user_author has been deprecated: Use $user_creation_id ( Ignorable by Annotation )

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

5077
        /** @scrutinizer ignore-deprecated */ $this->fk_user_author = $user->id;

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...
5078
5079
        $this->multicurrency_tx = 1;
5080
        $this->multicurrency_code = $conf->currency;
5081
5082
        $this->fk_incoterms = 0;
5083
        $this->location_incoterms = '';
5084
5085
        if (empty($option) || $option != 'nolines') {
5086
            // Lines
5087
            $nbp = 5;
5088
            $xnbp = 0;
5089
            while ($xnbp < $nbp) {
5090
                $line = new FactureLigne($this->db);
5091
                $line->desc = $langs->trans("Description") . " " . $xnbp;
5092
                $line->qty = 1;
5093
                $line->subprice = 100;
5094
                $line->tva_tx = 19.6;
5095
                $line->localtax1_tx = 0;
5096
                $line->localtax2_tx = 0;
5097
                $line->remise_percent = 0;
5098
                if ($xnbp == 1) {        // Qty is negative (product line)
5099
                    $prodid = mt_rand(1, $num_prods);
5100
                    $line->fk_product = $prodids[$prodid];
5101
                    $line->qty = -1;
5102
                    $line->total_ht = -100;
5103
                    $line->total_ttc = -119.6;
5104
                    $line->total_tva = -19.6;
5105
                    $line->multicurrency_total_ht = -200;
5106
                    $line->multicurrency_total_ttc = -239.2;
5107
                    $line->multicurrency_total_tva = -39.2;
5108
                } elseif ($xnbp == 2) {    // UP is negative (free line)
5109
                    $line->subprice = -100;
5110
                    $line->total_ht = -100;
5111
                    $line->total_ttc = -119.6;
5112
                    $line->total_tva = -19.6;
5113
                    $line->remise_percent = 0;
5114
                    $line->multicurrency_total_ht = -200;
5115
                    $line->multicurrency_total_ttc = -239.2;
5116
                    $line->multicurrency_total_tva = -39.2;
5117
                } elseif ($xnbp == 3) {    // Discount is 50% (product line)
5118
                    $prodid = mt_rand(1, $num_prods);
5119
                    $line->fk_product = $prodids[$prodid];
5120
                    $line->total_ht = 50;
5121
                    $line->total_ttc = 59.8;
5122
                    $line->total_tva = 9.8;
5123
                    $line->multicurrency_total_ht = 100;
5124
                    $line->multicurrency_total_ttc = 119.6;
5125
                    $line->multicurrency_total_tva = 19.6;
5126
                    $line->remise_percent = 50;
5127
                } else { // (product line)
5128
                    $prodid = mt_rand(1, $num_prods);
5129
                    $line->fk_product = $prodids[$prodid];
5130
                    $line->total_ht = 100;
5131
                    $line->total_ttc = 119.6;
5132
                    $line->total_tva = 19.6;
5133
                    $line->multicurrency_total_ht = 200;
5134
                    $line->multicurrency_total_ttc = 239.2;
5135
                    $line->multicurrency_total_tva = 39.2;
5136
                    $line->remise_percent = 0;
5137
                }
5138
5139
                $this->lines[$xnbp] = $line;
5140
5141
5142
                $this->total_ht       += $line->total_ht;
5143
                $this->total_tva      += $line->total_tva;
5144
                $this->total_ttc      += $line->total_ttc;
5145
5146
                $this->multicurrency_total_ht       += $line->multicurrency_total_ht;
5147
                $this->multicurrency_total_tva      += $line->multicurrency_total_tva;
5148
                $this->multicurrency_total_ttc      += $line->multicurrency_total_ttc;
5149
5150
                $xnbp++;
5151
            }
5152
            $this->revenuestamp = 0;
5153
5154
            // Add a line "offered"
5155
            $line = new FactureLigne($this->db);
5156
            $line->desc = $langs->trans("Description") . " (offered line)";
5157
            $line->qty = 1;
5158
            $line->subprice = 100;
5159
            $line->tva_tx = 19.6;
5160
            $line->localtax1_tx = 0;
5161
            $line->localtax2_tx = 0;
5162
            $line->remise_percent = 100;
5163
            $line->total_ht = 0;
5164
            $line->total_ttc = 0; // 90 * 1.196
5165
            $line->total_tva = 0;
5166
            $line->multicurrency_total_ht = 0;
5167
            $line->multicurrency_total_ttc = 0;
5168
            $line->multicurrency_total_tva = 0;
5169
            $prodid = mt_rand(1, $num_prods);
5170
            $line->fk_product = $prodids[$prodid];
5171
5172
            $this->lines[$xnbp] = $line;
5173
            $xnbp++;
5174
        }
5175
5176
        return 1;
5177
    }
5178
5179
    /**
5180
     *      Load indicators for dashboard (this->nbtodo and this->nbtodolate)
5181
     *
5182
     *      @return         int     Return integer <0 if KO, >0 if OK
5183
     */
5184
    public function loadStateBoard()
5185
    {
5186
        global $conf, $user;
5187
5188
        $this->nb = array();
5189
5190
        $clause = "WHERE";
5191
5192
        $sql = "SELECT count(f.rowid) as nb";
5193
        $sql .= " FROM " . MAIN_DB_PREFIX . "facture as f";
5194
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON f.fk_soc = s.rowid";
5195
        if (!$user->hasRight('societe', 'client', 'voir')) {
5196
            $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON s.rowid = sc.fk_soc";
5197
            $sql .= " WHERE sc.fk_user = " . ((int) $user->id);
5198
            $clause = "AND";
5199
        }
5200
        $sql .= " " . $clause . " f.entity IN (" . getEntity('invoice') . ")";
5201
5202
        $resql = $this->db->query($sql);
5203
        if ($resql) {
5204
            while ($obj = $this->db->fetch_object($resql)) {
5205
                $this->nb["invoices"] = $obj->nb;
5206
            }
5207
            $this->db->free($resql);
5208
            return 1;
5209
        } else {
5210
            dol_print_error($this->db);
5211
            $this->error = $this->db->error();
5212
            return -1;
5213
        }
5214
    }
5215
5216
    /**
5217
     *  Create an array of invoice lines
5218
     *
5219
     *  @return int     >0 if OK, <0 if KO
5220
     */
5221
    public function getLinesArray()
5222
    {
5223
        return $this->fetch_lines();
5224
    }
5225
5226
    /**
5227
     *  Create a document onto disk according to template module.
5228
     *
5229
     *  @param  string      $modele         Generator to use. Caller must set it to obj->model_pdf or GETPOST('model','alpha') for example.
5230
     *  @param  Translate   $outputlangs    Object lang to use for translation
5231
     *  @param  int         $hidedetails    Hide details of lines
5232
     *  @param  int         $hidedesc       Hide description
5233
     *  @param  int         $hideref        Hide ref
5234
     *  @param  null|array  $moreparams     Array to provide more information
5235
     *  @return int                         Return integer <0 if KO, >0 if OK
5236
     */
5237
    public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
5238
    {
5239
        global $conf, $langs;
5240
5241
        $outputlangs->loadLangs(array("bills", "products"));
5242
5243
        if (!dol_strlen($modele)) {
5244
            $modele = 'crabe';
5245
            $thisTypeConfName = 'FACTURE_ADDON_PDF_' . $this->type;
5246
5247
            if (!empty($this->model_pdf)) {
5248
                $modele = $this->model_pdf;
5249
            } elseif (getDolGlobalString($thisTypeConfName)) {
5250
                $modele = getDolGlobalString($thisTypeConfName);
5251
            } elseif (getDolGlobalString('FACTURE_ADDON_PDF')) {
5252
                $modele = getDolGlobalString('FACTURE_ADDON_PDF');
5253
            }
5254
        }
5255
5256
        $modelpath = "core/modules/facture/doc/";
5257
5258
        return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
5259
    }
5260
5261
    /**
5262
     * Gets the smallest reference available for a new cycle
5263
     *
5264
     * @return int >= 1 if OK, -1 if error
5265
     */
5266
    public function newCycle()
5267
    {
5268
        $sql = "SELECT max(situation_cycle_ref) as maxsituationref";
5269
        $sql .= " FROM " . MAIN_DB_PREFIX . "facture as f";
5270
        $sql .= " WHERE f.entity IN (" . getEntity('invoice', 0) . ")";
5271
5272
        $resql = $this->db->query($sql);
5273
        if ($resql) {
5274
            if ($this->db->num_rows($resql) > 0) {
5275
                $ref = 0;
5276
                $obj = $this->db->fetch_object($resql);
5277
                if ($obj) {
5278
                    $ref = $obj->maxsituationref;
5279
                }
5280
                $ref++;
5281
            } else {
5282
                $ref = 1;
5283
            }
5284
            $this->db->free($resql);
5285
            return $ref;
5286
        } else {
5287
            $this->error = $this->db->lasterror();
5288
            dol_syslog("Error sql=" . $sql . ", error=" . $this->error, LOG_ERR);
5289
            return -1;
5290
        }
5291
    }
5292
5293
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5294
    /**
5295
     * Checks if the invoice is the first of a cycle
5296
     *
5297
     * @return boolean
5298
     */
5299
    public function is_first()
5300
    {
5301
		// phpcs:enable
5302
        return ($this->situation_counter == 1);
5303
    }
5304
5305
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5306
    /**
5307
     * Returns an array containing the previous situations as Facture objects
5308
     *
5309
     * @return mixed -1 if error, array of previous situations
5310
     */
5311
    public function get_prev_sits()
5312
    {
5313
		// phpcs:enable
5314
        global $conf;
5315
5316
        $sql = 'SELECT rowid FROM ' . MAIN_DB_PREFIX . 'facture';
5317
        $sql .= ' WHERE situation_cycle_ref = ' . ((int) $this->situation_cycle_ref);
5318
        $sql .= ' AND situation_counter < ' . ((int) $this->situation_counter);
5319
        $sql .= ' AND entity = ' . ($this->entity > 0 ? $this->entity : $conf->entity);
5320
        $resql = $this->db->query($sql);
5321
        $res = array();
5322
        if ($resql && $this->db->num_rows($resql) > 0) {
5323
            while ($row = $this->db->fetch_object($resql)) {
5324
                $id = $row->rowid;
5325
                $situation = new Facture($this->db);
5326
                $situation->fetch($id);
5327
                $res[] = $situation;
5328
            }
5329
        } else {
5330
            $this->error = $this->db->error();
5331
            dol_syslog("Error sql=" . $sql . ", error=" . $this->error, LOG_ERR);
5332
            return -1;
5333
        }
5334
5335
        return $res;
5336
    }
5337
5338
    /**
5339
     * Sets the invoice as a final situation
5340
     *
5341
     *  @param      User    $user       Object user
5342
     *  @param      int     $notrigger  1=Does not execute triggers, 0= execute triggers
5343
     *  @return     int                 Return integer <0 if KO, >0 if OK
5344
     */
5345
    public function setFinal(User $user, $notrigger = 0)
5346
    {
5347
        $error = 0;
5348
5349
        $this->db->begin();
5350
5351
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture SET situation_final = ' . ((int) $this->situation_final) . ' WHERE rowid = ' . ((int) $this->id);
5352
5353
        dol_syslog(__METHOD__, LOG_DEBUG);
5354
        $resql = $this->db->query($sql);
5355
        if (!$resql) {
5356
            $this->errors[] = $this->db->error();
5357
            $error++;
5358
        }
5359
5360
        if (!$notrigger && empty($error)) {
5361
            // Call trigger
5362
            $result = $this->call_trigger('BILL_MODIFY', $user);
5363
            if ($result < 0) {
5364
                $error++;
5365
            }
5366
            // End call triggers
5367
        }
5368
5369
        if (!$error) {
5370
            $this->db->commit();
5371
            return 1;
5372
        } else {
5373
            foreach ($this->errors as $errmsg) {
5374
                dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
5375
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
5376
            }
5377
            $this->db->rollback();
5378
            return -1 * $error;
5379
        }
5380
    }
5381
5382
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5383
    /**
5384
     * Checks if the invoice is the last in its cycle
5385
     *
5386
     * @return bool Last of the cycle status
5387
     */
5388
    public function is_last_in_cycle()
5389
    {
5390
		// phpcs:enable
5391
        global $conf;
5392
5393
        if (!empty($this->situation_cycle_ref)) {
5394
            // No point in testing anything if we're not inside a cycle
5395
            $sql = 'SELECT max(situation_counter) FROM ' . MAIN_DB_PREFIX . 'facture';
5396
            $sql .= ' WHERE situation_cycle_ref = ' . ((int) $this->situation_cycle_ref);
5397
            $sql .= ' AND entity = ' . ($this->entity > 0 ? $this->entity : $conf->entity);
5398
            $resql = $this->db->query($sql);
5399
5400
            if ($resql && $this->db->num_rows($resql) > 0 && $res = $this->db->fetch_array($resql)) {
5401
                $last = $res['max(situation_counter)'];
5402
                return ($last == $this->situation_counter);
5403
            } else {
5404
                $this->error = $this->db->lasterror();
5405
                dol_syslog(get_only_class($this) . "::select Error " . $this->error, LOG_ERR);
5406
                return false;
5407
            }
5408
        } else {
5409
            return true;
5410
        }
5411
    }
5412
5413
    /**
5414
     * Replace a thirdparty id with another one.
5415
     *
5416
     * @param   DoliDB  $dbs        Database handler, because function is static we name it $dbs not $db to avoid breaking coding test
5417
     * @param   int     $origin_id  Old thirdparty id
5418
     * @param   int     $dest_id    New thirdparty id
5419
     * @return  bool
5420
     */
5421
    public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
5422
    {
5423
        $tables = array(
5424
            'facture'
5425
        );
5426
5427
        return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
5428
    }
5429
5430
    /**
5431
     * Replace a product id with another one.
5432
     *
5433
     * @param DoliDB $db Database handler
5434
     * @param int $origin_id Old product id
5435
     * @param int $dest_id New product id
5436
     * @return bool
5437
     */
5438
    public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
5439
    {
5440
        $tables = array(
5441
            'facturedet'
5442
        );
5443
5444
        return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
5445
    }
5446
5447
    /**
5448
     * Is the customer invoice delayed?
5449
     *
5450
     * @return bool
5451
     */
5452
    public function hasDelay()
5453
    {
5454
        global $conf;
5455
5456
        $now = dol_now();
5457
5458
        // Paid invoices have status STATUS_CLOSED
5459
        if ($this->status != Facture::STATUS_VALIDATED) {
5460
            return false;
5461
        }
5462
5463
        $hasDelay = $this->date_lim_reglement < ($now - $conf->facture->client->warning_delay);
5464
        if ($hasDelay && !empty($this->retained_warranty) && !empty($this->retained_warranty_date_limit)) {
5465
            $totalpaid = $this->getSommePaiement();
5466
            $totalpaid = (float) $totalpaid;
5467
            $RetainedWarrantyAmount = $this->getRetainedWarrantyAmount();
5468
            if ($totalpaid >= 0 && $RetainedWarrantyAmount >= 0) {
5469
                if (($totalpaid < $this->total_ttc - $RetainedWarrantyAmount) && $this->date_lim_reglement < ($now - $conf->facture->client->warning_delay)) {
5470
                    $hasDelay = 1;
5471
                } elseif ($totalpaid < $this->total_ttc && $this->retained_warranty_date_limit < ($now - $conf->facture->client->warning_delay)) {
5472
                    $hasDelay = 1;
5473
                } else {
5474
                    $hasDelay = 0;
5475
                }
5476
            }
5477
        }
5478
5479
        return $hasDelay;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $hasDelay also could return the type integer which is incompatible with the documented return type boolean.
Loading history...
5480
    }
5481
5482
    /**
5483
     * Currently used for documents generation : to know if retained warranty need to be displayed
5484
     * @return bool
5485
     */
5486
    public function displayRetainedWarranty()
5487
    {
5488
        global $conf;
5489
5490
        // TODO : add a flag on invoices to store this conf : INVOICE_RETAINED_WARRANTY_LIMITED_TO_FINAL_SITUATION
5491
5492
        // note : we don't need to test INVOICE_USE_RETAINED_WARRANTY because if $this->retained_warranty is not empty it's because it was set when this conf was active
5493
5494
        $displayWarranty = false;
5495
        if (!empty($this->retained_warranty)) {
5496
            $displayWarranty = true;
5497
5498
            if ($this->type == Facture::TYPE_SITUATION && getDolGlobalString('INVOICE_RETAINED_WARRANTY_LIMITED_TO_FINAL_SITUATION')) {
5499
                // Check if this situation invoice is 100% for real
5500
                $displayWarranty = false;
5501
                if (!empty($this->situation_final)) {
5502
                    $displayWarranty = true;
5503
                } elseif (!empty($this->lines) && $this->status == Facture::STATUS_DRAFT) {
5504
                    // $object->situation_final need validation to be done so this test is need for draft
5505
                    $displayWarranty = true;
5506
5507
                    foreach ($this->lines as $i => $line) {
5508
                        if ($line->product_type < 2 && $line->situation_percent < 100) {
0 ignored issues
show
Bug Best Practice introduced by
The property situation_percent does not exist on Dolibarr\Code\Core\Classes\CommonInvoiceLine. Since you implemented __get, consider adding a @property annotation.
Loading history...
5509
                            $displayWarranty = false;
5510
                            break;
5511
                        }
5512
                    }
5513
                }
5514
            }
5515
        }
5516
5517
        return $displayWarranty;
5518
    }
5519
5520
    /**
5521
     * @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)
5522
     * @return float or -1 if not available
5523
     */
5524
    public function getRetainedWarrantyAmount($rounding = -1)
5525
    {
5526
        global $conf;
5527
        if (empty($this->retained_warranty)) {
5528
            return -1;
5529
        }
5530
5531
        $retainedWarrantyAmount = 0;
5532
5533
        // Billed - retained warranty
5534
        if ($this->type == Facture::TYPE_SITUATION && getDolGlobalString('INVOICE_RETAINED_WARRANTY_LIMITED_TO_FINAL_SITUATION')) {
5535
            $displayWarranty = true;
5536
            // Check if this situation invoice is 100% for real
5537
            if (!empty($this->lines)) {
5538
                foreach ($this->lines as $i => $line) {
5539
                    if ($line->product_type < 2 && $line->situation_percent < 100) {
0 ignored issues
show
Bug Best Practice introduced by
The property situation_percent does not exist on Dolibarr\Code\Core\Classes\CommonInvoiceLine. Since you implemented __get, consider adding a @property annotation.
Loading history...
5540
                        $displayWarranty = false;
5541
                        break;
5542
                    }
5543
                }
5544
            }
5545
5546
            if ($displayWarranty && !empty($this->situation_final)) {
5547
                $this->fetchPreviousNextSituationInvoice();
5548
                $TPreviousIncoice = $this->tab_previous_situation_invoice;
5549
5550
                $total2BillWT = 0;
5551
                foreach ($TPreviousIncoice as &$fac) {
5552
                    $total2BillWT += $fac->total_ttc;
5553
                }
5554
                $total2BillWT += $this->total_ttc;
5555
5556
                $retainedWarrantyAmount = $total2BillWT * $this->retained_warranty / 100;
5557
            } else {
5558
                return -1;
5559
            }
5560
        } else {
5561
            // Because one day retained warranty could be used on standard invoices
5562
            $retainedWarrantyAmount = $this->total_ttc * $this->retained_warranty / 100;
5563
        }
5564
5565
        if ($rounding < 0) {
5566
            $rounding = min(getDolGlobalString('MAIN_MAX_DECIMALS_UNIT'), getDolGlobalString('MAIN_MAX_DECIMALS_TOT'));
5567
        }
5568
5569
        if ($rounding > 0) {
5570
            return round($retainedWarrantyAmount, $rounding);
5571
        }
5572
5573
        return $retainedWarrantyAmount;
5574
    }
5575
5576
    /**
5577
     *  Change the retained warranty
5578
     *
5579
     *  @param      float       $value      value of retained warranty
5580
     *  @return     int             >0 if OK, <0 if KO
5581
     */
5582
    public function setRetainedWarranty($value)
5583
    {
5584
        dol_syslog(get_only_class($this) . '::setRetainedWarranty(' . $value . ')');
5585
5586
        if ($this->status >= 0) {
5587
            $fieldname = 'retained_warranty';
5588
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element;
5589
            $sql .= " SET " . $fieldname . " = " . ((float) $value);
5590
            $sql .= ' WHERE rowid=' . ((int) $this->id);
5591
5592
            if ($this->db->query($sql)) {
5593
                $this->retained_warranty = (float) $value;
5594
                return 1;
5595
            } else {
5596
                dol_syslog(get_only_class($this) . '::setRetainedWarranty Erreur ' . $sql . ' - ' . $this->db->error());
5597
                $this->error = $this->db->error();
5598
                return -1;
5599
            }
5600
        } else {
5601
            dol_syslog(get_only_class($this) . '::setRetainedWarranty, status of the object is incompatible');
5602
            $this->error = 'Status of the object is incompatible ' . $this->status;
5603
            return -2;
5604
        }
5605
    }
5606
5607
5608
    /**
5609
     *  Change the retained_warranty_date_limit
5610
     *
5611
     *  @param      int     $timestamp      date limit of retained warranty in timestamp format
5612
     *  @param      string  $dateYmd        date limit of retained warranty in Y m d format
5613
     *  @return     int             >0 if OK, <0 if KO
5614
     */
5615
    public function setRetainedWarrantyDateLimit($timestamp, $dateYmd = '')
5616
    {
5617
        if (!$timestamp && $dateYmd) {
5618
            $timestamp = $this->db->jdate($dateYmd);
5619
        }
5620
5621
5622
        dol_syslog(get_only_class($this) . '::setRetainedWarrantyDateLimit(' . $timestamp . ')');
5623
        if ($this->status >= 0) {
5624
            $fieldname = 'retained_warranty_date_limit';
5625
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element;
5626
            $sql .= " SET " . $fieldname . " = " . (strval($timestamp) != '' ? "'" . $this->db->idate($timestamp) . "'" : 'null');
5627
            $sql .= ' WHERE rowid = ' . ((int) $this->id);
5628
5629
            if ($this->db->query($sql)) {
5630
                $this->retained_warranty_date_limit = $timestamp;
0 ignored issues
show
Documentation Bug introduced by
It seems like $timestamp can also be of type string. However, the property $retained_warranty_date_limit is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
5631
                return 1;
5632
            } else {
5633
                dol_syslog(get_only_class($this) . '::setRetainedWarrantyDateLimit Erreur ' . $sql . ' - ' . $this->db->error());
5634
                $this->error = $this->db->error();
5635
                return -1;
5636
            }
5637
        } else {
5638
            dol_syslog(get_only_class($this) . '::setRetainedWarrantyDateLimit, status of the object is incompatible');
5639
            $this->error = 'Status of the object is incompatible ' . $this->status;
5640
            return -2;
5641
        }
5642
    }
5643
5644
5645
    /**
5646
     *  Send reminders by emails for invoices validated that are due.
5647
     *  CAN BE A CRON TASK
5648
     *
5649
     *  @param  int         $nbdays             Delay before due date (or after if delay is negative)
5650
     *  @param  string      $paymentmode        '' or 'all' by default (no filter), or 'LIQ', 'CHQ', CB', ...
5651
     *  @param  int|string  $template           Name (or id) of email template (Must be a template of type 'facture_send')
5652
     *  @param  string      $datetouse          'duedate' (default) or 'invoicedate'
5653
     *  @param  string      $forcerecipient     Force email of recipient (for example to send the email to an accountant supervisor instead of the customer)
5654
     *  @return int                             0 if OK, <>0 if KO (this function is used also by cron so only 0 is OK)
5655
     */
5656
    public function sendEmailsRemindersOnInvoiceDueDate($nbdays = 0, $paymentmode = 'all', $template = '', $datetouse = 'duedate', $forcerecipient = '')
5657
    {
5658
        global $conf, $langs, $user;
5659
5660
        $error = 0;
5661
        $this->output = '';
5662
        $this->error = '';
5663
        $nbMailSend = 0;
5664
        $errorsMsg = array();
5665
5666
        $langs->load("bills");
5667
5668
        if (!isModEnabled('invoice')) { // Should not happen. If module disabled, cron job should not be visible.
5669
            $this->output .= $langs->trans('ModuleNotEnabled', $langs->transnoentitiesnoconv("Facture"));
5670
            return 0;
5671
        }
5672
        if (!in_array($datetouse, array('duedate', 'invoicedate'))) {
5673
            $this->output .= 'Bad value for parameter datetouse. Must be "duedate" or "invoicedate"';
5674
            return 0;
5675
        }
5676
        /*if (empty($conf->global->FACTURE_REMINDER_EMAIL)) {
5677
            $langs->load("bills");
5678
            $this->output .= $langs->trans('EventRemindersByEmailNotEnabled', $langs->transnoentitiesnoconv("Facture"));
5679
            return 0;
5680
        }
5681
        */
5682
5683
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/date.lib.php';
5684
        $formmail = new FormMail($this->db);
5685
5686
        $now = dol_now();
5687
        $tmpidate = dol_get_first_hour(dol_time_plus_duree($now, $nbdays, 'd'), 'gmt');
5688
5689
        $tmpinvoice = new Facture($this->db);
5690
5691
        dol_syslog(__METHOD__ . " start", LOG_INFO);
5692
5693
        // Select all action comm reminder
5694
        $sql = "SELECT rowid as id FROM " . MAIN_DB_PREFIX . "facture as f";
5695
        if (!empty($paymentmode) && $paymentmode != 'all') {
5696
            $sql .= ", " . MAIN_DB_PREFIX . "c_paiement as cp";
5697
        }
5698
        $sql .= " WHERE f.paye = 0";    // Only unpaid
5699
        $sql .= " AND f.fk_statut = " . self::STATUS_VALIDATED;   // Only validated status
5700
        if ($datetouse == 'invoicedate') {
5701
            $sql .= " AND f.datef = '" . $this->db->idate($tmpidate, 'gmt') . "'";
5702
        } else {
5703
            $sql .= " AND f.date_lim_reglement = '" . $this->db->idate($tmpidate, 'gmt') . "'";
5704
        }
5705
        $sql .= " AND f.entity IN (" . getEntity('facture', 0) . ")";   // One batch process only one company (no sharing)
5706
        if (!empty($paymentmode) && $paymentmode != 'all') {
5707
            $sql .= " AND f.fk_mode_reglement = cp.id AND cp.code = '" . $this->db->escape($paymentmode) . "'";
5708
        }
5709
        // TODO Add a filter to check there is no payment started yet
5710
        if ($datetouse == 'invoicedate') {
5711
            $sql .= $this->db->order("datef", "ASC");
5712
        } else {
5713
            $sql .= $this->db->order("date_lim_reglement", "ASC");
5714
        }
5715
5716
        $resql = $this->db->query($sql);
5717
5718
        $stmpidate = dol_print_date($tmpidate, 'day', 'gmt');
5719
        if ($datetouse == 'invoicedate') {
5720
            $this->output .= $langs->transnoentitiesnoconv("SearchValidatedInvoicesWithDate", $stmpidate);
5721
        } else {
5722
            $this->output .= $langs->transnoentitiesnoconv("SearchUnpaidInvoicesWithDueDate", $stmpidate);
5723
        }
5724
        if (!empty($paymentmode) && $paymentmode != 'all') {
5725
            $this->output .= ' (' . $langs->transnoentitiesnoconv("PaymentMode") . ' ' . $paymentmode . ')';
5726
        }
5727
        $this->output .= '<br>';
5728
5729
        if ($resql) {
5730
            while ($obj = $this->db->fetch_object($resql)) {
5731
                if (!$error) {
5732
                    // Load event
5733
                    $res = $tmpinvoice->fetch($obj->id);
5734
                    if ($res > 0) {
5735
                        $tmpinvoice->fetch_thirdparty();
5736
5737
                        $outputlangs = new Translate('', $conf);
5738
                        if ($tmpinvoice->thirdparty->default_lang) {
5739
                            $outputlangs->setDefaultLang($tmpinvoice->thirdparty->default_lang);
5740
                            $outputlangs->loadLangs(array("main", "bills"));
5741
                        } else {
5742
                            $outputlangs = $langs;
5743
                        }
5744
5745
                        // Select email template according to language of recipient
5746
                        $arraymessage = $formmail->getEMailTemplate($this->db, 'facture_send', $user, $outputlangs, (is_numeric($template) ? $template : 0), 1, (is_numeric($template) ? '' : $template));
5747
                        if (is_numeric($arraymessage) && $arraymessage <= 0) {
5748
                            $langs->load("errors");
5749
                            $this->output .= $langs->trans('ErrorFailedToFindEmailTemplate', $template);
5750
                            return 0;
5751
                        }
5752
5753
                        // PREPARE EMAIL
5754
                        $errormesg = '';
5755
5756
                        // Make substitution in email content
5757
                        $substitutionarray = getCommonSubstitutionArray($outputlangs, 0, '', $tmpinvoice);
5758
5759
                        complete_substitutions_array($substitutionarray, $outputlangs, $tmpinvoice);
5760
5761
                        // Topic
5762
                        $sendTopic = make_substitutions(empty($arraymessage->topic) ? $outputlangs->transnoentitiesnoconv('InformationMessage') : $arraymessage->topic, $substitutionarray, $outputlangs, 1);
5763
5764
                        // Content
5765
                        $content = $outputlangs->transnoentitiesnoconv($arraymessage->content);
5766
5767
                        $sendContent = make_substitutions($content, $substitutionarray, $outputlangs, 1);
5768
5769
                        // Recipient
5770
                        $to = array();
5771
                        if ($forcerecipient) {  // If a recipient was forced
5772
                            $to = array($forcerecipient);
5773
                        } else {
5774
                            $res = $tmpinvoice->fetch_thirdparty();
5775
                            $recipient = $tmpinvoice->thirdparty;
5776
                            if ($res > 0) {
5777
                                $tmparraycontact = $tmpinvoice->liste_contact(-1, 'external', 0, 'BILLING');
5778
                                if (is_array($tmparraycontact) && count($tmparraycontact) > 0) {
5779
                                    foreach ($tmparraycontact as $data_email) {
5780
                                        if (!empty($data_email['email'])) {
5781
                                            $to[] = $tmpinvoice->thirdparty->contact_get_property($data_email['id'], 'email');
0 ignored issues
show
Bug introduced by
The method contact_get_property() does not exist on null. ( Ignorable by Annotation )

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

5781
                                            /** @scrutinizer ignore-call */ 
5782
                                            $to[] = $tmpinvoice->thirdparty->contact_get_property($data_email['id'], 'email');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
5782
                                        }
5783
                                    }
5784
                                }
5785
                                if (empty($to) && !empty($recipient->email)) {
5786
                                    $to[] = $recipient->email;
5787
                                }
5788
                                if (empty($to)) {
5789
                                    $errormesg = "Failed to send remind to thirdparty id=" . $tmpinvoice->socid . ". No email defined for invoice or customer.";
5790
                                    $error++;
5791
                                }
5792
                            } else {
5793
                                $errormesg = "Failed to load recipient with thirdparty id=" . $tmpinvoice->socid;
5794
                                $error++;
5795
                            }
5796
                        }
5797
5798
                        // Sender
5799
                        $from = getDolGlobalString('MAIN_MAIL_EMAIL_FROM');
5800
                        if (!empty($arraymessage->email_from)) {    // If a sender is defined into template, we use it in priority
5801
                            $from = $arraymessage->email_from;
5802
                        }
5803
                        if (empty($from)) {
5804
                            $errormesg = "Failed to get sender into global setup MAIN_MAIL_EMAIL_FROM";
5805
                            $error++;
5806
                        }
5807
5808
                        if (!$error && !empty($to)) {
5809
                            $this->db->begin();
5810
5811
                            $to = implode(',', $to);
5812
                            if (!empty($arraymessage->email_to)) {  // If a recipient is defined into template, we add it
5813
                                $to = $to . ',' . $arraymessage->email_to;
5814
                            }
5815
5816
                            // Errors Recipient
5817
                            $errors_to = getDolGlobalString('MAIN_MAIL_ERRORS_TO');
5818
5819
                            $trackid = 'inv' . $tmpinvoice->id;
5820
                            $sendcontext = 'standard';
5821
5822
                            $email_tocc = '';
5823
                            if (!empty($arraymessage->email_tocc)) {    // If a CC is defined into template, we use it
5824
                                $email_tocc = $arraymessage->email_tocc;
5825
                            }
5826
5827
                            $email_tobcc = '';
5828
                            if (!empty($arraymessage->email_tobcc)) {   // If a BCC is defined into template, we use it
5829
                                $email_tobcc = $arraymessage->email_tobcc;
5830
                            }
5831
5832
                            //join file is asked
5833
                            $joinFile = [];
5834
                            $joinFileName = [];
5835
                            $joinFileMime = [];
5836
                            if ($arraymessage->joinfiles == 1 && !empty($tmpinvoice->last_main_doc)) {
5837
                                $joinFile[] = DOL_DATA_ROOT . '/' . $tmpinvoice->last_main_doc;
5838
                                $joinFileName[] = basename($tmpinvoice->last_main_doc);
5839
                                $joinFileMime[] = dol_mimetype(DOL_DATA_ROOT . '/' . $tmpinvoice->last_main_doc);
5840
                            }
5841
5842
                            // Mail Creation
5843
                            $cMailFile = new CMailFile($sendTopic, $to, $from, $sendContent, $joinFile, $joinFileMime, $joinFileName, $email_tocc, $email_tobcc, 0, 1, $errors_to, '', $trackid, '', $sendcontext, '');
5844
5845
                            // Sending Mail
5846
                            if ($cMailFile->sendfile()) {
5847
                                $nbMailSend++;
5848
5849
                                // Add a line into event table
5850
                                
5851
                                // Insert record of emails sent
5852
                                $actioncomm = new ActionComm($this->db);
5853
5854
                                $actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
5855
                                $actioncomm->socid = $tmpinvoice->thirdparty->id; // To link to a company
5856
                                $actioncomm->contact_id = 0;
5857
5858
                                $actioncomm->code = 'AC_EMAIL';
5859
                                $actioncomm->label = 'sendEmailsRemindersOnInvoiceDueDateOK (nbdays=' . $nbdays . ' paymentmode=' . $paymentmode . ' template=' . $template . ' datetouse=' . $datetouse . ' forcerecipient=' . $forcerecipient . ')';
5860
                                $actioncomm->note_private = $sendContent;
5861
                                $actioncomm->fk_project = $tmpinvoice->fk_project;
5862
                                $actioncomm->datep = dol_now();
5863
                                $actioncomm->datef = $actioncomm->datep;
5864
                                $actioncomm->percentage = -1; // Not applicable
5865
                                $actioncomm->authorid = $user->id; // User saving action
5866
                                $actioncomm->userownerid = $user->id; // Owner of action
5867
                                // Fields when action is an email (content should be added into note)
5868
                                $actioncomm->email_msgid = $cMailFile->msgid;
5869
                                $actioncomm->email_subject = $sendTopic;
5870
                                $actioncomm->email_from = $from;
5871
                                $actioncomm->email_sender = '';
5872
                                $actioncomm->email_to = $to;
5873
                                //$actioncomm->email_tocc = $sendtocc;
5874
                                //$actioncomm->email_tobcc = $sendtobcc;
5875
                                //$actioncomm->email_subject = $subject;
5876
                                $actioncomm->errors_to = $errors_to;
5877
5878
                                $actioncomm->elementtype = 'invoice';
5879
                                $actioncomm->fk_element = $tmpinvoice->id;
5880
5881
                                //$actioncomm->extraparams = $extraparams;
5882
5883
                                $actioncomm->create($user);
5884
                            } else {
5885
                                $errormesg = $cMailFile->error . ' : ' . $to;
5886
                                $error++;
5887
5888
                                // Add a line into event table
5889
                                
5890
                                // Insert record of emails sent
5891
                                $actioncomm = new ActionComm($this->db);
5892
5893
                                $actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
5894
                                $actioncomm->socid = $tmpinvoice->thirdparty->id; // To link to a company
5895
                                $actioncomm->contact_id = 0;
5896
5897
                                $actioncomm->code = 'AC_EMAIL';
5898
                                $actioncomm->label = 'sendEmailsRemindersOnInvoiceDueDateKO';
5899
                                $actioncomm->note_private = $errormesg;
5900
                                $actioncomm->fk_project = $tmpinvoice->fk_project;
5901
                                $actioncomm->datep = dol_now();
5902
                                $actioncomm->datef = $actioncomm->datep;
5903
                                $actioncomm->percentage = -1; // Not applicable
5904
                                $actioncomm->authorid = $user->id; // User saving action
5905
                                $actioncomm->userownerid = $user->id; // Owner of action
5906
                                // Fields when action is an email (content should be added into note)
5907
                                $actioncomm->email_msgid = $cMailFile->msgid;
5908
                                $actioncomm->email_from = $from;
5909
                                $actioncomm->email_sender = '';
5910
                                $actioncomm->email_to = $to;
5911
                                //$actioncomm->email_tocc = $sendtocc;
5912
                                //$actioncomm->email_tobcc = $sendtobcc;
5913
                                //$actioncomm->email_subject = $subject;
5914
                                $actioncomm->errors_to = $errors_to;
5915
5916
                                //$actioncomm->extraparams = $extraparams;
5917
5918
                                $actioncomm->create($user);
5919
                            }
5920
5921
                            $this->db->commit();    // We always commit
5922
                        }
5923
5924
                        if ($errormesg) {
5925
                            $errorsMsg[] = $errormesg;
5926
                        }
5927
                    } else {
5928
                        $errorsMsg[] = 'Failed to fetch record invoice with ID = ' . $obj->id;
5929
                        $error++;
5930
                    }
5931
                }
5932
            }
5933
        } else {
5934
            $error++;
5935
        }
5936
5937
        if (!$error) {
5938
            $this->output .= 'Nb of emails sent : ' . $nbMailSend;
5939
5940
            dol_syslog(__METHOD__ . " end - " . $this->output, LOG_INFO);
5941
5942
            return 0;
5943
        } else {
5944
            $this->error = 'Nb of emails sent : ' . $nbMailSend . ', ' . (!empty($errorsMsg) ? implode(', ', $errorsMsg) : $error);
5945
5946
            dol_syslog(__METHOD__ . " end - " . $this->error, LOG_INFO);
5947
5948
            return $error;
5949
        }
5950
    }
5951
5952
    /**
5953
     * See if current invoice date is posterior to the last invoice date among validated invoices of same type.
5954
     *
5955
     * @param   boolean     $allow_validated_drafts         return true if the invoice has been validated before returning to DRAFT state.
5956
     * @return  array                                       return array
5957
     */
5958
    public function willBeLastOfSameType($allow_validated_drafts = false)
5959
    {
5960
        // get date of last validated invoices of same type
5961
        $sql  = "SELECT datef";
5962
        $sql .= " FROM " . MAIN_DB_PREFIX . "facture";
5963
        $sql .= " WHERE type = " . (int) $this->type ;
5964
        $sql .= " AND date_valid IS NOT NULL";
5965
        $sql .= " AND entity IN (" . getEntity('invoice') . ")";
5966
        $sql .= " ORDER BY datef DESC LIMIT 1";
5967
5968
        $result = $this->db->query($sql);
5969
        if ($result) {
5970
            // compare with current validation date
5971
            if ($this->db->num_rows($result)) {
5972
                $obj = $this->db->fetch_object($result);
5973
                $last_date = $this->db->jdate($obj->datef);
5974
                $invoice_date = $this->date;
5975
5976
                $is_last_of_same_type = $invoice_date >= $last_date;
5977
                if ($allow_validated_drafts) {
5978
                    $is_last_of_same_type = $is_last_of_same_type || (!strpos($this->ref, 'PROV') && $this->status == self::STATUS_DRAFT);
5979
                }
5980
5981
                return array($is_last_of_same_type, $last_date);
5982
            } else {
5983
                // element is first of type to be validated
5984
                return array(true);
5985
            }
5986
        } else {
5987
            dol_print_error($this->db);
5988
        }
5989
5990
        return array();
5991
    }
5992
5993
    /**
5994
     *  Return clicable link of object (with eventually picto)
5995
     *
5996
     *  @param      string      $option                 Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
5997
     *  @param      array       $arraydata              Array of data
5998
     *  @return     string                              HTML Code for Kanban thumb.
5999
     */
6000
    public function getKanbanView($option = '', $arraydata = null)
6001
    {
6002
        global $langs;
6003
6004
        $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
6005
6006
        $picto = $this->picto;
6007
        if ($this->type == self::TYPE_REPLACEMENT) {
6008
            $picto .= 'r'; // Replacement invoice
6009
        }
6010
        if ($this->type == self::TYPE_CREDIT_NOTE) {
6011
            $picto .= 'a'; // Credit note
6012
        }
6013
        if ($this->type == self::TYPE_DEPOSIT) {
6014
            $picto .= 'd'; // Deposit invoice
6015
        }
6016
6017
        $return = '<div class="box-flex-item box-flex-grow-zero">';
6018
        $return .= '<div class="info-box info-box-sm">';
6019
        $return .= '<span class="info-box-icon bg-infobox-action">';
6020
        $return .= img_picto('', $picto);
6021
        $return .= '</span>';
6022
        $return .= '<div class="info-box-content">';
6023
        $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . (method_exists($this, 'getNomUrl') ? $this->getNomUrl(1) : $this->ref) . '</span>';
6024
        if ($selected >= 0) {
6025
            $return .= '<input id="cb' . $this->id . '" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="' . $this->id . '"' . ($selected ? ' checked="checked"' : '') . '>';
6026
        }
6027
        if (!empty($arraydata['thirdparty'])) {
6028
            $return .= '<br><span class="info-box-label">' . $arraydata['thirdparty'] . '</span>';
6029
        }
6030
        if (property_exists($this, 'date')) {
6031
            $return .= '<br><span class="info-box-label">' . dol_print_date($this->date, 'day') . '</span>';
6032
        }
6033
        if (property_exists($this, 'total_ht')) {
6034
            $return .= ' &nbsp; <span class="info-box-label amount" title="' . dol_escape_htmltag($langs->trans("AmountHT")) . '">' . price($this->total_ht);
6035
            $return .= ' ' . $langs->trans("HT");
6036
            $return .= '</span>';
6037
        }
6038
        if (method_exists($this, 'getLibStatut')) {
6039
            $alreadypaid = (empty($arraydata['alreadypaid']) ? 0 : $arraydata['alreadypaid']);
6040
            $return .= '<br><div class="info-box-status">' . $this->getLibStatut(3, $alreadypaid) . '</div>';
6041
        }
6042
        $return .= '</div>';
6043
        $return .= '</div>';
6044
        $return .= '</div>';
6045
        return $return;
6046
    }
6047
}
6048