Passed
Pull Request — dev (#8)
by Rafael
58:47
created

Facture   F

Complexity

Total Complexity 949

Size/Duplication

Total Lines 5970
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 3311
dl 0
loc 5970
rs 0.8
c 1
b 0
f 0
wmc 949

59 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
C createFromContract() 0 124 12
D getTooltipContentArray() 0 67 22
B setFinal() 0 34 8
A checkProgressLine() 0 18 3
B load_board() 0 55 6
C liste_array() 0 62 14
F create() 0 666 155
B deleteLine() 0 48 7
A setUnpaid() 0 33 4
F createDepositFromOrigin() 0 266 37
D getNomUrl() 0 116 34
D getNextNumRef() 0 97 19
F updateline() 0 202 52
B fetch_lines() 0 110 6
C setDraft() 0 75 14
A getIdBillingContact() 0 3 1
A getIdShippingContact() 0 3 1
A set_canceled() 0 5 1
C getRetainedWarrantyAmount() 0 50 13
B updatePriceNextInvoice() 0 46 6
A loadStateBoard() 0 29 4
A info() 0 26 3
A replaceProduct() 0 7 1
A newCycle() 0 24 4
A set_unpaid() 0 5 1
A generateDocument() 0 22 5
D fetch() 0 158 16
D createFromClone() 0 133 23
B insert_discount() 0 87 9
A getLinesArray() 0 3 1
A setRetainedWarranty() 0 22 3
C createFromOrder() 0 119 10
A is_first() 0 4 1
F validate() 0 437 104
A update_percent() 0 38 5
F addline() 0 262 63
A set_paid() 0 5 1
D update() 0 129 66
B setCanceled() 0 48 6
B set_ref_client() 0 49 10
A list_replacable_invoices() 0 41 4
A get_prev_sits() 0 25 5
B displayRetainedWarranty() 0 32 10
F delete() 0 178 34
B hasDelay() 0 28 11
B createFromCurrent() 0 84 10
B fetchPreviousNextSituationInvoice() 0 26 8
B setPaid() 0 48 8
A is_last_in_cycle() 0 22 6
A replaceThirdparty() 0 7 1
B list_qualified_avoir_invoices() 0 64 10
B setDiscount() 0 52 10
C initAsSpecimen() 0 149 10
A setRetainedWarrantyDateLimit() 0 26 6
A set_remise() 0 6 1
F sendEmailsRemindersOnInvoiceDueDate() 0 293 44
C getKanbanView() 0 46 13
B willBeLastOfSameType() 0 33 6

How to fix   Complexity   

Complex Class

Complex classes like Facture often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Facture, and based on these observations, apply Extract Interface, too.

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

451
        /** @scrutinizer ignore-deprecated */ $this->note = (isset($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...
452
        $this->note_public = trim($this->note_public);
453
        if (!$this->cond_reglement_id) {
454
            $this->cond_reglement_id = 0;
455
        }
456
        if (!$this->mode_reglement_id) {
457
            $this->mode_reglement_id = 0;
458
        }
459
        $this->status = self::STATUS_DRAFT;
460
        $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

460
        /** @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...
461
462
        if (!empty($this->multicurrency_code)) {
463
            // Multicurrency (test on $this->multicurrency_tx because we should take the default rate of multicurrency_code only if not using original rate)
464
            if (empty($this->multicurrency_tx)) {
465
                // If original rate is not set, we take a default value from date
466
                list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date);
467
            } else {
468
                // original rate multicurrency_tx and multicurrency_code are set, we use them
469
                $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
470
            }
471
        } else {
472
            $this->fk_multicurrency = 0;
473
        }
474
        if (empty($this->fk_multicurrency)) {
475
            $this->multicurrency_code = $conf->currency;
476
            $this->fk_multicurrency = 0;
477
            $this->multicurrency_tx = 1;
478
        }
479
480
        dol_syslog(get_class($this) . "::create user=" . $user->id . " date=" . $this->date);
481
482
        // Check parameters
483
        if (empty($this->date)) {
484
            $this->error = "Try to create an invoice with an empty parameter (date)";
485
            dol_syslog(get_class($this) . "::create " . $this->error, LOG_ERR);
486
            return -3;
487
        }
488
        $soc = new Societe($this->db);
489
        $result = $soc->fetch($this->socid);
490
        if ($result < 0) {
491
            $this->error = "Failed to fetch company: " . $soc->error;
492
            dol_syslog(get_class($this) . "::create " . $this->error, LOG_ERR);
493
            return -2;
494
        }
495
496
        $now = dol_now();
497
        $this->date_creation = $now;
498
499
        $this->db->begin();
500
501
        $originaldatewhen = null;
502
        $nextdatewhen = null;
503
        $previousdaynextdatewhen = null;
504
505
        // Erase some properties of the invoice to create with the one of the recurring invoice
506
        if ($this->fac_rec > 0) {
507
            $this->fk_fac_rec_source = $this->fac_rec;
508
509
            if (getDolGlobalString('MODEL_FAC_REC_AUTHOR')) {
510
                $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

510
                $origin_user_author_id = ($this->fk_user_author > 0 ? /** @scrutinizer ignore-deprecated */ $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...
511
            }
512
            $_facrec = new FactureRec($this->db);
513
            $result = $_facrec->fetch($this->fac_rec);
514
            $result = $_facrec->fetchObjectLinked(null, '', null, '', 'OR', 1, 'sourcetype', 0); // This load $_facrec->linkedObjectsIds
515
516
            // Define some dates
517
            $originaldatewhen = $_facrec->date_when;
518
            $nextdatewhen = null;
519
            $previousdaynextdatewhen = null;
520
            if ($originaldatewhen) {
521
                $nextdatewhen = dol_time_plus_duree($originaldatewhen, $_facrec->frequency, $_facrec->unit_frequency);
522
                $previousdaynextdatewhen = dol_time_plus_duree($nextdatewhen, -1, 'd');
523
            }
524
525
            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.
526
                $this->socid = $_facrec->socid;
527
            }
528
            $this->entity            = $_facrec->entity; // Invoice created in same entity than template
529
530
            // Fields coming from GUI.
531
            // @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
532
            // set by posted page with $object->xxx = ... and this section should be removed.
533
            $this->fk_project        = GETPOSTINT('projectid') > 0 ? GETPOSTINT('projectid') : $_facrec->fk_project;
534
            $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...
535
            $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...
536
            $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...
537
            $this->cond_reglement_id = GETPOSTINT('cond_reglement_id') > 0 ? GETPOSTINT('cond_reglement_id') : $_facrec->cond_reglement_id;
538
            $this->mode_reglement_id = GETPOSTINT('mode_reglement_id') > 0 ? GETPOSTINT('mode_reglement_id') : $_facrec->mode_reglement_id;
539
            $this->fk_account        = GETPOST('fk_account') > 0 ? GETPOSTINT('fk_account') : $_facrec->fk_account;
540
541
            // Set here to have this defined for substitution into notes, should be recalculated after adding lines to get same result
542
            $this->total_ht          = $_facrec->total_ht;
543
            $this->total_ttc         = $_facrec->total_ttc;
544
545
            // Fields always coming from template
546
            //$this->remise_absolue    = $_facrec->remise_absolue;
547
            //$this->remise_percent    = $_facrec->remise_percent;  // TODO deprecated
548
            $this->fk_incoterms = $_facrec->fk_incoterms;
549
            $this->location_incoterms = $_facrec->location_incoterms;
550
551
            // Clean parameters
552
            if (!$this->type) {
553
                $this->type = self::TYPE_STANDARD;
554
            }
555
            $this->ref_client = trim($this->ref_client);
556
            $this->ref_customer = trim($this->ref_customer);
557
            $this->note_public = trim($this->note_public);
558
            $this->note_private = trim($this->note_private);
559
            $this->note_private = dol_concatdesc($this->note_private, $langs->trans("GeneratedFromRecurringInvoice", $_facrec->ref));
560
561
            $this->array_options = $_facrec->array_options;
562
563
            if (!$this->mode_reglement_id) {
564
                $this->mode_reglement_id = 0;
565
            }
566
            $this->status = self::STATUS_DRAFT;
567
            $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

567
            /** @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...
568
569
            $this->linked_objects = $_facrec->linkedObjectsIds;
570
            // We do not add link to template invoice or next invoice will be linked to all generated invoices
571
            //$this->linked_objects['facturerec'][0] = $this->fac_rec;
572
573
            // For recurring invoices, update date and number of last generation of recurring template invoice, before inserting new invoice
574
            if ($_facrec->frequency > 0) {
575
                dol_syslog("This is a recurring invoice so we set date_last_gen and next date_when");
576
                if (empty($_facrec->date_when)) {
577
                    $_facrec->date_when = $now;
578
                }
579
                $next_date = $_facrec->getNextDate(); // Calculate next date
580
                $result = $_facrec->setValueFrom('date_last_gen', $now, '', null, 'date', '', $user, '');
581
                //$_facrec->setValueFrom('nb_gen_done', $_facrec->nb_gen_done + 1);     // Not required, +1 already included into setNextDate when second param is 1.
582
                $result = $_facrec->setNextDate($next_date, 1);
583
            }
584
585
            // Define lang of customer
586
            $outputlangs = $langs;
587
            $newlang = '';
588
589
            if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang) && isset($this->thirdparty->default_lang)) {
590
                $newlang = $this->thirdparty->default_lang; // for proposal, order, invoice, ...
591
            }
592
            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...
593
                $newlang = $this->default_lang; // for thirdparty
594
            }
595
            if (!empty($newlang)) {
596
                $outputlangs = new Translate("", $conf);
597
                $outputlangs->setDefaultLang($newlang);
598
            }
599
600
            // Array of possible substitutions (See also file mailing-send.php that should manage same substitutions)
601
            $substitutionarray = getCommonSubstitutionArray($outputlangs, 0, null, $this);
602
            $substitutionarray['__INVOICE_PREVIOUS_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%m');
603
            $substitutionarray['__INVOICE_MONTH__'] = dol_print_date($this->date, '%m');
604
            $substitutionarray['__INVOICE_NEXT_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%m');
605
            $substitutionarray['__INVOICE_PREVIOUS_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%B');
606
            $substitutionarray['__INVOICE_MONTH_TEXT__'] = dol_print_date($this->date, '%B');
607
            $substitutionarray['__INVOICE_NEXT_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%B');
608
            $substitutionarray['__INVOICE_PREVIOUS_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'y'), '%Y');
609
            $substitutionarray['__INVOICE_YEAR__'] = dol_print_date($this->date, '%Y');
610
            $substitutionarray['__INVOICE_NEXT_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'y'), '%Y');
611
            // Only for template invoice
612
            $substitutionarray['__INVOICE_DATE_NEXT_INVOICE_BEFORE_GEN__'] = (isset($originaldatewhen) ? dol_print_date($originaldatewhen, 'dayhour') : '');
613
            $substitutionarray['__INVOICE_DATE_NEXT_INVOICE_AFTER_GEN__'] = (isset($nextdatewhen) ? dol_print_date($nextdatewhen, 'dayhour') : '');
614
            $substitutionarray['__INVOICE_PREVIOUS_DATE_NEXT_INVOICE_AFTER_GEN__'] = (isset($previousdaynextdatewhen) ? dol_print_date($previousdaynextdatewhen, 'dayhour') : '');
615
            $substitutionarray['__INVOICE_COUNTER_CURRENT__'] = $_facrec->nb_gen_done;
616
            $substitutionarray['__INVOICE_COUNTER_MAX__'] = $_facrec->nb_gen_max;
617
618
            //var_dump($substitutionarray);exit;
619
620
            complete_substitutions_array($substitutionarray, $outputlangs);
621
622
            $this->note_public = make_substitutions($this->note_public, $substitutionarray);
623
            $this->note_private = make_substitutions($this->note_private, $substitutionarray);
624
        }
625
626
        // Define due date if not already defined
627
        if (empty($forceduedate)) {
628
            $duedate = $this->calculate_date_lim_reglement();
629
            /*if ($duedate < 0) {   Regression, a date can be negative if before 1970.
630
                dol_syslog(__METHOD__ . ' Error in calculate_date_lim_reglement. We got ' . $duedate, LOG_ERR);
631
                return -1;
632
            }*/
633
            $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...
634
        } else {
635
            $this->date_lim_reglement = $forceduedate;
636
        }
637
638
        // Insert into database
639
        $socid = $this->socid;
640
641
        $sql = "INSERT INTO " . MAIN_DB_PREFIX . "facture (";
642
        $sql .= " ref";
643
        $sql .= ", entity";
644
        $sql .= ", ref_ext";
645
        $sql .= ", type";
646
        $sql .= ", subtype";
647
        $sql .= ", fk_soc";
648
        $sql .= ", datec";
649
        $sql .= ", datef";
650
        $sql .= ", date_pointoftax";
651
        $sql .= ", note_private";
652
        $sql .= ", note_public";
653
        $sql .= ", ref_client";
654
        $sql .= ", fk_account";
655
        $sql .= ", module_source, pos_source, fk_fac_rec_source, fk_facture_source, fk_user_author, fk_projet";
656
        $sql .= ", fk_cond_reglement, fk_mode_reglement, date_lim_reglement, model_pdf";
657
        $sql .= ", situation_cycle_ref, situation_counter, situation_final";
658
        $sql .= ", fk_incoterms, location_incoterms";
659
        $sql .= ", fk_multicurrency";
660
        $sql .= ", multicurrency_code";
661
        $sql .= ", multicurrency_tx";
662
        $sql .= ", retained_warranty";
663
        $sql .= ", retained_warranty_date_limit";
664
        $sql .= ", retained_warranty_fk_cond_reglement";
665
        $sql .= ")";
666
        $sql .= " VALUES (";
667
        $sql .= "'(PROV)'";
668
        $sql .= ", " . setEntity($this);
669
        $sql .= ", " . ($this->ref_ext ? "'" . $this->db->escape($this->ref_ext) . "'" : "null");
670
        $sql .= ", '" . $this->db->escape($this->type) . "'";
671
        $sql .= ", " . ($this->subtype ? "'" . $this->db->escape($this->subtype) . "'" : "null");
672
        $sql .= ", " . ((int) $socid);
673
        $sql .= ", '" . $this->db->idate($this->date_creation) . "'";
674
        $sql .= ", '" . $this->db->idate($this->date) . "'";
675
        $sql .= ", " . (empty($this->date_pointoftax) ? "null" : "'" . $this->db->idate($this->date_pointoftax) . "'");
676
        $sql .= ", " . ($this->note_private ? "'" . $this->db->escape($this->note_private) . "'" : "null");
677
        $sql .= ", " . ($this->note_public ? "'" . $this->db->escape($this->note_public) . "'" : "null");
678
        $sql .= ", " . ($this->ref_customer ? "'" . $this->db->escape($this->ref_customer) . "'" : ($this->ref_client ? "'" . $this->db->escape($this->ref_client) . "'" : "null"));
679
        $sql .= ", " . ($this->fk_account > 0 ? $this->fk_account : 'NULL');
680
        $sql .= ", " . ($this->module_source ? "'" . $this->db->escape($this->module_source) . "'" : "null");
681
        $sql .= ", " . ($this->pos_source != '' ? "'" . $this->db->escape($this->pos_source) . "'" : "null");
682
        $sql .= ", " . ($this->fk_fac_rec_source ? "'" . $this->db->escape($this->fk_fac_rec_source) . "'" : "null");
683
        $sql .= ", " . ($this->fk_facture_source ? "'" . $this->db->escape($this->fk_facture_source) . "'" : "null");
684
        $sql .= ", " . ($origin_user_author_id > 0 ? (int) $origin_user_author_id : "null");
685
        $sql .= ", " . ($this->fk_project ? (int) $this->fk_project : "null");
686
        $sql .= ", " . ((int) $this->cond_reglement_id);
687
        $sql .= ", " . ((int) $this->mode_reglement_id);
688
        $sql .= ", '" . $this->db->idate($this->date_lim_reglement) . "'";
689
        $sql .= ", " . (isset($this->model_pdf) ? "'" . $this->db->escape($this->model_pdf) . "'" : "null");
690
        $sql .= ", " . ($this->situation_cycle_ref ? "'" . $this->db->escape($this->situation_cycle_ref) . "'" : "null");
691
        $sql .= ", " . ($this->situation_counter ? "'" . $this->db->escape($this->situation_counter) . "'" : "null");
692
        $sql .= ", " . ($this->situation_final ? (int) $this->situation_final : 0);
693
        $sql .= ", " . (int) $this->fk_incoterms;
694
        $sql .= ", '" . $this->db->escape($this->location_incoterms) . "'";
695
        $sql .= ", " . (int) $this->fk_multicurrency;
696
        $sql .= ", '" . $this->db->escape($this->multicurrency_code) . "'";
697
        $sql .= ", " . (float) $this->multicurrency_tx;
698
        $sql .= ", " . (empty($this->retained_warranty) ? "0" : $this->db->escape($this->retained_warranty));
699
        $sql .= ", " . (!empty($this->retained_warranty_date_limit) ? "'" . $this->db->idate($this->retained_warranty_date_limit) . "'" : 'NULL');
700
        $sql .= ", " . (int) $this->retained_warranty_fk_cond_reglement;
701
        $sql .= ")";
702
703
        $resql = $this->db->query($sql);
704
        if ($resql) {
705
            $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX . 'facture');
706
707
            // Update ref with new one
708
            $this->ref = '(PROV' . $this->id . ')';
709
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . "facture SET ref='" . $this->db->escape($this->ref) . "' WHERE rowid=" . ((int) $this->id);
710
711
            $resql = $this->db->query($sql);
712
            if (!$resql) {
713
                $error++;
714
            }
715
716
            if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) {  // To use new linkedObjectsIds instead of old linked_objects
717
                $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
718
            }
719
720
            // Add object linked
721
            if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
722
                foreach ($this->linked_objects as $origin => $tmp_origin_id) {
723
                    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, ...))
724
                        foreach ($tmp_origin_id as $origin_id) {
725
                            $ret = $this->add_object_linked($origin, $origin_id);
726
                            if (!$ret) {
727
                                $this->error = $this->db->lasterror();
728
                                $error++;
729
                            }
730
                        }
731
                    } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
732
                        $origin_id = $tmp_origin_id;
733
                        $ret = $this->add_object_linked($origin, $origin_id);
734
                        if (!$ret) {
735
                            $this->error = $this->db->lasterror();
736
                            $error++;
737
                        }
738
                    }
739
                }
740
            }
741
742
            // Propagate contacts
743
            if (!$error && $this->id && getDolGlobalString('MAIN_PROPAGATE_CONTACTS_FROM_ORIGIN') && !empty($this->origin) && !empty($this->origin_id)) {   // Get contact from origin object
744
                $originforcontact = $this->origin;
745
                $originidforcontact = $this->origin_id;
746
                if ($originforcontact == 'shipping') {     // shipment and order share the same contacts. If creating from shipment we take data of order
747
                    $exp = new Expedition($this->db);
748
                    $exp->fetch($this->origin_id);
749
                    $exp->fetchObjectLinked(null, '', null, '', 'OR', 1, 'sourcetype', 0);
750
                    if (count($exp->linkedObjectsIds['commande']) > 0) {
751
                        foreach ($exp->linkedObjectsIds['commande'] as $key => $value) {
752
                            $originforcontact = 'commande';
753
                            if (is_object($value)) {
754
                                $originidforcontact = $value->id;
755
                            } else {
756
                                $originidforcontact = $value;
757
                            }
758
                            break; // We take first one
759
                        }
760
                    }
761
                }
762
763
                $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";
764
                $sqlcontact .= " WHERE element_id = " . ((int) $originidforcontact) . " AND ec.fk_c_type_contact = ctc.rowid AND ctc.element = '" . $this->db->escape($originforcontact) . "'";
765
766
                $resqlcontact = $this->db->query($sqlcontact);
767
                if ($resqlcontact) {
768
                    while ($objcontact = $this->db->fetch_object($resqlcontact)) {
769
                        //print $objcontact->code.'-'.$objcontact->source.'-'.$objcontact->fk_socpeople."\n";
770
                        $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
771
                    }
772
                } else {
773
                    dol_print_error($resqlcontact);
774
                }
775
            }
776
777
            /*
778
             *  Insert lines of invoices, if not from template invoice, into database
779
             */
780
            if (!$error && empty($this->fac_rec) && count($this->lines) && is_object($this->lines[0])) {    // If this->lines is array of InvoiceLines (preferred mode)
781
                $fk_parent_line = 0;
782
783
                dol_syslog("There is " . count($this->lines) . " lines into ->lines that are InvoiceLines");
784
                foreach ($this->lines as $i => $val) {
785
                    $newinvoiceline = $this->lines[$i];
786
787
                    $newinvoiceline->context = $this->context;
788
789
                    $newinvoiceline->fk_facture = $this->id;
790
791
                    $newinvoiceline->origin = $this->lines[$i]->element;
792
                    $newinvoiceline->origin_id = $this->lines[$i]->id;
793
794
                    // Auto set date of service ?
795
                    if ($this->lines[$i]->date_start_fill == 1 && $originaldatewhen) {      // $originaldatewhen is defined when generating from recurring invoice only
796
                        $newinvoiceline->date_start = $originaldatewhen;
797
                    }
798
                    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...
799
                        $newinvoiceline->date_end = $previousdaynextdatewhen;
800
                    }
801
802
                    if ($result >= 0) {
803
                        // Reset fk_parent_line for no child products and special product
804
                        if (($newinvoiceline->product_type != 9 && empty($newinvoiceline->fk_parent_line)) || $newinvoiceline->product_type == 9) {
805
                            $fk_parent_line = 0;
806
                        }
807
808
                        // Complete vat rate with code
809
                        $vatrate = $newinvoiceline->tva_tx;
810
                        if ($newinvoiceline->vat_src_code && ! preg_match('/\(.*\)/', (string) $vatrate)) {
811
                            $vatrate .= ' (' . $newinvoiceline->vat_src_code . ')';
812
                        }
813
814
                        $newinvoiceline->fk_parent_line = $fk_parent_line;
815
816
                        if ($this->type === Facture::TYPE_REPLACEMENT && $newinvoiceline->fk_remise_except) {
817
                            $discount = new DiscountAbsolute($this->db);
818
                            $discount->fetch($newinvoiceline->fk_remise_except);
819
820
                            $discountId = $soc->set_remise_except($discount->amount_ht, $user, $discount->description, $discount->tva_tx);
821
                            $newinvoiceline->fk_remise_except = $discountId;
822
                        }
823
824
                        $result = $this->addline(
825
                            $newinvoiceline->desc,
826
                            $newinvoiceline->subprice,
827
                            $newinvoiceline->qty,
828
                            $vatrate,
829
                            $newinvoiceline->localtax1_tx,
830
                            $newinvoiceline->localtax2_tx,
831
                            $newinvoiceline->fk_product,
832
                            $newinvoiceline->remise_percent,
833
                            $newinvoiceline->date_start,
834
                            $newinvoiceline->date_end,
835
                            $newinvoiceline->fk_code_ventilation,
836
                            $newinvoiceline->info_bits,
837
                            $newinvoiceline->fk_remise_except,
838
                            'HT',
839
                            0,
840
                            $newinvoiceline->product_type,
841
                            $newinvoiceline->rang,
842
                            $newinvoiceline->special_code,
843
                            $newinvoiceline->element,
844
                            $newinvoiceline->id,
845
                            $fk_parent_line,
846
                            $newinvoiceline->fk_fournprice,
847
                            $newinvoiceline->pa_ht,
848
                            $newinvoiceline->label,
849
                            $newinvoiceline->array_options,
850
                            $newinvoiceline->situation_percent,
851
                            $newinvoiceline->fk_prev_id,
852
                            $newinvoiceline->fk_unit,
853
                            $newinvoiceline->multicurrency_subprice,
854
                            $newinvoiceline->ref_ext,
855
                            1
856
                        );
857
858
                        // Defined the new fk_parent_line
859
                        if ($result > 0 && $newinvoiceline->product_type == 9) {
860
                            $fk_parent_line = $result;
861
                        }
862
                    }
863
                    if ($result < 0) {
864
                        $this->error = $newinvoiceline->error;
865
                        $this->errors = $newinvoiceline->errors;
866
                        $error++;
867
                        break;
868
                    }
869
                }
870
            } elseif (!$error && empty($this->fac_rec)) {       // If this->lines is an array of invoice line arrays
871
                $fk_parent_line = 0;
872
873
                dol_syslog("There is " . count($this->lines) . " lines into ->lines as a simple array");
874
875
                foreach ($this->lines as $i => $val) {
876
                    $line = $this->lines[$i];
877
878
                    // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
879
                    //if (! is_object($line)) $line=json_decode(json_encode($line), false);  // convert recursively array into object.
880
                    if (!is_object($line)) {
881
                        $line = (object) $line;
882
                    }
883
884
                    if ($result >= 0) {
885
                        // Reset fk_parent_line for no child products and special product
886
                        if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
887
                            $fk_parent_line = 0;
888
                        }
889
890
                        // Complete vat rate with code
891
                        $vatrate = $line->tva_tx;
892
                        if ($line->vat_src_code && !preg_match('/\(.*\)/', (string) $vatrate)) {
893
                            $vatrate .= ' (' . $line->vat_src_code . ')';
894
                        }
895
896
                        if (getDolGlobalString('MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION')) {
897
                            $originid = $line->origin_id;
898
                            $origintype = $line->origin;
899
                        } else {
900
                            $originid = $line->id;
901
                            $origintype = $this->element;
902
                        }
903
904
                        // init ref_ext
905
                        if (empty($line->ref_ext)) {
906
                            $line->ref_ext = '';
907
                        }
908
909
                        $result = $this->addline(
910
                            $line->desc,
911
                            $line->subprice,
912
                            $line->qty,
913
                            $vatrate,
914
                            $line->localtax1_tx,
915
                            $line->localtax2_tx,
916
                            $line->fk_product,
917
                            $line->remise_percent,
918
                            $line->date_start,
919
                            $line->date_end,
920
                            $line->fk_code_ventilation,
921
                            $line->info_bits,
922
                            $line->fk_remise_except,
923
                            'HT',
924
                            0,
925
                            $line->product_type,
926
                            $line->rang,
927
                            $line->special_code,
928
                            $origintype,
929
                            $originid,
930
                            $fk_parent_line,
931
                            $line->fk_fournprice,
932
                            $line->pa_ht,
933
                            $line->label,
934
                            $line->array_options,
935
                            $line->situation_percent,
936
                            $line->fk_prev_id,
937
                            $line->fk_unit,
938
                            $line->multicurrency_subprice,
939
                            $line->ref_ext,
940
                            1
941
                        );
942
                        if ($result < 0) {
943
                            $this->error = $this->db->lasterror();
944
                            dol_print_error($this->db);
945
                            $this->db->rollback();
946
                            return -1;
947
                        }
948
949
                        // Defined the new fk_parent_line
950
                        if ($result > 0 && $line->product_type == 9) {
951
                            $fk_parent_line = $result;
952
                        }
953
                    }
954
                }
955
            }
956
957
            /*
958
             * Insert lines coming from the template invoice
959
             */
960
            if (!$error && $this->fac_rec > 0) {
961
                dol_syslog("There is " . count($_facrec->lines) . " lines from recurring invoice");
962
                $fk_parent_line = 0;
963
964
                foreach ($_facrec->lines as $i => $val) {
965
                    if ($_facrec->lines[$i]->fk_product) {
966
                        $prod = new Product($this->db);
967
                        $res = $prod->fetch($_facrec->lines[$i]->fk_product);
968
                    }
969
970
                    // Reset fk_parent_line for no child products and special product
971
                    if (($_facrec->lines[$i]->product_type != 9 && empty($_facrec->lines[$i]->fk_parent_line)) || $_facrec->lines[$i]->product_type == 9) {
972
                        $fk_parent_line = 0;
973
                    }
974
975
                    // For line from template invoice, we use data from template invoice
976
                    /*
977
                    $tva_tx = get_default_tva($mysoc,$soc,$prod->id);
978
                    $tva_npr = get_default_npr($mysoc,$soc,$prod->id);
979
                    if (empty($tva_tx)) $tva_npr=0;
980
                    $localtax1_tx=get_localtax($tva_tx,1,$soc,$mysoc,$tva_npr);
981
                    $localtax2_tx=get_localtax($tva_tx,2,$soc,$mysoc,$tva_npr);
982
                    */
983
                    $tva_tx = $_facrec->lines[$i]->tva_tx . ($_facrec->lines[$i]->vat_src_code ? '(' . $_facrec->lines[$i]->vat_src_code . ')' : '');
984
                    $tva_npr = $_facrec->lines[$i]->info_bits;
985
                    if (empty($tva_tx)) {
986
                        $tva_npr = 0;
987
                    }
988
                    $localtax1_tx = $_facrec->lines[$i]->localtax1_tx;
989
                    $localtax2_tx = $_facrec->lines[$i]->localtax2_tx;
990
991
                    $fk_product_fournisseur_price = empty($_facrec->lines[$i]->fk_product_fournisseur_price) ? null : $_facrec->lines[$i]->fk_product_fournisseur_price;
992
                    $buyprice = empty($_facrec->lines[$i]->buyprice) ? 0 : $_facrec->lines[$i]->buyprice;
993
994
                    // If buyprice not defined from template invoice, we try to guess the best value
995
                    if (!$buyprice && $_facrec->lines[$i]->fk_product > 0) {
996
                        $producttmp = new ProductFournisseur($this->db);
997
                        $producttmp->fetch($_facrec->lines[$i]->fk_product);
998
999
                        // If margin module defined on costprice, we try the costprice
1000
                        // If not defined or if module margin defined and pmp and stock module enabled, we try pmp price
1001
                        // else we get the best supplier price
1002
                        if (getDolGlobalString('MARGIN_TYPE') == 'costprice' && !empty($producttmp->cost_price)) {
1003
                            $buyprice = $producttmp->cost_price;
1004
                        } elseif (isModEnabled('stock') && (getDolGlobalString('MARGIN_TYPE') == 'costprice' || getDolGlobalString('MARGIN_TYPE') == 'pmp') && !empty($producttmp->pmp)) {
1005
                            $buyprice = $producttmp->pmp;
1006
                        } else {
1007
                            if ($producttmp->find_min_price_product_fournisseur($_facrec->lines[$i]->fk_product) > 0) {
1008
                                if ($producttmp->product_fourn_price_id > 0) {
1009
                                    $buyprice = price2num($producttmp->fourn_unitprice * (1 - $producttmp->fourn_remise_percent / 100) + $producttmp->fourn_remise, 'MU');
1010
                                }
1011
                            }
1012
                        }
1013
                    }
1014
1015
                    $result_insert = $this->addline(
1016
                        $_facrec->lines[$i]->desc,
1017
                        $_facrec->lines[$i]->subprice,
1018
                        $_facrec->lines[$i]->qty,
1019
                        $tva_tx,
1020
                        $localtax1_tx,
1021
                        $localtax2_tx,
1022
                        $_facrec->lines[$i]->fk_product,
1023
                        $_facrec->lines[$i]->remise_percent,
1024
                        ($_facrec->lines[$i]->date_start_fill == 1 && $originaldatewhen) ? $originaldatewhen : '',
1025
                        ($_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...
1026
                        0,
1027
                        $tva_npr,
1028
                        '',
1029
                        'HT',
1030
                        0,
1031
                        $_facrec->lines[$i]->product_type,
1032
                        $_facrec->lines[$i]->rang,
1033
                        $_facrec->lines[$i]->special_code,
1034
                        '',
1035
                        0,
1036
                        $fk_parent_line,
1037
                        $fk_product_fournisseur_price,
1038
                        $buyprice,
1039
                        $_facrec->lines[$i]->label,
1040
                        empty($_facrec->lines[$i]->array_options) ? null : $_facrec->lines[$i]->array_options,
1041
                        100,    // situation percent is undefined on recurring invoice lines
1042
                        '',
1043
                        $_facrec->lines[$i]->fk_unit,
1044
                        $_facrec->lines[$i]->multicurrency_subprice,
1045
                        $_facrec->lines[$i]->ref_ext,
1046
                        1
1047
                    );
1048
1049
                    // Defined the new fk_parent_line
1050
                    if ($result_insert > 0 && $_facrec->lines[$i]->product_type == 9) {
1051
                        $fk_parent_line = $result_insert;
1052
                    }
1053
1054
                    if ($result_insert < 0) {
1055
                        $error++;
1056
                        $this->error = $this->db->error();
1057
                        break;
1058
                    }
1059
                }
1060
            }
1061
1062
            if (!$error) {
1063
                $result = $this->update_price(1, 'auto', 0, $mysoc);
1064
                if ($result > 0) {
1065
                    $action = 'create';
1066
1067
                    // Actions on extra fields
1068
                    if (!$error) {
1069
                        $result = $this->insertExtraFields();
1070
                        if ($result < 0) {
1071
                            $error++;
1072
                        }
1073
                    }
1074
1075
                    if (!$error && !$notrigger) {
1076
                        // Call trigger
1077
                        $result = $this->call_trigger('BILL_CREATE', $user);
1078
                        if ($result < 0) {
1079
                            $error++;
1080
                        }
1081
                    }
1082
1083
                    if (!$error) {
1084
                        $this->db->commit();
1085
                        return $this->id;
1086
                    } else {
1087
                        $this->db->rollback();
1088
                        return -4;
1089
                    }
1090
                } else {
1091
                    $this->error = $langs->trans('FailedToUpdatePrice');
1092
                    $this->db->rollback();
1093
                    return -3;
1094
                }
1095
            } else {
1096
                dol_syslog(get_class($this) . "::create error " . $this->error, LOG_ERR);
1097
                $this->db->rollback();
1098
                return -2;
1099
            }
1100
        } else {
1101
            $this->error = $this->db->error();
1102
            $this->db->rollback();
1103
            return -1;
1104
        }
1105
    }
1106
1107
1108
    /**
1109
     *  Create a new invoice in database from current invoice
1110
     *
1111
     *  @param      User    $user           Object user that ask creation
1112
     *  @param      int     $invertdetail   Reverse sign of amounts for lines
1113
     *  @return     int                     Return integer <0 if KO, >0 if OK
1114
     */
1115
    public function createFromCurrent(User $user, $invertdetail = 0)
1116
    {
1117
        // Source invoice load
1118
        $facture = new Facture($this->db);
1119
1120
        // Retrieve all extrafield
1121
        // fetch optionals attributes and labels
1122
        $this->fetch_optionals();
1123
1124
        if (!empty($this->array_options)) {
1125
            $facture->array_options = $this->array_options;
1126
        }
1127
1128
        foreach ($this->lines as &$line) {
1129
            $line->fetch_optionals(); //fetch extrafields
1130
        }
1131
1132
        $facture->fk_facture_source = $this->fk_facture_source;
1133
        $facture->type              = $this->type;
1134
        $facture->subtype           = $this->subtype;
1135
        $facture->socid             = $this->socid;
1136
        $facture->date              = $this->date;
1137
        $facture->date_pointoftax   = $this->date_pointoftax;
1138
        $facture->note_public       = $this->note_public;
1139
        $facture->note_private      = $this->note_private;
1140
        $facture->ref_client        = $this->ref_client;
1141
        $facture->ref_customer      = $this->ref_customer;
1142
        $facture->model_pdf         = $this->model_pdf;
1143
        $facture->fk_project        = $this->fk_project;
1144
        $facture->cond_reglement_id = $this->cond_reglement_id;
1145
        $facture->mode_reglement_id = $this->mode_reglement_id;
1146
        //$facture->remise_absolue    = $this->remise_absolue;
1147
        //$facture->remise_percent    = $this->remise_percent;  // TODO deprecated
1148
1149
        $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

1149
        /** @scrutinizer ignore-deprecated */ $facture->origin            = $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...
1150
        $facture->origin_id         = $this->origin_id;
1151
        $facture->fk_account         = $this->fk_account;
1152
1153
        $facture->lines = $this->lines; // Array of lines of invoice
1154
        $facture->situation_counter = $this->situation_counter;
1155
        $facture->situation_cycle_ref = $this->situation_cycle_ref;
1156
        $facture->situation_final = $this->situation_final;
1157
1158
        $facture->retained_warranty = $this->retained_warranty;
1159
        $facture->retained_warranty_fk_cond_reglement = $this->retained_warranty_fk_cond_reglement;
1160
        $facture->retained_warranty_date_limit = $this->retained_warranty_date_limit;
1161
1162
        $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

1162
        /** @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...
1163
        $facture->user_creation_id = $user->id;
1164
1165
1166
        // Loop on each line of new invoice
1167
        foreach ($facture->lines as $i => $tmpline) {
1168
            $facture->lines[$i]->fk_prev_id = $this->lines[$i]->rowid;
1169
            if ($invertdetail) {
1170
                $facture->lines[$i]->subprice  = -$facture->lines[$i]->subprice;
1171
                $facture->lines[$i]->total_ht  = -$facture->lines[$i]->total_ht;
1172
                $facture->lines[$i]->total_tva = -$facture->lines[$i]->total_tva;
1173
                $facture->lines[$i]->total_localtax1 = -$facture->lines[$i]->total_localtax1;
1174
                $facture->lines[$i]->total_localtax2 = -$facture->lines[$i]->total_localtax2;
1175
                $facture->lines[$i]->total_ttc = -$facture->lines[$i]->total_ttc;
1176
                $facture->lines[$i]->ref_ext = '';
1177
            }
1178
        }
1179
1180
        dol_syslog(get_class($this) . "::createFromCurrent invertdetail=" . $invertdetail . " socid=" . $this->socid . " nboflines=" . count($facture->lines));
1181
1182
        $facid = $facture->create($user);
1183
        if ($facid <= 0) {
1184
            $this->error = $facture->error;
1185
            $this->errors = $facture->errors;
1186
        } elseif ($this->type == self::TYPE_SITUATION && getDolGlobalString('INVOICE_USE_SITUATION')) {
1187
            $this->fetchObjectLinked('', '', $this->id, 'facture');
1188
1189
            foreach ($this->linkedObjectsIds as $typeObject => $Tfk_object) {
1190
                foreach ($Tfk_object as $fk_object) {
1191
                    $facture->add_object_linked($typeObject, $fk_object);
1192
                }
1193
            }
1194
1195
            $facture->add_object_linked('facture', $this->fk_facture_source);
1196
        }
1197
1198
        return $facid;
1199
    }
1200
1201
1202
    /**
1203
     *  Load an object from its id and create a new one in database
1204
     *
1205
     *  @param      User    $user           User that clone
1206
     *  @param      int     $fromid         Id of object to clone
1207
     *  @return     int                     New id of clone
1208
     */
1209
    public function createFromClone(User $user, $fromid = 0)
1210
    {
1211
        global $conf, $hookmanager;
1212
1213
        $error = 0;
1214
1215
        $object = new Facture($this->db);
1216
1217
        $this->db->begin();
1218
1219
        $object->fetch($fromid);
1220
1221
        // Load source object
1222
        $objFrom = clone $object;
1223
1224
        // Change socid if needed
1225
        if (!empty($this->socid) && $this->socid != $object->socid) {
1226
            $objsoc = new Societe($this->db);
1227
1228
            if ($objsoc->fetch($this->socid) > 0) {
1229
                $object->socid = $objsoc->id;
1230
                $object->cond_reglement_id  = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1231
                $object->mode_reglement_id  = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1232
                $object->fk_project = 0;
1233
                $object->fk_delivery_address = 0;
1234
            }
1235
1236
            // TODO Change product price if multi-prices
1237
        }
1238
1239
        $object->id = 0;
1240
        $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

1240
        /** @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...
1241
        $object->status = self::STATUS_DRAFT;
1242
1243
        // Clear fields
1244
        $object->date               = (empty($this->date) ? dol_now() : $this->date);
1245
        $object->user_creation_id   = $user->id;
1246
        $object->user_validation_id = null;
1247
        $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

1247
        /** @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...
1248
        $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

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

1433
        /** @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...
1434
        $this->origin_id = $object->id;
1435
1436
        $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

1436
        /** @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...
1437
1438
        // get extrafields from original line
1439
        $object->fetch_optionals();
1440
        foreach ($object->array_options as $options_key => $value) {
1441
            $this->array_options[$options_key] = $value;
1442
        }
1443
1444
        // Possibility to add external linked objects with hooks
1445
        $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

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

1568
        /** @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...
1569
        $this->origin_id = $object->id;
1570
1571
        $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

1571
        /** @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...
1572
1573
        // get extrafields from original line
1574
        $object->fetch_optionals();
1575
        foreach ($object->array_options as $options_key => $value) {
1576
            $this->array_options[$options_key] = $value;
1577
        }
1578
1579
        // Possibility to add external linked objects with hooks
1580
        $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

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

1692
        /** @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...
1693
        $deposit->origin_id = $origin->id;
1694
1695
        $origin->fetch_optionals();
1696
1697
        foreach ($origin->array_options as $extrakey => $value) {
1698
            $deposit->array_options[$extrakey] = $value;
1699
        }
1700
1701
        $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

1701
        $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...
1702
1703
        foreach ($overrideFields as $key => $value) {
1704
            $deposit->$key = $value;
1705
        }
1706
1707
        $deposit->context['createdepositfromorigin'] = 'createdepositfromorigin';
1708
1709
        $origin->db->begin();
1710
1711
        // Facture::create() also imports contact from origin
1712
        $createReturn = $deposit->create($user, $notrigger);
1713
1714
        if ($createReturn <= 0) {
1715
            $origin->db->rollback();
1716
            $origin->error = $deposit->error;
1717
            $origin->errors = $deposit->errors;
1718
            return null;
1719
        }
1720
1721
        $amount_ttc_diff = 0;
1722
        $amountdeposit = array();
1723
        $descriptions = array();
1724
1725
        if (getDolGlobalString('MAIN_DEPOSIT_MULTI_TVA')) {
1726
            $amount = $origin->total_ttc * ($origin->deposit_percent / 100);
1727
1728
            $TTotalByTva = array();
1729
            foreach ($origin->lines as &$line) {
1730
                if (!empty($line->special_code)) {
1731
                    continue;
1732
                }
1733
                $TTotalByTva[$line->tva_tx] += $line->total_ttc;
1734
                $descriptions[$line->tva_tx] .= '<li>' . (!empty($line->product_ref) ? $line->product_ref . ' - ' : '');
1735
                $descriptions[$line->tva_tx] .= (!empty($line->product_label) ? $line->product_label . ' - ' : '');
1736
                $descriptions[$line->tva_tx] .= $langs->trans('Qty') . ' : ' . $line->qty;
1737
                $descriptions[$line->tva_tx] .= ' - ' . $langs->trans('TotalHT') . ' : ' . price($line->total_ht) . '</li>';
1738
            }
1739
1740
            foreach ($TTotalByTva as $tva => &$total) {
1741
                $coef = $total / $origin->total_ttc; // Calc coef
1742
                $am = $amount * $coef;
1743
                $amount_ttc_diff += $am;
1744
                $amountdeposit[$tva] += $am / (1 + $tva / 100); // Convert into HT for the addline
1745
            }
1746
        } else {
1747
            $totalamount = 0;
1748
            $lines = $origin->lines;
1749
            $numlines = count($lines);
1750
            for ($i = 0; $i < $numlines; $i++) {
1751
                if (empty($lines[$i]->qty)) {
1752
                    continue; // We discard qty=0, it is an option
1753
                }
1754
                if (!empty($lines[$i]->special_code)) {
1755
                    continue; // We discard special_code (frais port, ecotaxe, option, ...)
1756
                }
1757
1758
                $totalamount += $lines[$i]->total_ht; // Fixme : is it not for the customer ? Shouldn't we take total_ttc ?
1759
                $tva_tx = $lines[$i]->tva_tx;
1760
                $amountdeposit[$tva_tx] += ($lines[$i]->total_ht * $origin->deposit_percent) / 100;
1761
                $descriptions[$tva_tx] .= '<li>' . (!empty($lines[$i]->product_ref) ? $lines[$i]->product_ref . ' - ' : '');
1762
                $descriptions[$tva_tx] .= (!empty($lines[$i]->product_label) ? $lines[$i]->product_label . ' - ' : '');
1763
                $descriptions[$tva_tx] .= $langs->trans('Qty') . ' : ' . $lines[$i]->qty;
1764
                $descriptions[$tva_tx] .= ' - ' . $langs->trans('TotalHT') . ' : ' . price($lines[$i]->total_ht) . '</li>';
1765
            }
1766
1767
            if ($totalamount == 0) {
1768
                $amountdeposit[0] = 0;
1769
            }
1770
1771
            $amount_ttc_diff = $amountdeposit[0];
1772
        }
1773
1774
        foreach ($amountdeposit as $tva => $amount) {
1775
            if (empty($amount)) {
1776
                continue;
1777
            }
1778
1779
            $descline = '(DEPOSIT) (' . $origin->deposit_percent . '%) - ' . $origin->ref;
1780
1781
            // Hidden conf
1782
            if (getDolGlobalString('INVOICE_DEPOSIT_VARIABLE_MODE_DETAIL_LINES_IN_DESCRIPTION') && !empty($descriptions[$tva])) {
1783
                $descline .= '<ul>' . $descriptions[$tva] . '</ul>';
1784
            }
1785
1786
            $addlineResult = $deposit->addline(
1787
                $descline,
1788
                $amount, // subprice
1789
                1, // quantity
1790
                $tva, // vat rate
1791
                0, // localtax1_tx
1792
                0, // localtax2_tx
1793
                (!getDolGlobalString('INVOICE_PRODUCTID_DEPOSIT') ? 0 : $conf->global->INVOICE_PRODUCTID_DEPOSIT), // fk_product
1794
                0, // remise_percent
1795
                0, // date_start
1796
                0, // date_end
1797
                0,
1798
                0, // info_bits
1799
                0,
1800
                'HT',
1801
                0,
1802
                0, // product_type
1803
                1,
1804
                0, // special_code
1805
                $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

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

2186
                /** @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...
2187
                $this->status = $obj->status;
2188
2189
                $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...
2190
                $this->mode_reglement_id    = $obj->fk_mode_reglement;
2191
                $this->mode_reglement_code  = $obj->mode_reglement_code;
2192
                $this->mode_reglement       = $obj->mode_reglement_libelle;
2193
                $this->cond_reglement_id    = $obj->fk_cond_reglement;
2194
                $this->cond_reglement_code  = $obj->cond_reglement_code;
2195
                $this->cond_reglement       = $obj->cond_reglement_libelle;
0 ignored issues
show
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

2195
                /** @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...
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...
2196
                $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
2197
                $this->fk_account = ($obj->fk_account > 0) ? $obj->fk_account : null;
2198
                $this->fk_facture_source    = $obj->fk_facture_source;
2199
                $this->fk_fac_rec_source    = $obj->fk_fac_rec_source;
2200
                $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

2200
                /** @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...
2201
                $this->note_private = $obj->note_private;
2202
                $this->note_public          = $obj->note_public;
2203
                $this->user_creation_id     = $obj->fk_user_author;
2204
                $this->user_validation_id   = $obj->fk_user_valid;
2205
                $this->user_modification_id = $obj->fk_user_modif;
2206
                $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

2206
                /** @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...
2207
                $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

2207
                /** @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...
2208
                $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

2208
                /** @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...
2209
                $this->model_pdf = $obj->model_pdf;
2210
                $this->last_main_doc = $obj->last_main_doc;
2211
                $this->situation_cycle_ref  = $obj->situation_cycle_ref;
2212
                $this->situation_counter    = $obj->situation_counter;
2213
                $this->situation_final      = $obj->situation_final;
2214
                $this->retained_warranty    = $obj->retained_warranty;
2215
                $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...
2216
                $this->retained_warranty_fk_cond_reglement  = $obj->retained_warranty_fk_cond_reglement;
2217
2218
                $this->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
2219
2220
                //Incoterms
2221
                $this->fk_incoterms         = $obj->fk_incoterms;
2222
                $this->location_incoterms   = $obj->location_incoterms;
2223
                $this->label_incoterms = $obj->label_incoterms;
2224
2225
                $this->module_source = $obj->module_source;
2226
                $this->pos_source = $obj->pos_source;
2227
2228
                // Multicurrency
2229
                $this->fk_multicurrency         = $obj->fk_multicurrency;
2230
                $this->multicurrency_code = $obj->multicurrency_code;
2231
                $this->multicurrency_tx         = $obj->multicurrency_tx;
2232
                $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
2233
                $this->multicurrency_total_tva  = $obj->multicurrency_total_tva;
2234
                $this->multicurrency_total_ttc  = $obj->multicurrency_total_ttc;
2235
2236
                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...
2237
                    $this->fetchPreviousNextSituationInvoice();
2238
                }
2239
2240
                // Retrieve all extrafield
2241
                // fetch optionals attributes and labels
2242
                $this->fetch_optionals();
2243
2244
                // Lines
2245
                $this->lines = array();
2246
2247
                $result = $this->fetch_lines();
2248
                if ($result < 0) {
2249
                    $this->error = $this->db->error();
2250
                    return -3;
2251
                }
2252
2253
                $this->db->free($resql);
2254
2255
                return 1;
2256
            } else {
2257
                $this->error = 'Invoice with id=' . $rowid . ' or ref=' . $ref . ' or ref_ext=' . $ref_ext . ' not found';
2258
2259
                dol_syslog(__METHOD__ . $this->error, LOG_WARNING);
2260
                return 0;
2261
            }
2262
        } else {
2263
            $this->error = $this->db->lasterror();
2264
            return -1;
2265
        }
2266
    }
2267
2268
2269
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2270
    /**
2271
     *  Load all detailed lines into this->lines
2272
     *
2273
     *  @param      int     $only_product   Return only physical products
2274
     *  @param      int     $loadalsotranslation    Return translation for products
2275
     *
2276
     *  @return     int         1 if OK, < 0 if KO
2277
     */
2278
    public function fetch_lines($only_product = 0, $loadalsotranslation = 0)
2279
    {
2280
		// phpcs:enable
2281
        $this->lines = array();
2282
2283
        $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,';
2284
        $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,';
2285
        $sql .= ' l.situation_percent, l.fk_prev_id,';
2286
        $sql .= ' l.rang, l.special_code, l.batch, l.fk_warehouse,';
2287
        $sql .= ' l.date_start as date_start, l.date_end as date_end,';
2288
        $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,';
2289
        $sql .= ' l.fk_unit,';
2290
        $sql .= ' l.fk_multicurrency, l.multicurrency_code, l.multicurrency_subprice, l.multicurrency_total_ht, l.multicurrency_total_tva, l.multicurrency_total_ttc,';
2291
        $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';
2292
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'facturedet as l';
2293
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'product as p ON l.fk_product = p.rowid';
2294
        $sql .= ' WHERE l.fk_facture = ' . ((int) $this->id);
2295
        $sql .= ' ORDER BY l.rang, l.rowid';
2296
2297
        dol_syslog(get_class($this) . '::fetch_lines', LOG_DEBUG);
2298
        $result = $this->db->query($sql);
2299
        if ($result) {
2300
            $num = $this->db->num_rows($result);
2301
            $i = 0;
2302
            while ($i < $num) {
2303
                $objp = $this->db->fetch_object($result);
2304
                $line = new FactureLigne($this->db);
2305
2306
                $line->id               = $objp->rowid;
2307
                $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

2307
                /** @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...
2308
                $line->fk_facture       = $objp->fk_facture;
2309
                $line->label            = $objp->custom_label; // deprecated
2310
                $line->desc             = $objp->description; // Description line
2311
                $line->description      = $objp->description; // Description line
2312
                $line->product_type     = $objp->product_type; // Type of line
2313
                $line->ref              = $objp->product_ref; // Ref product
2314
                $line->product_ref      = $objp->product_ref; // Ref product
2315
                $line->libelle          = $objp->product_label; // deprecated
2316
                $line->product_label    = $objp->product_label; // Label product
2317
                $line->product_barcode  = $objp->product_barcode; // Barcode number product
2318
                $line->product_desc     = $objp->product_desc; // Description product
2319
                $line->fk_product_type  = $objp->fk_product_type; // Type of product
2320
                $line->qty              = $objp->qty;
2321
                $line->subprice         = $objp->subprice;
2322
                $line->ref_ext          = $objp->ref_ext; // line external ref
2323
2324
                $line->vat_src_code = $objp->vat_src_code;
2325
                $line->tva_tx           = $objp->tva_tx;
2326
                $line->localtax1_tx     = $objp->localtax1_tx;
2327
                $line->localtax2_tx     = $objp->localtax2_tx;
2328
                $line->localtax1_type   = $objp->localtax1_type;
2329
                $line->localtax2_type   = $objp->localtax2_type;
2330
                $line->remise_percent   = $objp->remise_percent;
2331
                $line->fk_remise_except = $objp->fk_remise_except;
2332
                $line->fk_product       = $objp->fk_product;
2333
                $line->date_start       = $this->db->jdate($objp->date_start);
2334
                $line->date_end         = $this->db->jdate($objp->date_end);
2335
                $line->info_bits        = $objp->info_bits;
2336
                $line->total_ht         = $objp->total_ht;
2337
                $line->total_tva        = $objp->total_tva;
2338
                $line->total_localtax1  = $objp->total_localtax1;
2339
                $line->total_localtax2  = $objp->total_localtax2;
2340
                $line->total_ttc        = $objp->total_ttc;
2341
2342
                $line->fk_fournprice = $objp->fk_fournprice;
2343
                $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
2344
                $line->pa_ht = $marginInfos[0];
2345
                $line->marge_tx         = $marginInfos[1];
2346
                $line->marque_tx        = $marginInfos[2];
2347
                $line->rang = $objp->rang;
2348
                $line->special_code = $objp->special_code;
2349
                $line->fk_parent_line = $objp->fk_parent_line;
2350
                $line->situation_percent = $objp->situation_percent;
2351
                $line->fk_prev_id = $objp->fk_prev_id;
2352
                $line->fk_unit = $objp->fk_unit;
2353
2354
                $line->batch = $objp->batch;
2355
                $line->fk_warehouse = $objp->fk_warehouse;
2356
2357
                // Accountancy
2358
                $line->fk_accounting_account = $objp->fk_code_ventilation;
2359
2360
                // Multicurrency
2361
                $line->fk_multicurrency = $objp->fk_multicurrency;
2362
                $line->multicurrency_code = $objp->multicurrency_code;
2363
                $line->multicurrency_subprice   = $objp->multicurrency_subprice;
2364
                $line->multicurrency_total_ht   = $objp->multicurrency_total_ht;
2365
                $line->multicurrency_total_tva  = $objp->multicurrency_total_tva;
2366
                $line->multicurrency_total_ttc  = $objp->multicurrency_total_ttc;
2367
2368
                $line->fetch_optionals();
2369
2370
                // multilangs
2371
                if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
2372
                    $tmpproduct = new Product($this->db);
2373
                    $tmpproduct->fetch($objp->fk_product);
2374
                    $tmpproduct->getMultiLangs();
2375
2376
                    $line->multilangs = $tmpproduct->multilangs;
2377
                }
2378
2379
                $this->lines[$i] = $line;
2380
2381
                $i++;
2382
            }
2383
            $this->db->free($result);
2384
            return 1;
2385
        } else {
2386
            $this->error = $this->db->error();
2387
            return -3;
2388
        }
2389
    }
2390
2391
    /**
2392
     * Fetch previous and next situations invoices.
2393
     * Return all previous and next invoices (both standard and credit notes).
2394
     *
2395
     * @return  void
2396
     */
2397
    public function fetchPreviousNextSituationInvoice()
2398
    {
2399
        global $conf;
2400
2401
        $this->tab_previous_situation_invoice = array();
2402
        $this->tab_next_situation_invoice = array();
2403
2404
        $sql = 'SELECT rowid, type, situation_cycle_ref, situation_counter FROM ' . MAIN_DB_PREFIX . 'facture';
2405
        $sql .= " WHERE rowid <> " . ((int) $this->id);
2406
        $sql .= ' AND entity = ' . ((int) $this->entity);
2407
        $sql .= ' AND situation_cycle_ref = ' . (int) $this->situation_cycle_ref;
2408
        $sql .= ' ORDER BY situation_counter ASC';
2409
2410
        dol_syslog(get_class($this) . '::fetchPreviousNextSituationInvoice ', LOG_DEBUG);
2411
        $result = $this->db->query($sql);
2412
        if ($result && $this->db->num_rows($result) > 0) {
2413
            while ($objp = $this->db->fetch_object($result)) {
2414
                $invoice = new Facture($this->db);
2415
                if ($invoice->fetch($objp->rowid) > 0) {
2416
                    if (
2417
                        $objp->situation_counter < $this->situation_counter
2418
                        || ($objp->situation_counter == $this->situation_counter && $objp->rowid < $this->id) // This case appear when there are credit notes
2419
                    ) {
2420
                        $this->tab_previous_situation_invoice[] = $invoice;
2421
                    } else {
2422
                        $this->tab_next_situation_invoice[] = $invoice;
2423
                    }
2424
                }
2425
            }
2426
        }
2427
    }
2428
2429
    /**
2430
     *      Update database
2431
     *
2432
     *      @param      User    $user           User that modify
2433
     *      @param      int     $notrigger      0=launch triggers after, 1=disable triggers
2434
     *      @return     int                     Return integer <0 if KO, >0 if OK
2435
     */
2436
    public function update(User $user, $notrigger = 0)
2437
    {
2438
        $error = 0;
2439
2440
        // Clean parameters
2441
        if (empty($this->type)) {
2442
            $this->type = self::TYPE_STANDARD;
2443
        }
2444
        if (isset($this->subtype)) {
2445
            $this->subtype = (int) trim((string) $this->subtype);
2446
        }
2447
        if (isset($this->ref)) {
2448
            $this->ref = trim($this->ref);
2449
        }
2450
        if (isset($this->ref_ext)) {
2451
            $this->ref_ext = trim($this->ref_ext);
2452
        }
2453
        if (isset($this->ref_client)) {
2454
            $this->ref_client = trim($this->ref_client);
2455
        }
2456
        if (isset($this->increment)) {
2457
            $this->increment = trim($this->increment);
2458
        }
2459
        if (isset($this->close_code)) {
2460
            $this->close_code = trim($this->close_code);
2461
        }
2462
        if (isset($this->close_note)) {
2463
            $this->close_note = trim($this->close_note);
2464
        }
2465
        if (isset($this->note) || isset($this->note_private)) {
2466
            $this->note = (isset($this->note) ? trim($this->note) : trim($this->note_private)); // deprecated
2467
        }
2468
        if (isset($this->note) || isset($this->note_private)) {
2469
            $this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
2470
        }
2471
        if (isset($this->note_public)) {
2472
            $this->note_public = trim($this->note_public);
2473
        }
2474
        if (isset($this->model_pdf)) {
2475
            $this->model_pdf = trim($this->model_pdf);
2476
        }
2477
        if (isset($this->import_key)) {
2478
            $this->import_key = trim($this->import_key);
2479
        }
2480
        if (isset($this->retained_warranty)) {
2481
            $this->retained_warranty = (float) $this->retained_warranty;
2482
        }
2483
2484
2485
        // Check parameters
2486
        // Put here code to add control on parameters values
2487
2488
        // Update request
2489
        $sql = "UPDATE " . MAIN_DB_PREFIX . "facture SET";
2490
        $sql .= " ref=" . (isset($this->ref) ? "'" . $this->db->escape($this->ref) . "'" : "null") . ",";
2491
        $sql .= " ref_ext=" . (isset($this->ref_ext) ? "'" . $this->db->escape($this->ref_ext) . "'" : "null") . ",";
2492
        $sql .= " type=" . (isset($this->type) ? $this->db->escape($this->type) : "null") . ",";
2493
        $sql .= " subtype=" . (isset($this->subtype) ? $this->db->escape($this->subtype) : "null") . ",";
2494
        $sql .= " ref_client=" . (isset($this->ref_client) ? "'" . $this->db->escape($this->ref_client) . "'" : "null") . ",";
2495
        $sql .= " increment=" . (isset($this->increment) ? "'" . $this->db->escape($this->increment) . "'" : "null") . ",";
2496
        $sql .= " fk_soc=" . (isset($this->socid) ? $this->db->escape($this->socid) : "null") . ",";
2497
        $sql .= " datec=" . (strval($this->date_creation) != '' ? "'" . $this->db->idate($this->date_creation) . "'" : 'null') . ",";
2498
        $sql .= " datef=" . (strval($this->date) != '' ? "'" . $this->db->idate($this->date) . "'" : 'null') . ",";
2499
        $sql .= " date_pointoftax=" . (strval($this->date_pointoftax) != '' ? "'" . $this->db->idate($this->date_pointoftax) . "'" : 'null') . ",";
2500
        $sql .= " date_valid=" . (strval($this->date_validation) != '' ? "'" . $this->db->idate($this->date_validation) . "'" : 'null') . ",";
2501
        $sql .= " paye=" . (isset($this->paye) ? $this->db->escape($this->paye) : 0) . ",";
2502
        $sql .= " close_code=" . (isset($this->close_code) ? "'" . $this->db->escape($this->close_code) . "'" : "null") . ",";
2503
        $sql .= " close_note=" . (isset($this->close_note) ? "'" . $this->db->escape($this->close_note) . "'" : "null") . ",";
2504
        $sql .= " total_tva=" . (isset($this->total_tva) ? (float) $this->total_tva : "null") . ",";
2505
        $sql .= " localtax1=" . (isset($this->total_localtax1) ? (float) $this->total_localtax1 : "null") . ",";
2506
        $sql .= " localtax2=" . (isset($this->total_localtax2) ? (float) $this->total_localtax2 : "null") . ",";
2507
        $sql .= " total_ht=" . (isset($this->total_ht) ? (float) $this->total_ht : "null") . ",";
2508
        $sql .= " total_ttc=" . (isset($this->total_ttc) ? (float) $this->total_ttc : "null") . ",";
2509
        $sql .= " revenuestamp=" . ((isset($this->revenuestamp) && $this->revenuestamp != '') ? (int) $this->revenuestamp : "null") . ",";
2510
        $sql .= " fk_statut=" . (isset($this->status) ? (int) $this->status : "null") . ",";
2511
        $sql .= " fk_user_valid=" . (isset($this->fk_user_valid) ? (int) $this->fk_user_valid : "null") . ",";
2512
        $sql .= " fk_facture_source=" . (isset($this->fk_facture_source) ? (int) $this->fk_facture_source : "null") . ",";
2513
        $sql .= " fk_projet=" . (isset($this->fk_project) ? (int) $this->fk_project : "null") . ",";
2514
        $sql .= " fk_cond_reglement=" . (isset($this->cond_reglement_id) ? (int) $this->cond_reglement_id : "null") . ",";
2515
        $sql .= " fk_mode_reglement=" . (isset($this->mode_reglement_id) ? (int) $this->mode_reglement_id : "null") . ",";
2516
        $sql .= " date_lim_reglement=" . (strval($this->date_lim_reglement) != '' ? "'" . $this->db->idate($this->date_lim_reglement) . "'" : 'null') . ",";
2517
        $sql .= " note_private=" . (isset($this->note_private) ? "'" . $this->db->escape($this->note_private) . "'" : "null") . ",";
2518
        $sql .= " note_public=" . (isset($this->note_public) ? "'" . $this->db->escape($this->note_public) . "'" : "null") . ",";
2519
        $sql .= " model_pdf=" . (isset($this->model_pdf) ? "'" . $this->db->escape($this->model_pdf) . "'" : "null") . ",";
2520
        $sql .= " import_key=" . (isset($this->import_key) ? "'" . $this->db->escape($this->import_key) . "'" : "null") . ",";
2521
        $sql .= " situation_cycle_ref=" . (empty($this->situation_cycle_ref) ? "null" : (int) $this->situation_cycle_ref) . ",";
2522
        $sql .= " situation_counter=" . (empty($this->situation_counter) ? "null" : (int) $this->situation_counter) . ",";
2523
        $sql .= " situation_final=" . (empty($this->situation_final) ? "0" : (int) $this->situation_final) . ",";
2524
        $sql .= " retained_warranty=" . (empty($this->retained_warranty) ? "0" : (float) $this->retained_warranty) . ",";
2525
        $sql .= " retained_warranty_date_limit=" . (strval($this->retained_warranty_date_limit) != '' ? "'" . $this->db->idate($this->retained_warranty_date_limit) . "'" : 'null') . ",";
2526
        $sql .= " retained_warranty_fk_cond_reglement=" . (isset($this->retained_warranty_fk_cond_reglement) ? (int) $this->retained_warranty_fk_cond_reglement : "null");
2527
        $sql .= " WHERE rowid=" . ((int) $this->id);
2528
2529
        $this->db->begin();
2530
2531
        dol_syslog(get_class($this) . "::update", LOG_DEBUG);
2532
        $resql = $this->db->query($sql);
2533
        if (!$resql) {
2534
            $error++;
2535
            $this->errors[] = "Error " . $this->db->lasterror();
2536
        }
2537
2538
        if (!$error) {
2539
            $result = $this->insertExtraFields();
2540
            if ($result < 0) {
2541
                $error++;
2542
            }
2543
        }
2544
2545
        if (!$error && !$notrigger) {
2546
            // Call trigger
2547
            $result = $this->call_trigger('BILL_MODIFY', $user);
2548
            if ($result < 0) {
2549
                $error++;
2550
            }
2551
            // End call triggers
2552
        }
2553
2554
        // Commit or rollback
2555
        if ($error) {
2556
            foreach ($this->errors as $errmsg) {
2557
                dol_syslog(get_class($this) . "::update " . $errmsg, LOG_ERR);
2558
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2559
            }
2560
            $this->db->rollback();
2561
            return -1 * $error;
2562
        } else {
2563
            $this->db->commit();
2564
            return 1;
2565
        }
2566
    }
2567
2568
2569
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2570
    /**
2571
     *    Add a discount line into an invoice (as an invoice line) using an existing absolute discount (Consume the discount)
2572
     *
2573
     *    @param     int    $idremise   Id of absolute discount
2574
     *    @return    int                >0 if OK, <0 if KO
2575
     */
2576
    public function insert_discount($idremise)
2577
    {
2578
		// phpcs:enable
2579
        global $conf, $langs;
2580
2581
        include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
2582
        include_once DOL_DOCUMENT_ROOT . '/core/class/discount.class.php';
2583
2584
        $this->db->begin();
2585
2586
        $remise = new DiscountAbsolute($this->db);
2587
        $result = $remise->fetch($idremise);
2588
2589
        if ($result > 0) {
2590
            if ($remise->fk_facture) {  // Protection against multiple submission
2591
                $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
2592
                $this->db->rollback();
2593
                return -5;
2594
            }
2595
2596
            $facligne = new FactureLigne($this->db);
2597
            $facligne->fk_facture = $this->id;
2598
            $facligne->fk_remise_except = $remise->id;
2599
            $facligne->desc = $remise->description; // Description ligne
2600
            $facligne->vat_src_code = $remise->vat_src_code;
2601
            $facligne->tva_tx = $remise->tva_tx;
2602
            $facligne->subprice = -$remise->amount_ht;
2603
            $facligne->fk_product = 0; // Id produit predefini
2604
            $facligne->qty = 1;
2605
            $facligne->remise_percent = 0;
2606
            $facligne->rang = -1;
2607
            $facligne->info_bits = 2;
2608
2609
            if (getDolGlobalString('MAIN_ADD_LINE_AT_POSITION')) {
2610
                $facligne->rang = 1;
2611
                $linecount = count($this->lines);
2612
                for ($ii = 1; $ii <= $linecount; $ii++) {
2613
                    $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
2614
                }
2615
            }
2616
2617
            // Get buy/cost price of invoice that is source of discount
2618
            if ($remise->fk_facture_source > 0) {
2619
                $srcinvoice = new Facture($this->db);
2620
                $srcinvoice->fetch($remise->fk_facture_source);
2621
                include_once DOL_DOCUMENT_ROOT . '/core/class/html.formmargin.class.php'; // TODO Move this into commonobject
2622
                $formmargin = new FormMargin($this->db);
2623
                $arraytmp = $formmargin->getMarginInfosArray($srcinvoice, false);
2624
                $facligne->pa_ht = $arraytmp['pa_total'];
2625
            }
2626
2627
            $facligne->total_ht  = -$remise->amount_ht;
2628
            $facligne->total_tva = -$remise->amount_tva;
2629
            $facligne->total_ttc = -$remise->amount_ttc;
2630
2631
            $facligne->multicurrency_subprice = -$remise->multicurrency_subprice;
2632
            $facligne->multicurrency_total_ht = -$remise->multicurrency_amount_ht;
2633
            $facligne->multicurrency_total_tva = -$remise->multicurrency_amount_tva;
2634
            $facligne->multicurrency_total_ttc = -$remise->multicurrency_amount_ttc;
2635
2636
            $lineid = $facligne->insert();
2637
            if ($lineid > 0) {
2638
                $result = $this->update_price(1);
2639
                if ($result > 0) {
2640
                    // Create link between discount and invoice line
2641
                    $result = $remise->link_to_invoice($lineid, 0);
2642
                    if ($result < 0) {
2643
                        $this->error = $remise->error;
2644
                        $this->db->rollback();
2645
                        return -4;
2646
                    }
2647
2648
                    $this->db->commit();
2649
                    return 1;
2650
                } else {
2651
                    $this->error = $facligne->error;
2652
                    $this->db->rollback();
2653
                    return -1;
2654
                }
2655
            } else {
2656
                $this->error = $facligne->error;
2657
                $this->db->rollback();
2658
                return -2;
2659
            }
2660
        } else {
2661
            $this->db->rollback();
2662
            return -3;
2663
        }
2664
    }
2665
2666
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2667
    /**
2668
     *  Set customer ref
2669
     *
2670
     *  @param      string  $ref_client     Customer ref
2671
     *  @param      int     $notrigger      1=Does not execute triggers, 0= execute triggers
2672
     *  @return     int                     Return integer <0 if KO, >0 if OK
2673
     */
2674
    public function set_ref_client($ref_client, $notrigger = 0)
2675
    {
2676
		// phpcs:enable
2677
        global $user;
2678
2679
        $error = 0;
2680
2681
        $this->db->begin();
2682
2683
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture';
2684
        if (empty($ref_client)) {
2685
            $sql .= ' SET ref_client = NULL';
2686
        } else {
2687
            $sql .= ' SET ref_client = \'' . $this->db->escape($ref_client) . '\'';
2688
        }
2689
        $sql .= " WHERE rowid = " . ((int) $this->id);
2690
2691
        dol_syslog(__METHOD__ . ' this->id=' . $this->id . ', ref_client=' . $ref_client, LOG_DEBUG);
2692
        $resql = $this->db->query($sql);
2693
        if (!$resql) {
2694
            $this->errors[] = $this->db->error();
2695
            $error++;
2696
        }
2697
2698
        if (!$error) {
2699
            $this->ref_client = $ref_client;
2700
        }
2701
2702
        if (!$notrigger && empty($error)) {
2703
            // Call trigger
2704
            $result = $this->call_trigger('BILL_MODIFY', $user);
2705
            if ($result < 0) {
2706
                $error++;
2707
            }
2708
            // End call triggers
2709
        }
2710
2711
        if (!$error) {
2712
            $this->ref_client = $ref_client;
2713
2714
            $this->db->commit();
2715
            return 1;
2716
        } else {
2717
            foreach ($this->errors as $errmsg) {
2718
                dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
2719
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2720
            }
2721
            $this->db->rollback();
2722
            return -1 * $error;
2723
        }
2724
    }
2725
2726
    /**
2727
     *  Delete invoice
2728
     *
2729
     *  @param      User    $user           User making the deletion.
2730
     *  @param      int     $notrigger      1=Does not execute triggers, 0= execute triggers
2731
     *  @param      int     $idwarehouse    Id warehouse to use for stock change.
2732
     *  @return     int                     Return integer <0 if KO, 0=Refused, >0 if OK
2733
     */
2734
    public function delete($user, $notrigger = 0, $idwarehouse = -1)
2735
    {
2736
        global $langs, $conf;
2737
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
2738
2739
        $rowid = $this->id;
2740
2741
        dol_syslog(get_class($this) . "::delete rowid=" . $rowid . ", ref=" . $this->ref . ", thirdparty=" . (empty($this->thirdparty) ? '' : $this->thirdparty->name), LOG_DEBUG);
2742
2743
        // Test to avoid invoice deletion (allowed if draft)
2744
        $result = $this->is_erasable();
2745
2746
        if ($result <= 0) {
2747
            return 0;
2748
        }
2749
2750
        $error = 0;
2751
2752
        $this->db->begin();
2753
2754
        if (!$error && !$notrigger) {
2755
            // Call trigger
2756
            $result = $this->call_trigger('BILL_DELETE', $user);
2757
            if ($result < 0) {
2758
                $error++;
2759
            }
2760
            // End call triggers
2761
        }
2762
2763
        // Removed extrafields
2764
        if (!$error) {
2765
            $result = $this->deleteExtraFields();
2766
            if ($result < 0) {
2767
                $error++;
2768
                dol_syslog(get_class($this) . "::delete error deleteExtraFields " . $this->error, LOG_ERR);
2769
            }
2770
        }
2771
2772
        if (!$error) {
2773
            // Delete linked object
2774
            $res = $this->deleteObjectLinked();
2775
            if ($res < 0) {
2776
                $error++;
2777
            }
2778
        }
2779
2780
        if (!$error) {
2781
            // If invoice was converted into a discount not yet consumed, we remove discount
2782
            $sql = 'DELETE FROM ' . MAIN_DB_PREFIX . 'societe_remise_except';
2783
            $sql .= ' WHERE fk_facture_source = ' . ((int) $rowid);
2784
            $sql .= ' AND fk_facture_line IS NULL';
2785
            $resql = $this->db->query($sql);
2786
2787
            // If invoice has consumed discounts
2788
            $this->fetch_lines();
2789
            $list_rowid_det = array();
2790
            foreach ($this->lines as $key => $invoiceline) {
2791
                $list_rowid_det[] = $invoiceline->id;
2792
            }
2793
2794
            // Consumed discounts are freed
2795
            if (count($list_rowid_det)) {
2796
                $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'societe_remise_except';
2797
                $sql .= ' SET fk_facture = NULL, fk_facture_line = NULL';
2798
                $sql .= ' WHERE fk_facture_line IN (' . $this->db->sanitize(implode(',', $list_rowid_det)) . ')';
2799
2800
                if (!$this->db->query($sql)) {
2801
                    $this->error = $this->db->error() . " sql=" . $sql;
2802
                    $this->errors[] = $this->error;
2803
                    $this->db->rollback();
2804
                    return -5;
2805
                }
2806
            }
2807
2808
            // Remove other links to the deleted invoice
2809
2810
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'eventorganization_conferenceorboothattendee';
2811
            $sql .= ' SET fk_invoice = NULL';
2812
            $sql .= ' WHERE fk_invoice = ' . ((int) $rowid);
2813
2814
            if (!$this->db->query($sql)) {
2815
                $this->error = $this->db->error() . " sql=" . $sql;
2816
                $this->errors[] = $this->error;
2817
                $this->db->rollback();
2818
                return -5;
2819
            }
2820
2821
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'element_time';
2822
            $sql .= ' SET invoice_id = NULL, invoice_line_id = NULL';
2823
            $sql .= ' WHERE invoice_id = ' . ((int) $rowid);
2824
2825
            if (!$this->db->query($sql)) {
2826
                $this->error = $this->db->error() . " sql=" . $sql;
2827
                $this->errors[] = $this->error;
2828
                $this->db->rollback();
2829
                return -5;
2830
            }
2831
2832
            // If we decrease stock on invoice validation, we increase back if a warehouse id was provided
2833
            if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_BILL') && $idwarehouse != -1) {
2834
                $langs->load("agenda");
2835
2836
                $num = count($this->lines);
2837
                for ($i = 0; $i < $num; $i++) {
2838
                    if ($this->lines[$i]->fk_product > 0) {
2839
                        $mouvP = new MouvementStock($this->db);
2840
                        $mouvP->origin = &$this;
2841
                        $mouvP->setOrigin($this->element, $this->id);
2842
                        // We decrease stock for product
2843
                        if ($this->type == self::TYPE_CREDIT_NOTE) {
2844
                            $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceDeleteDolibarr", $this->ref));
2845
                        } else {
2846
                            $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
2847
                        }
2848
                    }
2849
                }
2850
            }
2851
2852
            // Invoice line extrafileds
2853
            $main = MAIN_DB_PREFIX . 'facturedet';
2854
            $ef = $main . "_extrafields";
2855
            $sqlef = "DELETE FROM " . $ef . " WHERE fk_object IN (SELECT rowid FROM " . $main . " WHERE fk_facture = " . ((int) $rowid) . ")";
2856
            // Delete invoice line
2857
            $sql = 'DELETE FROM ' . MAIN_DB_PREFIX . 'facturedet WHERE fk_facture = ' . ((int) $rowid);
2858
2859
            if ($this->db->query($sqlef) && $this->db->query($sql) && $this->delete_linked_contact() >= 0) {
2860
                $sql = 'DELETE FROM ' . MAIN_DB_PREFIX . 'facture WHERE rowid = ' . ((int) $rowid);
2861
2862
                $resql = $this->db->query($sql);
2863
                if ($resql) {
2864
                    // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
2865
                    $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
2866
                    $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
2867
2868
                    // On efface le repertoire de pdf provisoire
2869
                    $ref = dol_sanitizeFileName($this->ref);
2870
                    if ($conf->facture->dir_output && !empty($this->ref)) {
2871
                        $dir = $conf->facture->dir_output . "/" . $ref;
2872
                        $file = $conf->facture->dir_output . "/" . $ref . "/" . $ref . ".pdf";
2873
                        if (file_exists($file)) {   // We must delete all files before deleting directory
2874
                            $ret = dol_delete_preview($this);
2875
2876
                            if (!dol_delete_file($file, 0, 0, 0, $this)) { // For triggers
2877
                                $langs->load("errors");
2878
                                $this->error = $langs->trans("ErrorFailToDeleteFile", $file);
2879
                                $this->errors[] = $this->error;
2880
                                $this->db->rollback();
2881
                                return 0;
2882
                            }
2883
                        }
2884
                        if (file_exists($dir)) {
2885
                            if (!dol_delete_dir_recursive($dir)) { // For remove dir and meta
2886
                                $langs->load("errors");
2887
                                $this->error = $langs->trans("ErrorFailToDeleteDir", $dir);
2888
                                $this->errors[] = $this->error;
2889
                                $this->db->rollback();
2890
                                return 0;
2891
                            }
2892
                        }
2893
                    }
2894
2895
                    $this->db->commit();
2896
                    return 1;
2897
                } else {
2898
                    $this->error = $this->db->lasterror() . " sql=" . $sql;
2899
                    $this->errors[] = $this->error;
2900
                    $this->db->rollback();
2901
                    return -6;
2902
                }
2903
            } else {
2904
                $this->error = $this->db->lasterror() . " sql=" . $sql;
2905
                $this->errors[] = $this->error;
2906
                $this->db->rollback();
2907
                return -4;
2908
            }
2909
        } else {
2910
            $this->db->rollback();
2911
            return -2;
2912
        }
2913
    }
2914
2915
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2916
    /**
2917
     *  Tag the invoice as paid completely (if close_code is filled) => this->fk_statut=2, this->paye=1
2918
     *  or partially (if close_code filled) + appel trigger BILL_PAYED => this->fk_statut=2, this->paye stay 0
2919
     *
2920
     *  @deprecated
2921
     *  @see setPaid()
2922
     *  @param  User    $user       Object user that modify
2923
     *  @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)
2924
     *  @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)
2925
     *  @return int                 Return integer <0 if KO, >0 if OK
2926
     */
2927
    public function set_paid($user, $close_code = '', $close_note = '')
2928
    {
2929
		// phpcs:enable
2930
        dol_syslog(get_class($this) . "::set_paid is deprecated, use setPaid instead", LOG_NOTICE);
2931
        return $this->setPaid($user, $close_code, $close_note);
2932
    }
2933
2934
    /**
2935
     *  Tag the invoice as :
2936
     *  - paid completely (if close_code is not filled) => this->fk_statut=2, this->paye=1
2937
     *  - or partially (if close_code filled) + appel trigger BILL_PAYED => this->fk_statut=2, this->paye stay 0
2938
     *
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 setPaid($user, $close_code = '', $close_note = '')
2945
    {
2946
        $error = 0;
2947
2948
        if ($this->paye != 1) {
2949
            $this->db->begin();
2950
2951
            $now = dol_now();
2952
2953
            dol_syslog(get_class($this) . "::setPaid rowid=" . ((int) $this->id), LOG_DEBUG);
2954
2955
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture SET';
2956
            $sql .= ' fk_statut=' . self::STATUS_CLOSED;
2957
            if (!$close_code) {
2958
                $sql .= ', paye=1';
2959
            }
2960
            if ($close_code) {
2961
                $sql .= ", close_code='" . $this->db->escape($close_code) . "'";
2962
            }
2963
            if ($close_note) {
2964
                $sql .= ", close_note='" . $this->db->escape($close_note) . "'";
2965
            }
2966
            $sql .= ', fk_user_closing = ' . ((int) $user->id);
2967
            $sql .= ", date_closing = '" . $this->db->idate($now) . "'";
2968
            $sql .= " WHERE rowid = " . ((int) $this->id);
2969
2970
            $resql = $this->db->query($sql);
2971
            if ($resql) {
2972
                // Call trigger
2973
                $result = $this->call_trigger('BILL_PAYED', $user);
2974
                if ($result < 0) {
2975
                    $error++;
2976
                }
2977
                // End call triggers
2978
            } else {
2979
                $error++;
2980
                $this->error = $this->db->lasterror();
2981
            }
2982
2983
            if (!$error) {
2984
                $this->db->commit();
2985
                return 1;
2986
            } else {
2987
                $this->db->rollback();
2988
                return -1;
2989
            }
2990
        } else {
2991
            return 0;
2992
        }
2993
    }
2994
2995
2996
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2997
    /**
2998
     *  Tags the invoice as incompletely paid and call the trigger BILL_UNPAYED
2999
     *  This method is used when a direct debit (fr:prelevement) is refused
3000
     *  or when a canceled invoice is reopened.
3001
     *
3002
     *  @deprecated
3003
     *  @see setUnpaid()
3004
     *  @param  User    $user       Object user that change status
3005
     *  @return int                 Return integer <0 if KO, >0 if OK
3006
     */
3007
    public function set_unpaid($user)
3008
    {
3009
		// phpcs:enable
3010
        dol_syslog(get_class($this) . "::set_unpaid is deprecated, use setUnpaid instead", LOG_NOTICE);
3011
        return $this->setUnpaid($user);
3012
    }
3013
3014
    /**
3015
     *  Tag 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
     *  @param  User    $user       Object user that change status
3020
     *  @return int                 Return integer <0 if KO, >0 if OK
3021
     */
3022
    public function setUnpaid($user)
3023
    {
3024
        $error = 0;
3025
3026
        $this->db->begin();
3027
3028
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture';
3029
        $sql .= ' SET paye=0, fk_statut=' . self::STATUS_VALIDATED . ', close_code=null, close_note=null,';
3030
        $sql .= ' date_closing=null,';
3031
        $sql .= ' fk_user_closing=null';
3032
        $sql .= " WHERE rowid = " . ((int) $this->id);
3033
3034
        dol_syslog(get_class($this) . "::setUnpaid", LOG_DEBUG);
3035
        $resql = $this->db->query($sql);
3036
        if ($resql) {
3037
            // Call trigger
3038
            $result = $this->call_trigger('BILL_UNPAYED', $user);
3039
            if ($result < 0) {
3040
                $error++;
3041
            }
3042
            // End call triggers
3043
        } else {
3044
            $error++;
3045
            $this->error = $this->db->error();
3046
            dol_print_error($this->db);
3047
        }
3048
3049
        if (!$error) {
3050
            $this->db->commit();
3051
            return 1;
3052
        } else {
3053
            $this->db->rollback();
3054
            return -1;
3055
        }
3056
    }
3057
3058
3059
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3060
    /**
3061
     *  Tag invoice as canceled, with no payment on it (example for replacement invoice or payment never received) + call trigger BILL_CANCEL
3062
     *  Warning, if option to decrease stock on invoice was set, this function does not change stock (it might be a cancel because
3063
     *  of no payment even if merchandises were sent).
3064
     *
3065
     *  @deprecated
3066
     *  @see setCanceled()
3067
     *  @param  User    $user           Object user making change
3068
     *  @param  string  $close_code     Code of closing invoice (CLOSECODE_REPLACED, CLOSECODE_...)
3069
     *  @param  string  $close_note     Comment
3070
     *  @return int                     Return integer <0 if KO, >0 if OK
3071
     */
3072
    public function set_canceled($user, $close_code = '', $close_note = '')
3073
    {
3074
		// phpcs:enable
3075
        dol_syslog(get_class($this) . "::set_canceled is deprecated, use setCanceled instead", LOG_NOTICE);
3076
        return $this->setCanceled($user, $close_code, $close_note);
3077
    }
3078
3079
    /**
3080
     *  Tag invoice as canceled, with no payment on it (example for replacement invoice or payment never received) + call trigger BILL_CANCEL
3081
     *  Warning, if option to decrease stock on invoice was set, this function does not change stock (it might be a cancel because
3082
     *  of no payment even if merchandises were sent).
3083
     *
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 setCanceled($user, $close_code = '', $close_note = '')
3090
    {
3091
        dol_syslog(get_class($this) . "::setCanceled rowid=" . ((int) $this->id), LOG_DEBUG);
3092
3093
        $this->db->begin();
3094
        $now = dol_now();
3095
3096
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture SET';
3097
        $sql .= ' fk_statut=' . self::STATUS_ABANDONED;
3098
        if ($close_code) {
3099
            $sql .= ", close_code='" . $this->db->escape($close_code) . "'";
3100
        }
3101
        if ($close_note) {
3102
            $sql .= ", close_note='" . $this->db->escape($close_note) . "'";
3103
        }
3104
        $sql .= ', fk_user_closing = ' . ((int) $user->id);
3105
        $sql .= ", date_closing = '" . $this->db->idate($now) . "'";
3106
        $sql .= " WHERE rowid = " . ((int) $this->id);
3107
3108
        $resql = $this->db->query($sql);
3109
        if ($resql) {
3110
            // Bound discounts are deducted from the invoice
3111
            // as they have not been used since the invoice is abandoned.
3112
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'societe_remise_except';
3113
            $sql .= ' SET fk_facture = NULL';
3114
            $sql .= ' WHERE fk_facture = ' . ((int) $this->id);
3115
3116
            $resql = $this->db->query($sql);
3117
            if ($resql) {
3118
                // Call trigger
3119
                $result = $this->call_trigger('BILL_CANCEL', $user);
3120
                if ($result < 0) {
3121
                    $this->db->rollback();
3122
                    return -1;
3123
                }
3124
                // End call triggers
3125
3126
                $this->db->commit();
3127
                return 1;
3128
            } else {
3129
                $this->error = $this->db->error() . " sql=" . $sql;
3130
                $this->db->rollback();
3131
                return -1;
3132
            }
3133
        } else {
3134
            $this->error = $this->db->error() . " sql=" . $sql;
3135
            $this->db->rollback();
3136
            return -2;
3137
        }
3138
    }
3139
3140
    /**
3141
     * Tag invoice as validated + call trigger BILL_VALIDATE
3142
     * Object must have lines loaded with fetch_lines
3143
     *
3144
     * @param   User    $user           Object user that validate
3145
     * @param   string  $force_number   Reference to force on invoice
3146
     * @param   int     $idwarehouse    Id of warehouse to use for stock decrease if option to decrease on stock is on (0=no decrease)
3147
     * @param   int     $notrigger      1=Does not execute triggers, 0= execute triggers
3148
     * @param   int     $batch_rule     0=do not decrement batch, else batch rule to use, 1=take in batches ordered by sellby and eatby dates
3149
     * @return  int                     Return integer <0 if KO, 0=Nothing done because invoice is not a draft, >0 if OK
3150
     */
3151
    public function validate($user, $force_number = '', $idwarehouse = 0, $notrigger = 0, $batch_rule = 0)
3152
    {
3153
        global $conf, $langs, $mysoc;
3154
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
3155
3156
        $productStatic = null;
3157
        $warehouseStatic = null;
3158
        if ($batch_rule > 0) {
3159
            $productStatic = new Product($this->db);
3160
            $warehouseStatic = new Entrepot($this->db);
3161
            $productbatch = new Productbatch($this->db);
3162
        }
3163
3164
        $now = dol_now();
3165
3166
        $error = 0;
3167
        dol_syslog(get_class($this) . '::validate user=' . $user->id . ', force_number=' . $force_number . ', idwarehouse=' . $idwarehouse);
3168
3169
        // Force to have object complete for checks
3170
        $this->fetch_thirdparty();
3171
        $this->fetch_lines();
3172
3173
        // Check parameters
3174
        if ($this->status != self::STATUS_DRAFT) {
3175
            dol_syslog(get_class($this) . "::validate Current status is not draft. operation canceled.", LOG_WARNING);
3176
            return 0;
3177
        }
3178
        if (count($this->lines) <= 0) {
3179
            $langs->load("errors");
3180
            $this->error = $langs->trans("ErrorObjectMustHaveLinesToBeValidated", $this->ref);
3181
            return -1;
3182
        }
3183
        if (
3184
            (!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && !$user->hasRight('facture', 'creer'))
3185
            || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && !$user->hasRight('facture', 'invoice_advance', 'validate'))
3186
        ) {
3187
            $this->error = 'Permission denied';
3188
            dol_syslog(get_class($this) . "::validate " . $this->error . ' MAIN_USE_ADVANCED_PERMS=' . getDolGlobalString('MAIN_USE_ADVANCED_PERMS'), LOG_ERR);
3189
            return -1;
3190
        }
3191
        if (
3192
            (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref)) &&   // empty should not happened, but when it occurs, the test save life
3193
            getDolGlobalString('FAC_FORCE_DATE_VALIDATION')                     // If option enabled, we force invoice date
3194
        ) {
3195
            $this->date = dol_now();
3196
            $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...
3197
        }
3198
        if (getDolGlobalString('INVOICE_CHECK_POSTERIOR_DATE')) {
3199
            $last_of_type = $this->willBeLastOfSameType(true);
3200
            if (!$last_of_type[0]) {
3201
                $this->error = $langs->transnoentities("ErrorInvoiceIsNotLastOfSameType", $this->ref, dol_print_date($this->date, 'day'), dol_print_date($last_of_type[1], 'day'));
3202
                return -1;
3203
            }
3204
        }
3205
3206
        // Check for mandatory fields in thirdparty (defined into setup)
3207
        if (!empty($this->thirdparty) && is_object($this->thirdparty)) {
3208
            $array_to_check = array('IDPROF1', 'IDPROF2', 'IDPROF3', 'IDPROF4', 'IDPROF5', 'IDPROF6', 'EMAIL', 'ACCOUNTANCY_CODE_CUSTOMER');
3209
            foreach ($array_to_check as $key) {
3210
                $keymin = strtolower($key);
3211
                if (!property_exists($this->thirdparty, $keymin)) {
3212
                    continue;
3213
                }
3214
                $vallabel = $this->thirdparty->$keymin;
3215
3216
                $i = (int) preg_replace('/[^0-9]/', '', $key);
3217
                if ($i > 0) {
3218
                    if ($this->thirdparty->isACompany()) {
3219
                        // Check for mandatory prof id (but only if country is other than ours)
3220
                        if ($mysoc->country_id > 0 && $this->thirdparty->country_id == $mysoc->country_id) {
3221
                            $idprof_mandatory = 'SOCIETE_' . $key . '_INVOICE_MANDATORY';
3222
                            if (!$vallabel && !empty($conf->global->$idprof_mandatory)) {
3223
                                $langs->load("errors");
3224
                                $this->error = $langs->trans('ErrorProdIdIsMandatory', $langs->transcountry('ProfId' . $i, $this->thirdparty->country_code)) . ' (' . $langs->trans("ForbiddenBySetupRules") . ') [' . $langs->trans('Company') . ' : ' . $this->thirdparty->name . ']';
3225
                                dol_syslog(__METHOD__ . ' ' . $this->error, LOG_ERR);
3226
                                return -1;
3227
                            }
3228
                        }
3229
                    }
3230
                } else {
3231
                    if ($key == 'EMAIL') {
3232
                        // Check for mandatory
3233
                        if (getDolGlobalString('SOCIETE_EMAIL_INVOICE_MANDATORY') && !isValidEmail($this->thirdparty->email)) {
3234
                            $langs->load("errors");
3235
                            $this->error = $langs->trans("ErrorBadEMail", $this->thirdparty->email) . ' (' . $langs->trans("ForbiddenBySetupRules") . ') [' . $langs->trans('Company') . ' : ' . $this->thirdparty->name . ']';
3236
                            dol_syslog(__METHOD__ . ' ' . $this->error, LOG_ERR);
3237
                            return -1;
3238
                        }
3239
                    }
3240
                    if ($key == 'ACCOUNTANCY_CODE_CUSTOMER') {
3241
                        // Check for mandatory
3242
                        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

3242
                        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...
3243
                            $langs->load("errors");
3244
                            $this->error = $langs->trans("ErrorAccountancyCodeCustomerIsMandatory", $this->thirdparty->name) . ' (' . $langs->trans("ForbiddenBySetupRules") . ')';
3245
                            dol_syslog(__METHOD__ . ' ' . $this->error, LOG_ERR);
3246
                            return -1;
3247
                        }
3248
                    }
3249
                }
3250
            }
3251
        }
3252
3253
        // Check for mandatory fields in $this
3254
        $array_to_check = array('REF_CLIENT' => 'RefCustomer');
3255
        foreach ($array_to_check as $key => $val) {
3256
            $keymin = strtolower($key);
3257
            $vallabel = $this->$keymin;
3258
3259
            // Check for mandatory
3260
            $keymandatory = 'INVOICE_' . $key . '_MANDATORY_FOR_VALIDATION';
3261
            if (!$vallabel && getDolGlobalString($keymandatory)) {
3262
                $langs->load("errors");
3263
                $error++;
3264
                setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv($val)), null, 'errors');
3265
            }
3266
        }
3267
3268
        $this->db->begin();
3269
3270
        // Check parameters
3271
        if ($this->type == self::TYPE_REPLACEMENT) {        // if this is a replacement invoice
3272
            // Check that source invoice is known
3273
            if ($this->fk_facture_source <= 0) {
3274
                $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("InvoiceReplacement"));
3275
                $this->db->rollback();
3276
                return -10;
3277
            }
3278
3279
            // Load source invoice that has been replaced
3280
            $facreplaced = new Facture($this->db);
3281
            $result = $facreplaced->fetch($this->fk_facture_source);
3282
            if ($result <= 0) {
3283
                $this->error = $langs->trans("ErrorBadInvoice");
3284
                $this->db->rollback();
3285
                return -11;
3286
            }
3287
3288
            // Check that source invoice not already replaced by another one.
3289
            $idreplacement = $facreplaced->getIdReplacingInvoice('validated');
3290
            if ($idreplacement && $idreplacement != $this->id) {
3291
                $facreplacement = new Facture($this->db);
3292
                $facreplacement->fetch($idreplacement);
3293
                $this->error = $langs->trans("ErrorInvoiceAlreadyReplaced", $facreplaced->ref, $facreplacement->ref);
3294
                $this->db->rollback();
3295
                return -12;
3296
            }
3297
3298
            $result = $facreplaced->setCanceled($user, self::CLOSECODE_REPLACED, '');
3299
            if ($result < 0) {
3300
                $this->error = $facreplaced->error;
3301
                $this->db->rollback();
3302
                return -13;
3303
            }
3304
        }
3305
3306
        // Define new ref
3307
        if ($force_number) {
3308
            $num = $force_number;
3309
        } elseif (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
3310
            if (getDolGlobalString('FAC_FORCE_DATE_VALIDATION')) {  // If option enabled, we force invoice date
3311
                $this->date = dol_now();
3312
                $this->date_lim_reglement = $this->calculate_date_lim_reglement();
3313
            }
3314
            $num = $this->getNextNumRef($this->thirdparty);
3315
        } else {
3316
            $num = $this->ref;
3317
        }
3318
3319
        $this->newref = dol_sanitizeFileName($num);
3320
3321
        if ($num) {
3322
            $this->update_price(1);
3323
3324
            // Validate
3325
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture';
3326
            $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) . "'";
3327
            if (getDolGlobalString('FAC_FORCE_DATE_VALIDATION')) {  // If option enabled, we force invoice date
3328
                $sql .= ", datef='" . $this->db->idate($this->date) . "'";
3329
                $sql .= ", date_lim_reglement='" . $this->db->idate($this->date_lim_reglement) . "'";
3330
            }
3331
            $sql .= " WHERE rowid = " . ((int) $this->id);
3332
3333
            dol_syslog(get_class($this) . "::validate", LOG_DEBUG);
3334
            $resql = $this->db->query($sql);
3335
            if (!$resql) {
3336
                dol_print_error($this->db);
3337
                $error++;
3338
            }
3339
3340
            // We check if the invoice was provisional
3341
            if (!$error && (preg_match('/^[\(]?PROV/i', $this->ref))) {
3342
                // La verif qu'une remise n'est pas utilisee 2 fois est faite au moment de l'insertion de ligne
3343
            }
3344
3345
            if (!$error) {
3346
                // Define third party as a customer
3347
                $result = $this->thirdparty->setAsCustomer();
3348
3349
                // If active we decrement the main product and its components at invoice validation
3350
                if ($this->type != self::TYPE_DEPOSIT && $result >= 0 && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_BILL') && $idwarehouse > 0) {
3351
                    $langs->load("agenda");
3352
3353
                    // Loop on each line
3354
                    $cpt = count($this->lines);
3355
                    for ($i = 0; $i < $cpt; $i++) {
3356
                        if ($this->lines[$i]->fk_product > 0) {
3357
                            $mouvP = new MouvementStock($this->db);
3358
                            $mouvP->origin = &$this;
3359
                            $mouvP->setOrigin($this->element, $this->id);
3360
3361
                            // TODO If warehouseid has been set into invoice line, we should use this value in priority
3362
                            // $idwarehouse = $this->lines[$i]->fk_warehouse;
3363
3364
                            // We decrease stock for product
3365
                            if ($this->type == self::TYPE_CREDIT_NOTE) {
3366
                                $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, 0, $langs->trans("InvoiceValidatedInDolibarr", $num));
3367
                                if ($result < 0) {
3368
                                    $error++;
3369
                                    $this->error = $mouvP->error;
3370
                                }
3371
                            } else {
3372
                                $is_batch_line = false;
3373
                                if ($batch_rule > 0) {
3374
                                    $productStatic->fetch($this->lines[$i]->fk_product);
3375
                                    if ($productStatic->hasbatch()) {
3376
                                        $is_batch_line = true;
3377
                                        $product_qty_remain = $this->lines[$i]->qty;
3378
3379
                                        $sortfield = null;
3380
                                        $sortorder = null;
3381
                                        // find all batch order by sellby (DLC) and eatby dates (DLUO) first
3382
                                        if ($batch_rule == Productbatch::BATCH_RULE_SELLBY_EATBY_DATES_FIRST) {
3383
                                            $sortfield = 'pl.sellby,pl.eatby,pb.qty,pl.rowid';
3384
                                            $sortorder = 'ASC,ASC,ASC,ASC';
3385
                                        }
3386
3387
                                        $resBatchList = $productbatch->findAllForProduct($productStatic->id, $idwarehouse, (getDolGlobalInt('STOCK_ALLOW_NEGATIVE_TRANSFER') ? null : 0), $sortfield, $sortorder);
3388
                                        if (!is_array($resBatchList)) {
3389
                                            $error++;
3390
                                            $this->error = $this->db->lasterror();
3391
                                        }
3392
3393
                                        if (!$error) {
3394
                                            $batchList = $resBatchList;
3395
                                            if (empty($batchList)) {
3396
                                                $error++;
3397
                                                $langs->load('errors');
3398
                                                $warehouseStatic->fetch($idwarehouse);
3399
                                                $this->error = $langs->trans('ErrorBatchNoFoundForProductInWarehouse', $productStatic->label, $warehouseStatic->ref);
3400
                                                dol_syslog(__METHOD__ . ' Error: ' . $langs->transnoentitiesnoconv('ErrorBatchNoFoundForProductInWarehouse', $productStatic->label, $warehouseStatic->ref), LOG_ERR);
3401
                                            }
3402
3403
                                            foreach ($batchList as $batch) {
3404
                                                if ($batch->qty <= 0) {
3405
                                                    continue; // try to decrement only batches have positive quantity first
3406
                                                }
3407
3408
                                                // enough quantity in this batch
3409
                                                if ($batch->qty >= $product_qty_remain) {
3410
                                                    $product_batch_qty = $product_qty_remain;
3411
                                                } else {
3412
                                                    // not enough (take all in batch)
3413
                                                    $product_batch_qty = $batch->qty;
3414
                                                }
3415
                                                $result = $mouvP->livraison($user, $productStatic->id, $idwarehouse, $product_batch_qty, $this->lines[$i]->subprice, $langs->trans('InvoiceValidatedInDolibarr', $num), '', '', '', $batch->batch);
3416
                                                if ($result < 0) {
3417
                                                    $error++;
3418
                                                    $this->error = $mouvP->error;
3419
                                                    $this->errors = $mouvP->errors;
3420
                                                    break;
3421
                                                }
3422
3423
                                                $product_qty_remain -= $product_batch_qty;
3424
                                                // all product quantity was decremented
3425
                                                if ($product_qty_remain <= 0) {
3426
                                                    break;
3427
                                                }
3428
                                            }
3429
3430
                                            if (!$error && $product_qty_remain > 0) {
3431
                                                if (getDolGlobalInt('STOCK_ALLOW_NEGATIVE_TRANSFER')) {
3432
                                                    // take in the first batch
3433
                                                    $batch = $batchList[0];
3434
                                                    $result = $mouvP->livraison($user, $productStatic->id, $idwarehouse, $product_qty_remain, $this->lines[$i]->subprice, $langs->trans('InvoiceValidatedInDolibarr', $num), '', '', '', $batch->batch);
3435
                                                    if ($result < 0) {
3436
                                                        $error++;
3437
                                                        $this->error = $mouvP->error;
3438
                                                        $this->errors = $mouvP->errors;
3439
                                                    }
3440
                                                } else {
3441
                                                    $error++;
3442
                                                    $langs->load('errors');
3443
                                                    $warehouseStatic->fetch($idwarehouse);
3444
                                                    $this->error = $langs->trans('ErrorBatchNoFoundEnoughQuantityForProductInWarehouse', $productStatic->label, $warehouseStatic->ref);
3445
                                                    dol_syslog(__METHOD__ . ' Error: ' . $langs->transnoentitiesnoconv('ErrorBatchNoFoundEnoughQuantityForProductInWarehouse', $productStatic->label, $warehouseStatic->ref), LOG_ERR);
3446
                                                }
3447
                                            }
3448
                                        }
3449
                                    }
3450
                                }
3451
3452
                                if (!$is_batch_line) {
3453
                                    $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceValidatedInDolibarr", $num));
3454
                                    if ($result < 0) {
3455
                                        $error++;
3456
                                        $this->error = $mouvP->error;
3457
                                        $this->errors = $mouvP->errors;
3458
                                    }
3459
                                }
3460
                            }
3461
                        }
3462
                    }
3463
                }
3464
            }
3465
3466
            /*
3467
             * 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%)
3468
             * So we can continue to create new invoice situation
3469
             */
3470
            if (!$error && $this->type == self::TYPE_CREDIT_NOTE && $this->fk_facture_source > 0) {
3471
                $invoice_situation = new Facture($this->db);
3472
                $result = $invoice_situation->fetch($this->fk_facture_source);
3473
                if ($result > 0 && $invoice_situation->type == self::TYPE_SITUATION && $invoice_situation->situation_final == 1) {
3474
                    $invoice_situation->situation_final = 0;
3475
                    // Disable triggers because module can force situation_final to 1 by triggers (ex: SubTotal)
3476
                    $result = $invoice_situation->setFinal($user, 1);
3477
                }
3478
                if ($result < 0) {
3479
                    $this->error = $invoice_situation->error;
3480
                    $this->errors = $invoice_situation->errors;
3481
                    $error++;
3482
                }
3483
            }
3484
3485
            // Trigger calls
3486
            if (!$error && !$notrigger) {
3487
                // Call trigger
3488
                $result = $this->call_trigger('BILL_VALIDATE', $user);
3489
                if ($result < 0) {
3490
                    $error++;
3491
                }
3492
                // End call triggers
3493
            }
3494
3495
            if (!$error) {
3496
                $this->oldref = $this->ref;
3497
3498
                // Rename directory if dir was a temporary ref
3499
                if (preg_match('/^[\(]?PROV/i', $this->ref)) {
3500
                    // Now we rename also files into index
3501
                    $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) . "'";
3502
                    $sql .= " WHERE filename LIKE '" . $this->db->escape($this->ref) . "%' AND filepath = 'facture/" . $this->db->escape($this->ref) . "' and entity = " . $conf->entity;
3503
                    $resql = $this->db->query($sql);
3504
                    if (!$resql) {
3505
                        $error++;
3506
                        $this->error = $this->db->lasterror();
3507
                    }
3508
                    $sql = 'UPDATE ' . MAIN_DB_PREFIX . "ecm_files set filepath = 'facture/" . $this->db->escape($this->newref) . "'";
3509
                    $sql .= " WHERE filepath = 'facture/" . $this->db->escape($this->ref) . "' and entity = " . $conf->entity;
3510
                    $resql = $this->db->query($sql);
3511
                    if (!$resql) {
3512
                        $error++;
3513
                        $this->error = $this->db->lasterror();
3514
                    }
3515
3516
                    // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
3517
                    $oldref = dol_sanitizeFileName($this->ref);
3518
                    $newref = dol_sanitizeFileName($num);
3519
                    $dirsource = $conf->facture->dir_output . '/' . $oldref;
3520
                    $dirdest = $conf->facture->dir_output . '/' . $newref;
3521
                    if (!$error && file_exists($dirsource)) {
3522
                        dol_syslog(get_class($this) . "::validate rename dir " . $dirsource . " into " . $dirdest);
3523
3524
                        if (@rename($dirsource, $dirdest)) {
3525
                            dol_syslog("Rename ok");
3526
                            // Rename docs starting with $oldref with $newref
3527
                            $listoffiles = dol_dir_list($conf->facture->dir_output . '/' . $newref, 'files', 1, '^' . preg_quote($oldref, '/'));
3528
                            foreach ($listoffiles as $fileentry) {
3529
                                $dirsource = $fileentry['name'];
3530
                                $dirdest = preg_replace('/^' . preg_quote($oldref, '/') . '/', $newref, $dirsource);
3531
                                $dirsource = $fileentry['path'] . '/' . $dirsource;
3532
                                $dirdest = $fileentry['path'] . '/' . $dirdest;
3533
                                @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

3533
                                /** @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...
3534
                            }
3535
                        }
3536
                    }
3537
                }
3538
            }
3539
3540
            if (!$error && !$this->is_last_in_cycle()) {
3541
                if (!$this->updatePriceNextInvoice($langs)) {
3542
                    $error++;
3543
                }
3544
            }
3545
3546
            // Set new ref and define current status
3547
            if (!$error) {
3548
                $this->ref = $num;
3549
                $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

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

3700
                /** @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...
3701
                $this->status = self::STATUS_DRAFT;
3702
3703
                // Call trigger
3704
                $result = $this->call_trigger('BILL_UNVALIDATE', $user);
3705
                if ($result < 0) {
3706
                    $error++;
3707
                    $this->statut = $old_statut; // deprecated
3708
                    $this->status = $old_statut;
3709
                }
3710
                // End call triggers
3711
            } else {
3712
                $this->db->rollback();
3713
                return -1;
3714
            }
3715
3716
            if ($error == 0) {
3717
                $this->db->commit();
3718
                return 1;
3719
            } else {
3720
                $this->db->rollback();
3721
                return -1;
3722
            }
3723
        } else {
3724
            $this->error = $this->db->error();
3725
            $this->db->rollback();
3726
            return -1;
3727
        }
3728
    }
3729
3730
3731
    /**
3732
     *  Add an invoice line into database (linked to product/service or not).
3733
     *  Note: ->thirdparty must be defined.
3734
     *  Les parameters sont deja cense etre juste et avec valeurs finales a l'appel
3735
     *  de cette method. Aussi, pour le taux tva, il doit deja avoir ete defini
3736
     *  par l'appelant par la method get_default_tva(societe_vendeuse,societe_acheteuse,produit)
3737
     *  et le desc doit deja avoir la bonne valeur (a l'appelant de gerer le multilangue)
3738
     *
3739
     *  @param      string      $desc               Description of line
3740
     *  @param      double      $pu_ht              Unit price without tax (> 0 even for credit note)
3741
     *  @param      double      $qty                Quantity
3742
     *  @param      double      $txtva              Force Vat rate, -1 for auto (Can contain the vat_src_code too with syntax '9.9 (CODE)')
3743
     *  @param      double      $txlocaltax1        Local tax 1 rate (deprecated, use instead txtva with code inside)
3744
     *  @param      double      $txlocaltax2        Local tax 2 rate (deprecated, use instead txtva with code inside)
3745
     *  @param      int         $fk_product         Id of predefined product/service
3746
     *  @param      double      $remise_percent     Percent of discount on line
3747
     *  @param      int|string  $date_start         Date start of service
3748
     *  @param      int|string  $date_end           Date end of service
3749
     *  @param      int         $fk_code_ventilation    Code of dispatching into accountancy
3750
     *  @param      int         $info_bits          Bits of type of lines
3751
     *  @param      int         $fk_remise_except   Id discount used
3752
     *  @param      string      $price_base_type    'HT' or 'TTC'
3753
     *  @param      double      $pu_ttc             Unit price with tax (> 0 even for credit note)
3754
     *  @param      int         $type               Type of line (0=product, 1=service). Not used if fk_product is defined, the type of product is used.
3755
     *  @param      int         $rang               Position of line (-1 means last value + 1)
3756
     *  @param      int         $special_code       Special code (also used by externals modules!)
3757
     *  @param      string      $origin             Depend on global conf MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION can be 'orderdet', 'propaldet'..., else 'order','propal,'....
3758
     *  @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
3759
     *  @param      int         $fk_parent_line     Id of parent line
3760
     *  @param      int         $fk_fournprice      Supplier price id (to calculate margin) or ''
3761
     *  @param      int         $pa_ht              Buying price of line (to calculate margin) or ''
3762
     *  @param      string      $label              Label of the line (deprecated, do not use)
3763
     *  @param      array       $array_options      extrafields array
3764
     *  @param      int         $situation_percent  Situation advance percentage
3765
     *  @param      int         $fk_prev_id         Previous situation line id reference
3766
     *  @param      int|null    $fk_unit            Code of the unit to use. Null to use the default one
3767
     *  @param      double      $pu_ht_devise       Unit price in foreign currency
3768
     *  @param      string      $ref_ext            External reference of the line
3769
     *  @param      int         $noupdateafterinsertline    No update after insert of line
3770
     *  @return     int                             Return integer <0 if KO, Id of line if OK
3771
     */
3772
    public function addline(
3773
        $desc,
3774
        $pu_ht,
3775
        $qty,
3776
        $txtva,
3777
        $txlocaltax1 = 0,
3778
        $txlocaltax2 = 0,
3779
        $fk_product = 0,
3780
        $remise_percent = 0,
3781
        $date_start = '',
3782
        $date_end = '',
3783
        $fk_code_ventilation = 0,
3784
        $info_bits = 0,
3785
        $fk_remise_except = 0,
3786
        $price_base_type = 'HT',
3787
        $pu_ttc = 0,
3788
        $type = 0,
3789
        $rang = -1,
3790
        $special_code = 0,
3791
        $origin = '',
3792
        $origin_id = 0,
3793
        $fk_parent_line = 0,
3794
        $fk_fournprice = null,
3795
        $pa_ht = 0,
3796
        $label = '',
3797
        $array_options = array(),
3798
        $situation_percent = 100,
3799
        $fk_prev_id = 0,
3800
        $fk_unit = null,
3801
        $pu_ht_devise = 0,
3802
        $ref_ext = '',
3803
        $noupdateafterinsertline = 0
3804
    ) {
3805
        // Deprecation warning
3806
        if ($label) {
3807
            dol_syslog(__METHOD__ . ": using line label is deprecated", LOG_WARNING);
3808
            //var_dump(debug_backtrace(false));exit;
3809
        }
3810
3811
        global $mysoc, $conf, $langs;
3812
3813
        dol_syslog(get_class($this) . "::addline id=$this->id, pu_ht=$pu_ht, qty=$qty, txtva=$txtva, txlocaltax1=$txlocaltax1, txlocaltax2=$txlocaltax2, fk_product=$fk_product, remise_percent=$remise_percent, date_start=$date_start, date_end=$date_end, 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);
3814
3815
        if ($this->status == self::STATUS_DRAFT) {
3816
            include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
3817
3818
            // Clean parameters
3819
            if (empty($remise_percent)) {
3820
                $remise_percent = 0;
3821
            }
3822
            if (empty($qty)) {
3823
                $qty = 0;
3824
            }
3825
            if (empty($info_bits)) {
3826
                $info_bits = 0;
3827
            }
3828
            if (empty($rang)) {
3829
                $rang = 0;
3830
            }
3831
            if (empty($fk_code_ventilation)) {
3832
                $fk_code_ventilation = 0;
3833
            }
3834
            if (empty($txtva)) {
3835
                $txtva = 0;
3836
            }
3837
            if (empty($txlocaltax1)) {
3838
                $txlocaltax1 = 0;
3839
            }
3840
            if (empty($txlocaltax2)) {
3841
                $txlocaltax2 = 0;
3842
            }
3843
            if (empty($fk_parent_line) || $fk_parent_line < 0) {
3844
                $fk_parent_line = 0;
3845
            }
3846
            if (empty($fk_prev_id)) {
3847
                $fk_prev_id = 'null';
3848
            }
3849
            if (!isset($situation_percent) || $situation_percent > 100 || (string) $situation_percent == '') {
3850
                $situation_percent = 100;
3851
            }
3852
            if (empty($ref_ext)) {
3853
                $ref_ext = '';
3854
            }
3855
3856
            $remise_percent = (float) price2num($remise_percent);
3857
            $qty = price2num($qty);
3858
            $pu_ht = price2num($pu_ht);
3859
            $pu_ht_devise = price2num($pu_ht_devise);
3860
            $pu_ttc = price2num($pu_ttc);
3861
            $pa_ht = price2num($pa_ht);
3862
            if (!preg_match('/\((.*)\)/', (string) $txtva)) {
3863
                $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
3864
            }
3865
            $txlocaltax1 = price2num($txlocaltax1);
3866
            $txlocaltax2 = price2num($txlocaltax2);
3867
3868
            if ($price_base_type == 'HT') {
3869
                $pu = $pu_ht;
3870
            } else {
3871
                $pu = $pu_ttc;
3872
            }
3873
3874
            // Check parameters
3875
            if ($type < 0) {
3876
                return -1;
3877
            }
3878
3879
            if ($date_start && $date_end && $date_start > $date_end) {
3880
                $langs->load("errors");
3881
                $this->error = $langs->trans('ErrorStartDateGreaterEnd');
3882
                return -1;
3883
            }
3884
3885
            $this->db->begin();
3886
3887
            $product_type = $type;
3888
            if (!empty($fk_product) && $fk_product > 0) {
3889
                $product = new Product($this->db);
3890
                $result = $product->fetch($fk_product);
3891
                $product_type = $product->type;
3892
3893
                if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_INVOICE') && $product_type == 0 && $product->stock_reel < $qty) {
3894
                    $langs->load("errors");
3895
                    $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnInvoice', $product->ref);
3896
                    $this->db->rollback();
3897
                    return -3;
3898
                }
3899
            }
3900
3901
            $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
3902
3903
            // Clean vat code
3904
            $reg = array();
3905
            $vat_src_code = '';
3906
            if (preg_match('/\((.*)\)/', $txtva, $reg)) {
3907
                $vat_src_code = $reg[1];
3908
                $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
3909
            }
3910
3911
            // Calcul du total TTC et de la TVA pour la ligne a partir de
3912
            // qty, pu, remise_percent et txtva
3913
            // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
3914
            // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
3915
3916
            $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);
3917
3918
            $total_ht  = $tabprice[0];
3919
            $total_tva = $tabprice[1];
3920
            $total_ttc = $tabprice[2];
3921
            $total_localtax1 = $tabprice[9];
3922
            $total_localtax2 = $tabprice[10];
3923
            $pu_ht = $tabprice[3];
3924
3925
            // MultiCurrency
3926
            $multicurrency_total_ht = $tabprice[16];
3927
            $multicurrency_total_tva = $tabprice[17];
3928
            $multicurrency_total_ttc = $tabprice[18];
3929
            $pu_ht_devise = $tabprice[19];
3930
3931
            // Rank to use
3932
            $ranktouse = $rang;
3933
            if ($ranktouse == -1) {
3934
                $rangmax = $this->line_max($fk_parent_line);
3935
                $ranktouse = $rangmax + 1;
3936
            }
3937
3938
            // Insert line
3939
            $this->line = new FactureLigne($this->db);
3940
3941
            $this->line->context = $this->context;
3942
3943
            $this->line->fk_facture = $this->id;
3944
            $this->line->label = $label; // deprecated
3945
            $this->line->desc = $desc;
3946
            $this->line->ref_ext = $ref_ext;
3947
3948
            $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...
3949
            $this->line->subprice = ($this->type == self::TYPE_CREDIT_NOTE ? -abs($pu_ht) : $pu_ht); // For credit note, unit price always negative, always positive otherwise
3950
3951
            $this->line->vat_src_code = $vat_src_code;
3952
            $this->line->tva_tx = $txtva;
3953
            $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
3954
            $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
3955
            $this->line->localtax1_type = empty($localtaxes_type[0]) ? 0 : $localtaxes_type[0];
3956
            $this->line->localtax2_type = empty($localtaxes_type[2]) ? 0 : $localtaxes_type[2];
3957
3958
            $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
3959
            $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
3960
            $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
3961
            $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
3962
            $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
3963
3964
            $this->line->fk_product = $fk_product;
3965
            $this->line->product_type = $product_type;
3966
            $this->line->remise_percent = $remise_percent;
3967
            $this->line->date_start = $date_start;
3968
            $this->line->date_end = $date_end;
3969
            $this->line->fk_code_ventilation = $fk_code_ventilation;
3970
            $this->line->rang = $ranktouse;
3971
            $this->line->info_bits = $info_bits;
3972
            $this->line->fk_remise_except = $fk_remise_except;
3973
3974
            $this->line->special_code = $special_code;
3975
            $this->line->fk_parent_line = $fk_parent_line;
3976
            $this->line->origin = $origin;
3977
            $this->line->origin_id = $origin_id;
3978
            $this->line->situation_percent = $situation_percent;
3979
            $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...
3980
            $this->line->fk_unit = $fk_unit;
3981
3982
            // infos margin
3983
            $this->line->fk_fournprice = $fk_fournprice;
3984
            $this->line->pa_ht = $pa_ht;
3985
3986
            // Multicurrency
3987
            $this->line->fk_multicurrency = $this->fk_multicurrency;
3988
            $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...
3989
            $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
3990
3991
            $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
3992
            $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
3993
            $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
3994
3995
            if (is_array($array_options) && count($array_options) > 0) {
3996
                $this->line->array_options = $array_options;
3997
            }
3998
3999
            $result = $this->line->insert();
4000
            if ($result > 0) {
4001
                // Reorder if child line
4002
                if (!empty($fk_parent_line)) {
4003
                    $this->line_order(true, 'DESC');
4004
                } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) { // Update all rank of all other lines
4005
                    $linecount = count($this->lines);
4006
                    for ($ii = $ranktouse; $ii <= $linecount; $ii++) {
4007
                        $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
4008
                    }
4009
                }
4010
4011
                // Mise a jour information denormalisees au niveau de la facture meme
4012
                if (empty($noupdateafterinsertline)) {
4013
                    $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.
4014
                }
4015
4016
                if ($result > 0) {
4017
                    $this->db->commit();
4018
                    return $this->line->id;
4019
                } else {
4020
                    $this->error = $this->db->lasterror();
4021
                    $this->db->rollback();
4022
                    return -1;
4023
                }
4024
            } else {
4025
                $this->error = $this->line->error;
4026
                $this->errors = $this->line->errors;
4027
                $this->db->rollback();
4028
                return -2;
4029
            }
4030
        } else {
4031
            $this->errors[] = 'status of invoice must be Draft to allow use of ->addline()';
4032
            dol_syslog(get_class($this) . "::addline status of invoice must be Draft to allow use of ->addline()", LOG_ERR);
4033
            return -3;
4034
        }
4035
    }
4036
4037
    /**
4038
     *  Update a detail line
4039
     *
4040
     *  @param      int         $rowid              Id of line to update
4041
     *  @param      string      $desc               Description of line
4042
     *  @param      double      $pu                 Prix unitaire (HT ou TTC selon price_base_type) (> 0 even for credit note lines)
4043
     *  @param      double      $qty                Quantity
4044
     *  @param      double      $remise_percent     Percentage discount of the line
4045
     *  @param      int         $date_start         Date de debut de validite du service
4046
     *  @param      int         $date_end           Date de fin de validite du service
4047
     *  @param      double      $txtva              VAT Rate (Can be '8.5', '8.5 (ABC)')
4048
     *  @param      double      $txlocaltax1        Local tax 1 rate
4049
     *  @param      double      $txlocaltax2        Local tax 2 rate
4050
     *  @param      string      $price_base_type    HT or TTC
4051
     *  @param      int         $info_bits          Miscellaneous information
4052
     *  @param      int         $type               Type of line (0=product, 1=service)
4053
     *  @param      int         $fk_parent_line     Id of parent line (0 in most cases, used by modules adding sublevels into lines).
4054
     *  @param      int         $skip_update_total  Keep fields total_xxx to 0 (used for special lines by some modules)
4055
     *  @param      int         $fk_fournprice      Id of origin supplier price
4056
     *  @param      int         $pa_ht              Price (without tax) of product when it was bought
4057
     *  @param      string      $label              Label of the line (deprecated, do not use)
4058
     *  @param      int         $special_code       Special code (also used by externals modules!)
4059
     *  @param      array       $array_options      extrafields array
4060
     *  @param      int         $situation_percent  Situation advance percentage
4061
     *  @param      int|null    $fk_unit            Code of the unit to use. Null to use the default one
4062
     *  @param      double      $pu_ht_devise       Unit price in currency
4063
     *  @param      int         $notrigger          disable line update trigger
4064
     *  @param      string      $ref_ext            External reference of the line
4065
     *  @param      integer     $rang               rank of line
4066
     *  @return     int                             Return integer < 0 if KO, > 0 if OK
4067
     */
4068
    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)
4069
    {
4070
        global $conf, $user;
4071
        // Deprecation warning
4072
        if ($label) {
4073
            dol_syslog(__METHOD__ . ": using line label is deprecated", LOG_WARNING);
4074
        }
4075
4076
        include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
4077
4078
        global $mysoc, $langs;
4079
4080
        dol_syslog(get_class($this) . "::updateline rowid=$rowid, desc=$desc, pu=$pu, qty=$qty, remise_percent=$remise_percent, date_start=$date_start, date_end=$date_end, txtva=$txtva, txlocaltax1=$txlocaltax1, txlocaltax2=$txlocaltax2, price_base_type=$price_base_type, info_bits=$info_bits, type=$type, fk_parent_line=$fk_parent_line pa_ht=$pa_ht, special_code=$special_code, fk_unit=$fk_unit, pu_ht_devise=$pu_ht_devise", LOG_DEBUG);
4081
4082
        if ($this->status == self::STATUS_DRAFT) {
4083
            if (!$this->is_last_in_cycle() && empty($this->error)) {
4084
                if (!$this->checkProgressLine($rowid, $situation_percent)) {
4085
                    if (!$this->error) {
4086
                        $this->error = $langs->trans('invoiceLineProgressError');
4087
                    }
4088
                    return -3;
4089
                }
4090
            }
4091
4092
            if ($date_start && $date_end && $date_start > $date_end) {
4093
                $langs->load("errors");
4094
                $this->error = $langs->trans('ErrorStartDateGreaterEnd');
4095
                return -1;
4096
            }
4097
4098
            $this->db->begin();
4099
4100
            // Clean parameters
4101
            if (empty($qty)) {
4102
                $qty = 0;
4103
            }
4104
            if (empty($fk_parent_line) || $fk_parent_line < 0) {
4105
                $fk_parent_line = 0;
4106
            }
4107
            if (empty($special_code) || $special_code == 3) {
4108
                $special_code = 0;
4109
            }
4110
            if (!isset($situation_percent) || $situation_percent > 100 || (string) $situation_percent == '') {
4111
                $situation_percent = 100;
4112
            }
4113
            if (empty($ref_ext)) {
4114
                $ref_ext = '';
4115
            }
4116
4117
            $remise_percent = (float) price2num($remise_percent);
4118
            $qty            = price2num($qty);
4119
            $pu             = price2num($pu);
4120
            $pu_ht_devise = price2num($pu_ht_devise);
4121
            $pa_ht = price2num($pa_ht);
4122
            if (!preg_match('/\((.*)\)/', (string) $txtva)) {
4123
                $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
4124
            }
4125
            $txlocaltax1    = (float) price2num($txlocaltax1);
4126
            $txlocaltax2    = (float) price2num($txlocaltax2);
4127
4128
            // Check parameters
4129
            if ($type < 0) {
4130
                return -1;
4131
            }
4132
4133
            // Calculate total with, without tax and tax from qty, pu, remise_percent and txtva
4134
            // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
4135
            // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
4136
4137
            $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
4138
4139
            // Clean vat code
4140
            $reg = array();
4141
            $vat_src_code = '';
4142
            if (preg_match('/\((.*)\)/', $txtva, $reg)) {
4143
                $vat_src_code = $reg[1];
4144
                $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
4145
            }
4146
4147
            $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);
4148
4149
            $total_ht  = $tabprice[0];
4150
            $total_tva = $tabprice[1];
4151
            $total_ttc = $tabprice[2];
4152
            $total_localtax1 = $tabprice[9];
4153
            $total_localtax2 = $tabprice[10];
4154
            $pu_ht  = $tabprice[3];
4155
            $pu_tva = $tabprice[4];
4156
            $pu_ttc = $tabprice[5];
4157
4158
            // MultiCurrency
4159
            $multicurrency_total_ht = $tabprice[16];
4160
            $multicurrency_total_tva = $tabprice[17];
4161
            $multicurrency_total_ttc = $tabprice[18];
4162
            $pu_ht_devise = $tabprice[19];
4163
4164
            // Old properties: $price, $remise (deprecated)
4165
            $price = $pu;
4166
            $remise = 0;
4167
            if ($remise_percent > 0) {
4168
                $remise = round(((float) $pu * (float) $remise_percent / 100), 2);
4169
                $price = ((float) $pu - $remise);
4170
            }
4171
            $price = price2num($price);
4172
4173
            //Fetch current line from the database and then clone the object and set it in $oldline property
4174
            $line = new FactureLigne($this->db);
4175
            $line->fetch($rowid);
4176
            $line->fetch_optionals();
4177
4178
            if (!empty($line->fk_product)) {
4179
                $product = new Product($this->db);
4180
                $result = $product->fetch($line->fk_product);
4181
                $product_type = $product->type;
4182
4183
                if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_INVOICE') && $product_type == 0 && $product->stock_reel < $qty) {
4184
                    $langs->load("errors");
4185
                    $this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnInvoice', $product->ref);
4186
                    $this->db->rollback();
4187
                    return -3;
4188
                }
4189
            }
4190
4191
            $staticline = clone $line;
4192
4193
            $line->oldline = $staticline;
4194
            $this->line = $line;
4195
            $this->line->context = $this->context;
4196
            $this->line->rang = $rang;
4197
4198
            // Reorder if fk_parent_line change
4199
            if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) {
4200
                $rangmax = $this->line_max($fk_parent_line);
4201
                $this->line->rang = $rangmax + 1;
4202
            }
4203
4204
            $this->line->id = $rowid;
4205
            $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

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

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

4958
                /** @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...
4959
                $generic_facture->status = $obj->status;
4960
4961
                $response->nbtodo++;
4962
                $response->total += $obj->total_ht;
4963
4964
                if ($generic_facture->hasDelay()) {
4965
                    $response->nbtodolate++;
4966
                    $response->url_late = constant('BASE_URL') . '/compta/facture/list.php?search_option=late&mainmenu=billing&leftmenu=customers_bills';
4967
                }
4968
            }
4969
4970
            $this->db->free($resql);
4971
            return $response;
4972
        } else {
4973
            dol_print_error($this->db);
4974
            $this->error = $this->db->error();
4975
            return -1;
4976
        }
4977
    }
4978
4979
4980
    /* gestion des contacts d'une facture */
4981
4982
    /**
4983
     *  Retourne id des contacts clients de facturation
4984
     *
4985
     *  @return     array       Liste des id contacts facturation
4986
     */
4987
    public function getIdBillingContact()
4988
    {
4989
        return $this->getIdContact('external', 'BILLING');
4990
    }
4991
4992
    /**
4993
     *  Retourne id des contacts clients de livraison
4994
     *
4995
     *  @return     array       Liste des id contacts livraison
4996
     */
4997
    public function getIdShippingContact()
4998
    {
4999
        return $this->getIdContact('external', 'SHIPPING');
5000
    }
5001
5002
5003
    /**
5004
     *  Initialise an instance with random values.
5005
     *  Used to build previews or test instances.
5006
     *  id must be 0 if object instance is a specimen.
5007
     *
5008
     *  @param  string      $option     ''=Create a specimen invoice with lines, 'nolines'=No lines
5009
     *  @return int
5010
     */
5011
    public function initAsSpecimen($option = '')
5012
    {
5013
        global $conf, $langs, $user;
5014
5015
        $now = dol_now();
5016
        $arraynow = dol_getdate($now);
5017
        $nownotime = dol_mktime(0, 0, 0, $arraynow['mon'], $arraynow['mday'], $arraynow['year']);
5018
5019
        // Load array of products prodids
5020
        $num_prods = 0;
5021
        $prodids = array();
5022
        $sql = "SELECT rowid";
5023
        $sql .= " FROM " . MAIN_DB_PREFIX . "product";
5024
        $sql .= " WHERE entity IN (" . getEntity('product') . ")";
5025
        $sql .= $this->db->plimit(100);
5026
5027
        $resql = $this->db->query($sql);
5028
        if ($resql) {
5029
            $num_prods = $this->db->num_rows($resql);
5030
            $i = 0;
5031
            while ($i < $num_prods) {
5032
                $i++;
5033
                $row = $this->db->fetch_row($resql);
5034
                $prodids[$i] = $row[0];
5035
            }
5036
        }
5037
        //Avoid php warning Warning: mt_rand(): max(0) is smaller than min(1) when no product exists
5038
        if (empty($num_prods)) {
5039
            $num_prods = 1;
5040
        }
5041
5042
        // Initialize parameters
5043
        $this->id = 0;
5044
        $this->entity = 1;
5045
        $this->ref = 'SPECIMEN';
5046
        $this->specimen = 1;
5047
        $this->socid = 1;
5048
        $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...
5049
        $this->date_lim_reglement = $nownotime + 3600 * 24 * 30;
5050
        $this->cond_reglement_id   = 1;
5051
        $this->cond_reglement_code = 'RECEP';
5052
        $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...
5053
        $this->mode_reglement_id   = 0; // Not forced to show payment mode CHQ + VIR
5054
        $this->mode_reglement_code = ''; // Not forced to show payment mode CHQ + VIR
5055
5056
        $this->note_public = 'This is a comment (public)';
5057
        $this->note_private = 'This is a comment (private)';
5058
        $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

5058
        /** @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...
5059
5060
        $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

5060
        /** @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...
5061
5062
        $this->multicurrency_tx = 1;
5063
        $this->multicurrency_code = $conf->currency;
5064
5065
        $this->fk_incoterms = 0;
5066
        $this->location_incoterms = '';
5067
5068
        if (empty($option) || $option != 'nolines') {
5069
            // Lines
5070
            $nbp = 5;
5071
            $xnbp = 0;
5072
            while ($xnbp < $nbp) {
5073
                $line = new FactureLigne($this->db);
5074
                $line->desc = $langs->trans("Description") . " " . $xnbp;
5075
                $line->qty = 1;
5076
                $line->subprice = 100;
5077
                $line->tva_tx = 19.6;
5078
                $line->localtax1_tx = 0;
5079
                $line->localtax2_tx = 0;
5080
                $line->remise_percent = 0;
5081
                if ($xnbp == 1) {        // Qty is negative (product line)
5082
                    $prodid = mt_rand(1, $num_prods);
5083
                    $line->fk_product = $prodids[$prodid];
5084
                    $line->qty = -1;
5085
                    $line->total_ht = -100;
5086
                    $line->total_ttc = -119.6;
5087
                    $line->total_tva = -19.6;
5088
                    $line->multicurrency_total_ht = -200;
5089
                    $line->multicurrency_total_ttc = -239.2;
5090
                    $line->multicurrency_total_tva = -39.2;
5091
                } elseif ($xnbp == 2) {    // UP is negative (free line)
5092
                    $line->subprice = -100;
5093
                    $line->total_ht = -100;
5094
                    $line->total_ttc = -119.6;
5095
                    $line->total_tva = -19.6;
5096
                    $line->remise_percent = 0;
5097
                    $line->multicurrency_total_ht = -200;
5098
                    $line->multicurrency_total_ttc = -239.2;
5099
                    $line->multicurrency_total_tva = -39.2;
5100
                } elseif ($xnbp == 3) {    // Discount is 50% (product line)
5101
                    $prodid = mt_rand(1, $num_prods);
5102
                    $line->fk_product = $prodids[$prodid];
5103
                    $line->total_ht = 50;
5104
                    $line->total_ttc = 59.8;
5105
                    $line->total_tva = 9.8;
5106
                    $line->multicurrency_total_ht = 100;
5107
                    $line->multicurrency_total_ttc = 119.6;
5108
                    $line->multicurrency_total_tva = 19.6;
5109
                    $line->remise_percent = 50;
5110
                } else { // (product line)
5111
                    $prodid = mt_rand(1, $num_prods);
5112
                    $line->fk_product = $prodids[$prodid];
5113
                    $line->total_ht = 100;
5114
                    $line->total_ttc = 119.6;
5115
                    $line->total_tva = 19.6;
5116
                    $line->multicurrency_total_ht = 200;
5117
                    $line->multicurrency_total_ttc = 239.2;
5118
                    $line->multicurrency_total_tva = 39.2;
5119
                    $line->remise_percent = 0;
5120
                }
5121
5122
                $this->lines[$xnbp] = $line;
5123
5124
5125
                $this->total_ht       += $line->total_ht;
5126
                $this->total_tva      += $line->total_tva;
5127
                $this->total_ttc      += $line->total_ttc;
5128
5129
                $this->multicurrency_total_ht       += $line->multicurrency_total_ht;
5130
                $this->multicurrency_total_tva      += $line->multicurrency_total_tva;
5131
                $this->multicurrency_total_ttc      += $line->multicurrency_total_ttc;
5132
5133
                $xnbp++;
5134
            }
5135
            $this->revenuestamp = 0;
5136
5137
            // Add a line "offered"
5138
            $line = new FactureLigne($this->db);
5139
            $line->desc = $langs->trans("Description") . " (offered line)";
5140
            $line->qty = 1;
5141
            $line->subprice = 100;
5142
            $line->tva_tx = 19.6;
5143
            $line->localtax1_tx = 0;
5144
            $line->localtax2_tx = 0;
5145
            $line->remise_percent = 100;
5146
            $line->total_ht = 0;
5147
            $line->total_ttc = 0; // 90 * 1.196
5148
            $line->total_tva = 0;
5149
            $line->multicurrency_total_ht = 0;
5150
            $line->multicurrency_total_ttc = 0;
5151
            $line->multicurrency_total_tva = 0;
5152
            $prodid = mt_rand(1, $num_prods);
5153
            $line->fk_product = $prodids[$prodid];
5154
5155
            $this->lines[$xnbp] = $line;
5156
            $xnbp++;
5157
        }
5158
5159
        return 1;
5160
    }
5161
5162
    /**
5163
     *      Load indicators for dashboard (this->nbtodo and this->nbtodolate)
5164
     *
5165
     *      @return         int     Return integer <0 if KO, >0 if OK
5166
     */
5167
    public function loadStateBoard()
5168
    {
5169
        global $conf, $user;
5170
5171
        $this->nb = array();
5172
5173
        $clause = "WHERE";
5174
5175
        $sql = "SELECT count(f.rowid) as nb";
5176
        $sql .= " FROM " . MAIN_DB_PREFIX . "facture as f";
5177
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON f.fk_soc = s.rowid";
5178
        if (!$user->hasRight('societe', 'client', 'voir')) {
5179
            $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON s.rowid = sc.fk_soc";
5180
            $sql .= " WHERE sc.fk_user = " . ((int) $user->id);
5181
            $clause = "AND";
5182
        }
5183
        $sql .= " " . $clause . " f.entity IN (" . getEntity('invoice') . ")";
5184
5185
        $resql = $this->db->query($sql);
5186
        if ($resql) {
5187
            while ($obj = $this->db->fetch_object($resql)) {
5188
                $this->nb["invoices"] = $obj->nb;
5189
            }
5190
            $this->db->free($resql);
5191
            return 1;
5192
        } else {
5193
            dol_print_error($this->db);
5194
            $this->error = $this->db->error();
5195
            return -1;
5196
        }
5197
    }
5198
5199
    /**
5200
     *  Create an array of invoice lines
5201
     *
5202
     *  @return int     >0 if OK, <0 if KO
5203
     */
5204
    public function getLinesArray()
5205
    {
5206
        return $this->fetch_lines();
5207
    }
5208
5209
    /**
5210
     *  Create a document onto disk according to template module.
5211
     *
5212
     *  @param  string      $modele         Generator to use. Caller must set it to obj->model_pdf or GETPOST('model','alpha') for example.
5213
     *  @param  Translate   $outputlangs    Object lang to use for translation
5214
     *  @param  int         $hidedetails    Hide details of lines
5215
     *  @param  int         $hidedesc       Hide description
5216
     *  @param  int         $hideref        Hide ref
5217
     *  @param  null|array  $moreparams     Array to provide more information
5218
     *  @return int                         Return integer <0 if KO, >0 if OK
5219
     */
5220
    public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
5221
    {
5222
        global $conf, $langs;
5223
5224
        $outputlangs->loadLangs(array("bills", "products"));
5225
5226
        if (!dol_strlen($modele)) {
5227
            $modele = 'crabe';
5228
            $thisTypeConfName = 'FACTURE_ADDON_PDF_' . $this->type;
5229
5230
            if (!empty($this->model_pdf)) {
5231
                $modele = $this->model_pdf;
5232
            } elseif (getDolGlobalString($thisTypeConfName)) {
5233
                $modele = getDolGlobalString($thisTypeConfName);
5234
            } elseif (getDolGlobalString('FACTURE_ADDON_PDF')) {
5235
                $modele = getDolGlobalString('FACTURE_ADDON_PDF');
5236
            }
5237
        }
5238
5239
        $modelpath = "core/modules/facture/doc/";
5240
5241
        return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
5242
    }
5243
5244
    /**
5245
     * Gets the smallest reference available for a new cycle
5246
     *
5247
     * @return int >= 1 if OK, -1 if error
5248
     */
5249
    public function newCycle()
5250
    {
5251
        $sql = "SELECT max(situation_cycle_ref) as maxsituationref";
5252
        $sql .= " FROM " . MAIN_DB_PREFIX . "facture as f";
5253
        $sql .= " WHERE f.entity IN (" . getEntity('invoice', 0) . ")";
5254
5255
        $resql = $this->db->query($sql);
5256
        if ($resql) {
5257
            if ($this->db->num_rows($resql) > 0) {
5258
                $ref = 0;
5259
                $obj = $this->db->fetch_object($resql);
5260
                if ($obj) {
5261
                    $ref = $obj->maxsituationref;
5262
                }
5263
                $ref++;
5264
            } else {
5265
                $ref = 1;
5266
            }
5267
            $this->db->free($resql);
5268
            return $ref;
5269
        } else {
5270
            $this->error = $this->db->lasterror();
5271
            dol_syslog("Error sql=" . $sql . ", error=" . $this->error, LOG_ERR);
5272
            return -1;
5273
        }
5274
    }
5275
5276
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5277
    /**
5278
     * Checks if the invoice is the first of a cycle
5279
     *
5280
     * @return boolean
5281
     */
5282
    public function is_first()
5283
    {
5284
		// phpcs:enable
5285
        return ($this->situation_counter == 1);
5286
    }
5287
5288
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5289
    /**
5290
     * Returns an array containing the previous situations as Facture objects
5291
     *
5292
     * @return mixed -1 if error, array of previous situations
5293
     */
5294
    public function get_prev_sits()
5295
    {
5296
		// phpcs:enable
5297
        global $conf;
5298
5299
        $sql = 'SELECT rowid FROM ' . MAIN_DB_PREFIX . 'facture';
5300
        $sql .= ' WHERE situation_cycle_ref = ' . ((int) $this->situation_cycle_ref);
5301
        $sql .= ' AND situation_counter < ' . ((int) $this->situation_counter);
5302
        $sql .= ' AND entity = ' . ($this->entity > 0 ? $this->entity : $conf->entity);
5303
        $resql = $this->db->query($sql);
5304
        $res = array();
5305
        if ($resql && $this->db->num_rows($resql) > 0) {
5306
            while ($row = $this->db->fetch_object($resql)) {
5307
                $id = $row->rowid;
5308
                $situation = new Facture($this->db);
5309
                $situation->fetch($id);
5310
                $res[] = $situation;
5311
            }
5312
        } else {
5313
            $this->error = $this->db->error();
5314
            dol_syslog("Error sql=" . $sql . ", error=" . $this->error, LOG_ERR);
5315
            return -1;
5316
        }
5317
5318
        return $res;
5319
    }
5320
5321
    /**
5322
     * Sets the invoice as a final situation
5323
     *
5324
     *  @param      User    $user       Object user
5325
     *  @param      int     $notrigger  1=Does not execute triggers, 0= execute triggers
5326
     *  @return     int                 Return integer <0 if KO, >0 if OK
5327
     */
5328
    public function setFinal(User $user, $notrigger = 0)
5329
    {
5330
        $error = 0;
5331
5332
        $this->db->begin();
5333
5334
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture SET situation_final = ' . ((int) $this->situation_final) . ' WHERE rowid = ' . ((int) $this->id);
5335
5336
        dol_syslog(__METHOD__, LOG_DEBUG);
5337
        $resql = $this->db->query($sql);
5338
        if (!$resql) {
5339
            $this->errors[] = $this->db->error();
5340
            $error++;
5341
        }
5342
5343
        if (!$notrigger && empty($error)) {
5344
            // Call trigger
5345
            $result = $this->call_trigger('BILL_MODIFY', $user);
5346
            if ($result < 0) {
5347
                $error++;
5348
            }
5349
            // End call triggers
5350
        }
5351
5352
        if (!$error) {
5353
            $this->db->commit();
5354
            return 1;
5355
        } else {
5356
            foreach ($this->errors as $errmsg) {
5357
                dol_syslog(__METHOD__ . ' Error: ' . $errmsg, LOG_ERR);
5358
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
5359
            }
5360
            $this->db->rollback();
5361
            return -1 * $error;
5362
        }
5363
    }
5364
5365
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
5366
    /**
5367
     * Checks if the invoice is the last in its cycle
5368
     *
5369
     * @return bool Last of the cycle status
5370
     */
5371
    public function is_last_in_cycle()
5372
    {
5373
		// phpcs:enable
5374
        global $conf;
5375
5376
        if (!empty($this->situation_cycle_ref)) {
5377
            // No point in testing anything if we're not inside a cycle
5378
            $sql = 'SELECT max(situation_counter) FROM ' . MAIN_DB_PREFIX . 'facture';
5379
            $sql .= ' WHERE situation_cycle_ref = ' . ((int) $this->situation_cycle_ref);
5380
            $sql .= ' AND entity = ' . ($this->entity > 0 ? $this->entity : $conf->entity);
5381
            $resql = $this->db->query($sql);
5382
5383
            if ($resql && $this->db->num_rows($resql) > 0 && $res = $this->db->fetch_array($resql)) {
5384
                $last = $res['max(situation_counter)'];
5385
                return ($last == $this->situation_counter);
5386
            } else {
5387
                $this->error = $this->db->lasterror();
5388
                dol_syslog(get_class($this) . "::select Error " . $this->error, LOG_ERR);
5389
                return false;
5390
            }
5391
        } else {
5392
            return true;
5393
        }
5394
    }
5395
5396
    /**
5397
     * Replace a thirdparty id with another one.
5398
     *
5399
     * @param   DoliDB  $dbs        Database handler, because function is static we name it $dbs not $db to avoid breaking coding test
5400
     * @param   int     $origin_id  Old thirdparty id
5401
     * @param   int     $dest_id    New thirdparty id
5402
     * @return  bool
5403
     */
5404
    public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
5405
    {
5406
        $tables = array(
5407
            'facture'
5408
        );
5409
5410
        return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
5411
    }
5412
5413
    /**
5414
     * Replace a product id with another one.
5415
     *
5416
     * @param DoliDB $db Database handler
5417
     * @param int $origin_id Old product id
5418
     * @param int $dest_id New product id
5419
     * @return bool
5420
     */
5421
    public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
5422
    {
5423
        $tables = array(
5424
            'facturedet'
5425
        );
5426
5427
        return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
5428
    }
5429
5430
    /**
5431
     * Is the customer invoice delayed?
5432
     *
5433
     * @return bool
5434
     */
5435
    public function hasDelay()
5436
    {
5437
        global $conf;
5438
5439
        $now = dol_now();
5440
5441
        // Paid invoices have status STATUS_CLOSED
5442
        if ($this->status != Facture::STATUS_VALIDATED) {
5443
            return false;
5444
        }
5445
5446
        $hasDelay = $this->date_lim_reglement < ($now - $conf->facture->client->warning_delay);
5447
        if ($hasDelay && !empty($this->retained_warranty) && !empty($this->retained_warranty_date_limit)) {
5448
            $totalpaid = $this->getSommePaiement();
5449
            $totalpaid = (float) $totalpaid;
5450
            $RetainedWarrantyAmount = $this->getRetainedWarrantyAmount();
5451
            if ($totalpaid >= 0 && $RetainedWarrantyAmount >= 0) {
5452
                if (($totalpaid < $this->total_ttc - $RetainedWarrantyAmount) && $this->date_lim_reglement < ($now - $conf->facture->client->warning_delay)) {
5453
                    $hasDelay = 1;
5454
                } elseif ($totalpaid < $this->total_ttc && $this->retained_warranty_date_limit < ($now - $conf->facture->client->warning_delay)) {
5455
                    $hasDelay = 1;
5456
                } else {
5457
                    $hasDelay = 0;
5458
                }
5459
            }
5460
        }
5461
5462
        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...
5463
    }
5464
5465
    /**
5466
     * Currently used for documents generation : to know if retained warranty need to be displayed
5467
     * @return bool
5468
     */
5469
    public function displayRetainedWarranty()
5470
    {
5471
        global $conf;
5472
5473
        // TODO : add a flag on invoices to store this conf : INVOICE_RETAINED_WARRANTY_LIMITED_TO_FINAL_SITUATION
5474
5475
        // 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
5476
5477
        $displayWarranty = false;
5478
        if (!empty($this->retained_warranty)) {
5479
            $displayWarranty = true;
5480
5481
            if ($this->type == Facture::TYPE_SITUATION && getDolGlobalString('INVOICE_RETAINED_WARRANTY_LIMITED_TO_FINAL_SITUATION')) {
5482
                // Check if this situation invoice is 100% for real
5483
                $displayWarranty = false;
5484
                if (!empty($this->situation_final)) {
5485
                    $displayWarranty = true;
5486
                } elseif (!empty($this->lines) && $this->status == Facture::STATUS_DRAFT) {
5487
                    // $object->situation_final need validation to be done so this test is need for draft
5488
                    $displayWarranty = true;
5489
5490
                    foreach ($this->lines as $i => $line) {
5491
                        if ($line->product_type < 2 && $line->situation_percent < 100) {
5492
                            $displayWarranty = false;
5493
                            break;
5494
                        }
5495
                    }
5496
                }
5497
            }
5498
        }
5499
5500
        return $displayWarranty;
5501
    }
5502
5503
    /**
5504
     * @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)
5505
     * @return float or -1 if not available
5506
     */
5507
    public function getRetainedWarrantyAmount($rounding = -1)
5508
    {
5509
        global $conf;
5510
        if (empty($this->retained_warranty)) {
5511
            return -1;
5512
        }
5513
5514
        $retainedWarrantyAmount = 0;
5515
5516
        // Billed - retained warranty
5517
        if ($this->type == Facture::TYPE_SITUATION && getDolGlobalString('INVOICE_RETAINED_WARRANTY_LIMITED_TO_FINAL_SITUATION')) {
5518
            $displayWarranty = true;
5519
            // Check if this situation invoice is 100% for real
5520
            if (!empty($this->lines)) {
5521
                foreach ($this->lines as $i => $line) {
5522
                    if ($line->product_type < 2 && $line->situation_percent < 100) {
5523
                        $displayWarranty = false;
5524
                        break;
5525
                    }
5526
                }
5527
            }
5528
5529
            if ($displayWarranty && !empty($this->situation_final)) {
5530
                $this->fetchPreviousNextSituationInvoice();
5531
                $TPreviousIncoice = $this->tab_previous_situation_invoice;
5532
5533
                $total2BillWT = 0;
5534
                foreach ($TPreviousIncoice as &$fac) {
5535
                    $total2BillWT += $fac->total_ttc;
5536
                }
5537
                $total2BillWT += $this->total_ttc;
5538
5539
                $retainedWarrantyAmount = $total2BillWT * $this->retained_warranty / 100;
5540
            } else {
5541
                return -1;
5542
            }
5543
        } else {
5544
            // Because one day retained warranty could be used on standard invoices
5545
            $retainedWarrantyAmount = $this->total_ttc * $this->retained_warranty / 100;
5546
        }
5547
5548
        if ($rounding < 0) {
5549
            $rounding = min(getDolGlobalString('MAIN_MAX_DECIMALS_UNIT'), getDolGlobalString('MAIN_MAX_DECIMALS_TOT'));
5550
        }
5551
5552
        if ($rounding > 0) {
5553
            return round($retainedWarrantyAmount, $rounding);
5554
        }
5555
5556
        return $retainedWarrantyAmount;
5557
    }
5558
5559
    /**
5560
     *  Change the retained warranty
5561
     *
5562
     *  @param      float       $value      value of retained warranty
5563
     *  @return     int             >0 if OK, <0 if KO
5564
     */
5565
    public function setRetainedWarranty($value)
5566
    {
5567
        dol_syslog(get_class($this) . '::setRetainedWarranty(' . $value . ')');
5568
5569
        if ($this->status >= 0) {
5570
            $fieldname = 'retained_warranty';
5571
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element;
5572
            $sql .= " SET " . $fieldname . " = " . ((float) $value);
5573
            $sql .= ' WHERE rowid=' . ((int) $this->id);
5574
5575
            if ($this->db->query($sql)) {
5576
                $this->retained_warranty = (float) $value;
5577
                return 1;
5578
            } else {
5579
                dol_syslog(get_class($this) . '::setRetainedWarranty Erreur ' . $sql . ' - ' . $this->db->error());
5580
                $this->error = $this->db->error();
5581
                return -1;
5582
            }
5583
        } else {
5584
            dol_syslog(get_class($this) . '::setRetainedWarranty, status of the object is incompatible');
5585
            $this->error = 'Status of the object is incompatible ' . $this->status;
5586
            return -2;
5587
        }
5588
    }
5589
5590
5591
    /**
5592
     *  Change the retained_warranty_date_limit
5593
     *
5594
     *  @param      int     $timestamp      date limit of retained warranty in timestamp format
5595
     *  @param      string  $dateYmd        date limit of retained warranty in Y m d format
5596
     *  @return     int             >0 if OK, <0 if KO
5597
     */
5598
    public function setRetainedWarrantyDateLimit($timestamp, $dateYmd = '')
5599
    {
5600
        if (!$timestamp && $dateYmd) {
5601
            $timestamp = $this->db->jdate($dateYmd);
5602
        }
5603
5604
5605
        dol_syslog(get_class($this) . '::setRetainedWarrantyDateLimit(' . $timestamp . ')');
5606
        if ($this->status >= 0) {
5607
            $fieldname = 'retained_warranty_date_limit';
5608
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element;
5609
            $sql .= " SET " . $fieldname . " = " . (strval($timestamp) != '' ? "'" . $this->db->idate($timestamp) . "'" : 'null');
5610
            $sql .= ' WHERE rowid = ' . ((int) $this->id);
5611
5612
            if ($this->db->query($sql)) {
5613
                $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...
5614
                return 1;
5615
            } else {
5616
                dol_syslog(get_class($this) . '::setRetainedWarrantyDateLimit Erreur ' . $sql . ' - ' . $this->db->error());
5617
                $this->error = $this->db->error();
5618
                return -1;
5619
            }
5620
        } else {
5621
            dol_syslog(get_class($this) . '::setRetainedWarrantyDateLimit, status of the object is incompatible');
5622
            $this->error = 'Status of the object is incompatible ' . $this->status;
5623
            return -2;
5624
        }
5625
    }
5626
5627
5628
    /**
5629
     *  Send reminders by emails for invoices validated that are due.
5630
     *  CAN BE A CRON TASK
5631
     *
5632
     *  @param  int         $nbdays             Delay before due date (or after if delay is negative)
5633
     *  @param  string      $paymentmode        '' or 'all' by default (no filter), or 'LIQ', 'CHQ', CB', ...
5634
     *  @param  int|string  $template           Name (or id) of email template (Must be a template of type 'facture_send')
5635
     *  @param  string      $datetouse          'duedate' (default) or 'invoicedate'
5636
     *  @param  string      $forcerecipient     Force email of recipient (for example to send the email to an accountant supervisor instead of the customer)
5637
     *  @return int                             0 if OK, <>0 if KO (this function is used also by cron so only 0 is OK)
5638
     */
5639
    public function sendEmailsRemindersOnInvoiceDueDate($nbdays = 0, $paymentmode = 'all', $template = '', $datetouse = 'duedate', $forcerecipient = '')
5640
    {
5641
        global $conf, $langs, $user;
5642
5643
        $error = 0;
5644
        $this->output = '';
5645
        $this->error = '';
5646
        $nbMailSend = 0;
5647
        $errorsMsg = array();
5648
5649
        $langs->load("bills");
5650
5651
        if (!isModEnabled('invoice')) { // Should not happen. If module disabled, cron job should not be visible.
5652
            $this->output .= $langs->trans('ModuleNotEnabled', $langs->transnoentitiesnoconv("Facture"));
5653
            return 0;
5654
        }
5655
        if (!in_array($datetouse, array('duedate', 'invoicedate'))) {
5656
            $this->output .= 'Bad value for parameter datetouse. Must be "duedate" or "invoicedate"';
5657
            return 0;
5658
        }
5659
        /*if (empty($conf->global->FACTURE_REMINDER_EMAIL)) {
5660
            $langs->load("bills");
5661
            $this->output .= $langs->trans('EventRemindersByEmailNotEnabled', $langs->transnoentitiesnoconv("Facture"));
5662
            return 0;
5663
        }
5664
        */
5665
5666
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/date.lib.php';
5667
        $formmail = new FormMail($this->db);
5668
5669
        $now = dol_now();
5670
        $tmpidate = dol_get_first_hour(dol_time_plus_duree($now, $nbdays, 'd'), 'gmt');
5671
5672
        $tmpinvoice = new Facture($this->db);
5673
5674
        dol_syslog(__METHOD__ . " start", LOG_INFO);
5675
5676
        // Select all action comm reminder
5677
        $sql = "SELECT rowid as id FROM " . MAIN_DB_PREFIX . "facture as f";
5678
        if (!empty($paymentmode) && $paymentmode != 'all') {
5679
            $sql .= ", " . MAIN_DB_PREFIX . "c_paiement as cp";
5680
        }
5681
        $sql .= " WHERE f.paye = 0";    // Only unpaid
5682
        $sql .= " AND f.fk_statut = " . self::STATUS_VALIDATED;   // Only validated status
5683
        if ($datetouse == 'invoicedate') {
5684
            $sql .= " AND f.datef = '" . $this->db->idate($tmpidate, 'gmt') . "'";
5685
        } else {
5686
            $sql .= " AND f.date_lim_reglement = '" . $this->db->idate($tmpidate, 'gmt') . "'";
5687
        }
5688
        $sql .= " AND f.entity IN (" . getEntity('facture', 0) . ")";   // One batch process only one company (no sharing)
5689
        if (!empty($paymentmode) && $paymentmode != 'all') {
5690
            $sql .= " AND f.fk_mode_reglement = cp.id AND cp.code = '" . $this->db->escape($paymentmode) . "'";
5691
        }
5692
        // TODO Add a filter to check there is no payment started yet
5693
        if ($datetouse == 'invoicedate') {
5694
            $sql .= $this->db->order("datef", "ASC");
5695
        } else {
5696
            $sql .= $this->db->order("date_lim_reglement", "ASC");
5697
        }
5698
5699
        $resql = $this->db->query($sql);
5700
5701
        $stmpidate = dol_print_date($tmpidate, 'day', 'gmt');
5702
        if ($datetouse == 'invoicedate') {
5703
            $this->output .= $langs->transnoentitiesnoconv("SearchValidatedInvoicesWithDate", $stmpidate);
5704
        } else {
5705
            $this->output .= $langs->transnoentitiesnoconv("SearchUnpaidInvoicesWithDueDate", $stmpidate);
5706
        }
5707
        if (!empty($paymentmode) && $paymentmode != 'all') {
5708
            $this->output .= ' (' . $langs->transnoentitiesnoconv("PaymentMode") . ' ' . $paymentmode . ')';
5709
        }
5710
        $this->output .= '<br>';
5711
5712
        if ($resql) {
5713
            while ($obj = $this->db->fetch_object($resql)) {
5714
                if (!$error) {
5715
                    // Load event
5716
                    $res = $tmpinvoice->fetch($obj->id);
5717
                    if ($res > 0) {
5718
                        $tmpinvoice->fetch_thirdparty();
5719
5720
                        $outputlangs = new Translate('', $conf);
5721
                        if ($tmpinvoice->thirdparty->default_lang) {
5722
                            $outputlangs->setDefaultLang($tmpinvoice->thirdparty->default_lang);
5723
                            $outputlangs->loadLangs(array("main", "bills"));
5724
                        } else {
5725
                            $outputlangs = $langs;
5726
                        }
5727
5728
                        // Select email template according to language of recipient
5729
                        $arraymessage = $formmail->getEMailTemplate($this->db, 'facture_send', $user, $outputlangs, (is_numeric($template) ? $template : 0), 1, (is_numeric($template) ? '' : $template));
5730
                        if (is_numeric($arraymessage) && $arraymessage <= 0) {
5731
                            $langs->load("errors");
5732
                            $this->output .= $langs->trans('ErrorFailedToFindEmailTemplate', $template);
5733
                            return 0;
5734
                        }
5735
5736
                        // PREPARE EMAIL
5737
                        $errormesg = '';
5738
5739
                        // Make substitution in email content
5740
                        $substitutionarray = getCommonSubstitutionArray($outputlangs, 0, '', $tmpinvoice);
5741
5742
                        complete_substitutions_array($substitutionarray, $outputlangs, $tmpinvoice);
5743
5744
                        // Topic
5745
                        $sendTopic = make_substitutions(empty($arraymessage->topic) ? $outputlangs->transnoentitiesnoconv('InformationMessage') : $arraymessage->topic, $substitutionarray, $outputlangs, 1);
5746
5747
                        // Content
5748
                        $content = $outputlangs->transnoentitiesnoconv($arraymessage->content);
5749
5750
                        $sendContent = make_substitutions($content, $substitutionarray, $outputlangs, 1);
5751
5752
                        // Recipient
5753
                        $to = array();
5754
                        if ($forcerecipient) {  // If a recipient was forced
5755
                            $to = array($forcerecipient);
5756
                        } else {
5757
                            $res = $tmpinvoice->fetch_thirdparty();
5758
                            $recipient = $tmpinvoice->thirdparty;
5759
                            if ($res > 0) {
5760
                                $tmparraycontact = $tmpinvoice->liste_contact(-1, 'external', 0, 'BILLING');
5761
                                if (is_array($tmparraycontact) && count($tmparraycontact) > 0) {
5762
                                    foreach ($tmparraycontact as $data_email) {
5763
                                        if (!empty($data_email['email'])) {
5764
                                            $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

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