FactureFournisseur::setPaid()   B
last analyzed

Complexity

Conditions 8

Size

Total Lines 49
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 33
nop 3
dl 0
loc 49
rs 8.1475
c 0
b 0
f 0
1
<?php
2
3
/* Copyright (C) 2002-2004  Rodolphe Quiedeville        <[email protected]>
4
 * Copyright (C) 2004-2012	Laurent Destailleur		    <[email protected]>
5
 * Copyright (C) 2004		Christophe Combelles	    <[email protected]>
6
 * Copyright (C) 2005		Marc Barilley			    <[email protected]>
7
 * Copyright (C) 2005-2012	Regis Houssin			    <[email protected]>
8
 * Copyright (C) 2010-2020	Juanjo Menent			    <[email protected]>
9
 * Copyright (C) 2013-2019	Philippe Grand			    <[email protected]>
10
 * Copyright (C) 2013		Florian Henry			    <[email protected]>
11
 * Copyright (C) 2014-2016	Marcos García			    <[email protected]>
12
 * Copyright (C) 2015		Bahfir Abbes			    <[email protected]>
13
 * Copyright (C) 2015-2022	Ferran Marcet			    <[email protected]>
14
 * Copyright (C) 2016-2023	Alexandre Spangaro		    <[email protected]>
15
 * Copyright (C) 2018       Nicolas ZABOURI			    <[email protected]>
16
 * Copyright (C) 2018-2024  Frédéric France             <[email protected]>
17
 * Copyright (C) 2022      	Gauthier VERDOL     	    <[email protected]>
18
 * Copyright (C) 2023		Nick Fragoulis
19
 * Copyright (C) 2024		MDW							<[email protected]>
20
 * Copyright (C) 2024       Rafael San José             <[email protected]>
21
 *
22
 * This program is free software; you can redistribute it and/or modify
23
 * it under the terms of the GNU General Public License as published by
24
 * the Free Software Foundation; either version 3 of the License, or
25
 * (at your option) any later version.
26
 *
27
 * This program is distributed in the hope that it will be useful,
28
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
29
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
30
 * GNU General Public License for more details.
31
 *
32
 * You should have received a copy of the GNU General Public License
33
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
34
 */
35
36
namespace Dolibarr\Code\Fourn\Classes;
37
38
use Dolibarr\Code\Core\Classes\CommonInvoice;
39
use Dolibarr\Code\Core\Classes\CommonInvoiceLine;
40
use Dolibarr\Code\Core\Classes\WorkboardResponse;
41
use Dolibarr\Code\MultiCurrency\Classes\MultiCurrency;
42
43
/**
44
 *  \file       htdocs/fourn/class/fournisseur.facture.class.php
45
 *  \ingroup    fournisseur,facture
46
 *  \brief      File of class to manage suppliers invoices
47
 */
48
49
use Dolibarr\Core\Base\CommonObjectLine;
50
use DoliDB;
51
52
if (isModEnabled('accounting')) {
53
}
54
55
/**
56
 *  Class to manage suppliers invoices
57
 */
58
class FactureFournisseur extends CommonInvoice
59
{
60
    /**
61
     * @var string ID to identify managed object
62
     */
63
    public $element = 'invoice_supplier';
64
65
    /**
66
     * @var string Name of table without prefix where object is stored
67
     */
68
    public $table_element = 'facture_fourn';
69
70
    /**
71
     * @var string    Name of subtable line
72
     */
73
    public $table_element_line = 'facture_fourn_det';
74
75
    /**
76
     * @var string  Name of class line
77
     */
78
    public $class_element_line = 'SupplierInvoiceLine';
79
    /**
80
     * @var string Field with ID of parent key if this field has a parent
81
     */
82
    public $fk_element = 'fk_facture_fourn';
83
84
    /**
85
     * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
86
     */
87
    public $picto = 'supplier_invoice';
88
89
    /**
90
     * 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
91
     * @var integer
92
     */
93
    public $restrictiononfksoc = 1;
94
95
    /**
96
     * {@inheritdoc}
97
     */
98
    protected $table_ref_field = 'ref';
99
100
    /**
101
     * @var int ID
102
     */
103
    public $rowid;
104
105
    /**
106
     * @var string Ref
107
     */
108
    public $ref;
109
110
    /**
111
     * @var string Ref supplier
112
     */
113
    public $ref_supplier;
114
115
    /**
116
     * @var string  Label of invoice
117
     * @deprecated  Use $label
118
     */
119
    public $libelle;
120
    /**
121
     * @var string Label of invoice
122
     */
123
    public $label;
124
125
    //Check constants for types
126
    public $type = self::TYPE_STANDARD;
127
128
    /**
129
     * Supplier invoice status
130
     * @var int
131
     * @deprecated
132
     * @see $status
133
     */
134
    public $statut;
135
136
    /**
137
     * Supplier invoice status
138
     * @var int
139
     * @see FactureFournisseur::STATUS_DRAFT, FactureFournisseur::STATUS_VALIDATED, FactureFournisseur::STATUS_PAID, FactureFournisseur::STATUS_ABANDONED
140
     */
141
    public $status;
142
143
    /**
144
     * Supplier invoice status
145
     * @var int
146
     * @deprecated
147
     * @see $status
148
     */
149
    public $fk_statut;
150
151
    /**
152
     * Set to 1 if the invoice is completely paid, otherwise is 0
153
     * @var int<0,1>
154
     * @deprecated Use $paid
155
     */
156
    public $paye;
157
    /**
158
     * Set to 1 if the invoice is completely paid, otherwise is 0
159
     * @var int<0,1>
160
     */
161
    public $paid;
162
163
    /**
164
     * @var int
165
     * @deprecated  Use $user_creation_id
166
     */
167
    public $author;
168
169
    /**
170
     * Date creation record (datec)
171
     *
172
     * @var integer
173
     */
174
    public $datec;
175
176
    /**
177
     * Max payment date (date_echeance)
178
     *
179
     * @var integer
180
     */
181
    public $date_echeance;
182
183
    /**
184
     * @var double $amount
185
     * @deprecated See $total_ttc, $total_ht, $total_tva
186
     */
187
    public $amount = 0;
188
    /**
189
     * @var double $remise
190
     * @deprecated
191
     */
192
    public $remise = 0;
193
194
    /**
195
     * @var float tva
196
     * @deprecated Use $total_tva
197
     */
198
    public $tva;
199
200
    // Warning: Do not set default value into property definition. it must stay null.
201
    // For example to avoid to have substitution done when object is generic and not yet defined.
202
    /** @var ?string */
203
    public $localtax1;
204
    /** @var ?string */
205
    public $localtax2;
206
    /** @var float */
207
    public $total_ht;
208
    /** @var float */
209
    public $total_tva;
210
    /** @var float */
211
    public $total_localtax1;
212
    /** @var float */
213
    public $total_localtax2;
214
    /** @var float */
215
    public $total_ttc;
216
217
    /**
218
     * @deprecated
219
     * @see $note_private, $note_public
220
     * @var string
221
     */
222
    public $note;
223
    /** @var string */
224
    public $note_private;
225
    /** @var string */
226
    public $note_public;
227
    /** @var int */
228
    public $propalid;
229
230
    /**
231
     * @var int ID
232
     */
233
    public $fk_account;     // default bank account
234
235
    /**
236
     * @var int Transport mode id
237
     */
238
    public $transport_mode_id;
239
240
    /**
241
     * @var int<0,1>  VAT reverse charge can be used on the invoice
242
     */
243
    public $vat_reverse_charge;
244
245
    public $extraparams = array();
246
247
    /**
248
     * Invoice lines
249
     * @var CommonInvoiceLine[]
250
     */
251
    public $lines = array();
252
253
    /**
254
     * @deprecated
255
     * @var ?Fournisseur
256
     */
257
    public $fournisseur;
258
259
    //! id of source invoice if replacement invoice or credit note
260
    /**
261
     * @var int ID
262
     */
263
    public $fk_facture_source;
264
265
    /** @var int */
266
    public $fac_rec;
267
    /** @var int */
268
    public $fk_fac_rec_source;
269
270
    public $fields = array(
271
        'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 10),
272
        'ref' => array('type' => 'varchar(255)', 'label' => 'Ref', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'showoncombobox' => 1, 'position' => 15),
273
        'ref_supplier' => array('type' => 'varchar(255)', 'label' => 'RefSupplier', 'enabled' => 1, 'visible' => -1, 'position' => 20),
274
        'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 25, 'index' => 1),
275
        'ref_ext' => array('type' => 'varchar(255)', 'label' => 'RefExt', 'enabled' => 1, 'visible' => 0, 'position' => 30),
276
        'type' => array('type' => 'smallint(6)', 'label' => 'Type', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 35),
277
        'subtype' => array('type' => 'smallint(6)', 'label' => 'InvoiceSubtype', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 36),
278
        'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 'isModEnabled("societe")', 'visible' => -1, 'notnull' => 1, 'position' => 40),
279
        'datec' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -1, 'position' => 45),
280
        'datef' => array('type' => 'date', 'label' => 'Date', 'enabled' => 1, 'visible' => -1, 'position' => 50),
281
        'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 55),
282
        'libelle' => array('type' => 'varchar(255)', 'label' => 'Label', 'enabled' => 1, 'visible' => -1, 'position' => 60),
283
        'paye' => array('type' => 'smallint(6)', 'label' => 'Paye', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 65),
284
        'amount' => array('type' => 'double(24,8)', 'label' => 'Amount', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 70),
285
        'remise' => array('type' => 'double(24,8)', 'label' => 'Discount', 'enabled' => 1, 'visible' => -1, 'position' => 75),
286
        'close_code' => array('type' => 'varchar(16)', 'label' => 'CloseCode', 'enabled' => 1, 'visible' => -1, 'position' => 80),
287
        'close_note' => array('type' => 'varchar(128)', 'label' => 'CloseNote', 'enabled' => 1, 'visible' => -1, 'position' => 85),
288
        'tva' => array('type' => 'double(24,8)', 'label' => 'Tva', 'enabled' => 1, 'visible' => -1, 'position' => 90),
289
        'localtax1' => array('type' => 'double(24,8)', 'label' => 'Localtax1', 'enabled' => 1, 'visible' => -1, 'position' => 95),
290
        'localtax2' => array('type' => 'double(24,8)', 'label' => 'Localtax2', 'enabled' => 1, 'visible' => -1, 'position' => 100),
291
        'total_ht' => array('type' => 'double(24,8)', 'label' => 'TotalHT', 'enabled' => 1, 'visible' => -1, 'position' => 105),
292
        'total_tva' => array('type' => 'double(24,8)', 'label' => 'TotalVAT', 'enabled' => 1, 'visible' => -1, 'position' => 110),
293
        'total_ttc' => array('type' => 'double(24,8)', 'label' => 'TotalTTC', 'enabled' => 1, 'visible' => -1, 'position' => 115),
294
        'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => -1, 'position' => 125),
295
        'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'position' => 130),
296
        'fk_user_valid' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserValidation', 'enabled' => 1, 'visible' => -1, 'position' => 135),
297
        'fk_facture_source' => array('type' => 'integer', 'label' => 'Fk facture source', 'enabled' => 1, 'visible' => -1, 'position' => 140),
298
        'fk_projet' => array('type' => 'integer:Project:projet/class/project.class.php:1:fk_statut=1', 'label' => 'Project', 'enabled' => "isModEnabled('project')", 'visible' => -1, 'position' => 145),
299
        'fk_account' => array('type' => 'integer', 'label' => 'Account', 'enabled' => 'isModEnabled("bank")', 'visible' => -1, 'position' => 150),
300
        'fk_cond_reglement' => array('type' => 'integer', 'label' => 'PaymentTerm', 'enabled' => 1, 'visible' => -1, 'position' => 155),
301
        'fk_mode_reglement' => array('type' => 'integer', 'label' => 'PaymentMode', 'enabled' => 1, 'visible' => -1, 'position' => 160),
302
        'date_lim_reglement' => array('type' => 'date', 'label' => 'DateLimReglement', 'enabled' => 1, 'visible' => -1, 'position' => 165),
303
        'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 170),
304
        'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 175),
305
        'model_pdf' => array('type' => 'varchar(255)', 'label' => 'ModelPdf', 'enabled' => 1, 'visible' => 0, 'position' => 180),
306
        'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => -1, 'position' => 190),
307
        'fk_incoterms' => array('type' => 'integer', 'label' => 'IncotermCode', 'enabled' => 1, 'visible' => -1, 'position' => 195),
308
        'location_incoterms' => array('type' => 'varchar(255)', 'label' => 'IncotermLocation', 'enabled' => 1, 'visible' => -1, 'position' => 200),
309
        'fk_multicurrency' => array('type' => 'integer', 'label' => 'MulticurrencyId', 'enabled' => 1, 'visible' => -1, 'position' => 205),
310
        'multicurrency_code' => array('type' => 'varchar(255)', 'label' => 'MulticurrencyCode', 'enabled' => 1, 'visible' => -1, 'position' => 210),
311
        'multicurrency_tx' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyRate', 'enabled' => 1, 'visible' => -1, 'position' => 215),
312
        'multicurrency_total_ht' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyTotalHT', 'enabled' => 1, 'visible' => -1, 'position' => 220),
313
        'multicurrency_total_tva' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyTotalVAT', 'enabled' => 1, 'visible' => -1, 'position' => 225),
314
        'multicurrency_total_ttc' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyTotalTTC', 'enabled' => 1, 'visible' => -1, 'position' => 230),
315
        'date_pointoftax' => array('type' => 'date', 'label' => 'Date pointoftax', 'enabled' => 1, 'visible' => -1, 'position' => 235),
316
        'date_valid' => array('type' => 'date', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -1, 'position' => 240),
317
        'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'Last main doc', 'enabled' => 1, 'visible' => -1, 'position' => 245),
318
        'fk_statut' => array('type' => 'smallint(6)', 'label' => 'Status', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 500),
319
        'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'position' => 900),
320
    );
321
322
323
    /**
324
     * Standard invoice
325
     */
326
    const TYPE_STANDARD = 0;
327
328
    /**
329
     * Replacement invoice
330
     */
331
    const TYPE_REPLACEMENT = 1;
332
333
    /**
334
     * Credit note invoice
335
     */
336
    const TYPE_CREDIT_NOTE = 2;
337
338
    /**
339
     * Deposit invoice
340
     */
341
    const TYPE_DEPOSIT = 3;
342
343
    /**
344
     * Draft
345
     */
346
    const STATUS_DRAFT = 0;
347
348
    /**
349
     * Validated (need to be paid)
350
     */
351
    const STATUS_VALIDATED = 1;
352
353
    /**
354
     * Classified paid.
355
     * If paid partially, $this->close_code can be:
356
     * - CLOSECODE_DISCOUNTVAT
357
     * - CLOSECODE_BADCREDIT
358
     * If paid completely, this->close_code will be null
359
     */
360
    const STATUS_CLOSED = 2;
361
362
    /**
363
     * Classified abandoned and no payment done.
364
     * $this->close_code can be:
365
     * - CLOSECODE_BADCREDIT
366
     * - CLOSECODE_ABANDONED
367
     * - CLOSECODE_REPLACED
368
     */
369
    const STATUS_ABANDONED = 3;
370
371
    const CLOSECODE_DISCOUNTVAT = 'discount_vat';
372
    const CLOSECODE_BADCREDIT = 'badsupplier';
373
    const CLOSECODE_ABANDONED = 'abandon';
374
    const CLOSECODE_REPLACED = 'replaced';
375
376
    /**
377
     *  Constructor
378
     *
379
     *  @param      DoliDB      $db      Database handler
380
     */
381
    public function __construct($db)
382
    {
383
        $this->db = $db;
384
385
        $this->ismultientitymanaged = 1;
386
    }
387
388
    /**
389
     *    Create supplier invoice into database
390
     *
391
     *    @param      User      $user       user object that creates
392
     *    @return     int                   Id invoice created if OK, < 0 if KO
393
     */
394
    public function create($user)
395
    {
396
        global $langs, $conf, $hookmanager;
397
398
        $error = 0;
399
        $now = dol_now();
400
401
        // Clean parameters
402
        if (isset($this->ref_supplier)) {
403
            $this->ref_supplier = trim($this->ref_supplier);
404
        }
405
        if (empty($this->type)) {
406
            $this->type = self::TYPE_STANDARD;
407
        }
408
        if (empty($this->date)) {
409
            $this->date = $now;
410
        }
411
412
        // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
413
        if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
414
            list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date);
415
        } else {
416
            $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
417
        }
418
        if (empty($this->fk_multicurrency)) {
419
            $this->multicurrency_code = $conf->currency;
420
            $this->fk_multicurrency = 0;
421
            $this->multicurrency_tx = 1;
422
        }
423
424
        $this->db->begin();
425
426
        // Create invoice from a template recurring invoice
427
        if ($this->fac_rec > 0) {
428
            $this->fk_fac_rec_source = $this->fac_rec;
429
430
            $_facrec = new FactureFournisseurRec($this->db);
431
            $result = $_facrec->fetch($this->fac_rec);
432
            $result = $_facrec->fetchObjectLinked(null, '', null, '', 'OR', 1, 'sourcetype', 0); // This load $_facrec->linkedObjectsIds
433
434
            // Define some dates
435
            if (!empty($_facrec->frequency)) {
436
                $originaldatewhen = $_facrec->date_when;
437
                $nextdatewhen = dol_time_plus_duree($originaldatewhen, $_facrec->frequency, $_facrec->unit_frequency);
438
                $previousdaynextdatewhen = dol_time_plus_duree($nextdatewhen, -1, 'd');
439
                $this->socid = $_facrec->socid;
440
            } else {
441
                $originaldatewhen = 0;
442
                $nextdatewhen = 0;
443
                $previousdaynextdatewhen = 0;
444
            }
445
446
            $this->entity = $_facrec->entity; // Invoice created in same entity than template
447
448
            // Fields coming from GUI
449
            // @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
450
            // set by posted page with $object->xxx = ... and this section should be removed.
451
            $this->fk_project = GETPOSTINT('projectid') > 0 ? (GETPOSTINT('projectid')) : $_facrec->fk_project;
452
            $this->note_public = GETPOST('note_public', 'restricthtml') ? GETPOST('note_public', 'restricthtml') : $_facrec->note_public;
0 ignored issues
show
Documentation Bug introduced by
It seems like GETPOST('note_public', '...: $_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...
453
            $this->note_private = GETPOST('note_private', 'restricthtml') ? GETPOST('note_private', 'restricthtml') : $_facrec->note_private;
0 ignored issues
show
Documentation Bug introduced by
It seems like GETPOST('note_private', ... $_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...
454
            $this->model_pdf = GETPOST('model', 'alpha') ? GETPOST('model', 'alpha') : $_facrec->model_pdf;
0 ignored issues
show
Documentation Bug introduced by
It seems like GETPOST('model', 'alpha'...) : $_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...
455
            $this->cond_reglement_id = GETPOSTINT('cond_reglement_id') > 0 ? (GETPOSTINT('cond_reglement_id')) : $_facrec->cond_reglement_id;
456
            $this->mode_reglement_id = GETPOSTINT('mode_reglement_id') > 0 ? (GETPOSTINT('mode_reglement_id')) : $_facrec->mode_reglement_id;
457
            $this->fk_account = GETPOST('fk_account') > 0 ? ((int) GETPOST('fk_account')) : $_facrec->fk_account;
458
459
            // Set here to have this defined for substitution into notes, should be recalculated after adding lines to get same result
460
            $this->total_ht = $_facrec->total_ht;
461
            $this->total_ttc = $_facrec->total_ttc;
462
463
            // Fields always coming from template
464
            $this->fk_incoterms = $_facrec->fk_incoterms;
465
            $this->location_incoterms = $_facrec->location_incoterms;
466
467
            // Clean parameters
468
            if (! $this->type) {
469
                $this->type = self::TYPE_STANDARD;
470
            }
471
            if (!empty(GETPOST('ref_supplier'))) {
472
                $this->ref_supplier = trim($this->ref_supplier);
473
            } else {
474
                $this->ref_supplier = trim($this->ref_supplier . '_' . ($_facrec->nb_gen_done + 1));
475
            }
476
            $this->note_public = trim($this->note_public);
477
            $this->note_private = trim($this->note_private);
478
            $this->note_private = dol_concatdesc($this->note_private, $langs->trans("GeneratedFromRecurringInvoice", $_facrec->title));
479
480
            $this->array_options = $_facrec->array_options;
481
482
            if (! $this->mode_reglement_id) {
483
                $this->mode_reglement_id = 0;
484
            }
485
            $this->status = self::STATUS_DRAFT;
486
            $this->statut = self::STATUS_DRAFT; // deprecated
487
488
            $this->linked_objects = $_facrec->linkedObjectsIds;
489
            // We do not add link to template invoice or next invoice will be linked to all generated invoices
490
            //$this->linked_objects['facturerec'][0] = $this->fac_rec;
491
492
            $forceduedate = $this->calculate_date_lim_reglement();
493
494
            // For recurring invoices, update date and number of last generation of recurring template invoice, before inserting new invoice
495
            if ($_facrec->frequency > 0) {
496
                dol_syslog("This is a recurring invoice so we set date_last_gen and next date_when");
497
                if (empty($_facrec->date_when)) {
498
                    $_facrec->date_when = $now;
499
                }
500
                $next_date = $_facrec->getNextDate(); // Calculate next date
501
                $result = $_facrec->setValueFrom('date_last_gen', $now, '', 0, 'date', '', $user, '');
502
                //$_facrec->setValueFrom('nb_gen_done', $_facrec->nb_gen_done + 1);     // Not required, +1 already included into setNextDate when second param is 1.
503
                $result = $_facrec->setNextDate($next_date, 1);
504
            }
505
506
            // Define lang of customer
507
            $outputlangs = $langs;
508
            $newlang = '';
509
510
            if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang) && isset($this->thirdparty->default_lang)) {
511
                $newlang = $this->thirdparty->default_lang; // for proposal, order, invoice, ...
512
            }
513
            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\Fourn\Classes\FactureFournisseur. Since you implemented __get, consider adding a @property annotation.
Loading history...
514
                $newlang = $this->default_lang; // for thirdparty
515
            }
516
            if (!empty($newlang)) {
517
                $outputlangs = new Translate("", $conf);
518
                $outputlangs->setDefaultLang($newlang);
519
            } // Array of possible substitutions (See also file mailing-send.php that should manage same substitutions)
520
            $substitutionarray = getCommonSubstitutionArray($outputlangs, 0, null, $this);
521
            $substitutionarray['__INVOICE_PREVIOUS_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%m');
522
            $substitutionarray['__INVOICE_MONTH__'] = dol_print_date($this->date, '%m');
523
            $substitutionarray['__INVOICE_NEXT_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%m');
524
            $substitutionarray['__INVOICE_PREVIOUS_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%B');
525
            $substitutionarray['__INVOICE_MONTH_TEXT__'] = dol_print_date($this->date, '%B');
526
            $substitutionarray['__INVOICE_NEXT_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%B');
527
            $substitutionarray['__INVOICE_PREVIOUS_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'y'), '%Y');
528
            $substitutionarray['__INVOICE_YEAR__'] = dol_print_date($this->date, '%Y');
529
            $substitutionarray['__INVOICE_NEXT_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'y'), '%Y'); // Only for template invoice
530
            $substitutionarray['__INVOICE_DATE_NEXT_INVOICE_BEFORE_GEN__'] = $originaldatewhen ? dol_print_date($originaldatewhen, 'dayhour') : '';
531
            $substitutionarray['__INVOICE_DATE_NEXT_INVOICE_AFTER_GEN__'] = $nextdatewhen ? dol_print_date($nextdatewhen, 'dayhour') : '';
532
            $substitutionarray['__INVOICE_PREVIOUS_DATE_NEXT_INVOICE_AFTER_GEN__'] = $previousdaynextdatewhen ? dol_print_date($previousdaynextdatewhen, 'dayhour') : '';
533
            $substitutionarray['__INVOICE_COUNTER_CURRENT__'] = $_facrec->nb_gen_done;
534
            $substitutionarray['__INVOICE_COUNTER_MAX__'] = $_facrec->nb_gen_max;
535
536
            complete_substitutions_array($substitutionarray, $outputlangs);
537
538
            $this->note_public = make_substitutions($this->note_public, $substitutionarray);
539
            $this->note_private = make_substitutions($this->note_private, $substitutionarray);
540
        }
541
542
        // Define due date if not already defined
543
        if (!empty($forceduedate)) {
544
            $this->date_echeance = $forceduedate;
545
        }
546
547
        $sql = "INSERT INTO " . MAIN_DB_PREFIX . "facture_fourn (";
548
        $sql .= "ref";
549
        $sql .= ", ref_supplier";
550
        $sql .= ", ref_ext";
551
        $sql .= ", entity";
552
        $sql .= ", type";
553
        $sql .= ", subtype";
554
        $sql .= ", libelle";
555
        $sql .= ", fk_soc";
556
        $sql .= ", datec";
557
        $sql .= ", datef";
558
        $sql .= ", vat_reverse_charge";
559
        $sql .= ", fk_projet";
560
        $sql .= ", fk_cond_reglement";
561
        $sql .= ", fk_mode_reglement";
562
        $sql .= ", fk_account";
563
        $sql .= ", note_private";
564
        $sql .= ", note_public";
565
        $sql .= ", fk_user_author";
566
        $sql .= ", date_lim_reglement";
567
        $sql .= ", fk_incoterms, location_incoterms";
568
        $sql .= ", fk_multicurrency";
569
        $sql .= ", multicurrency_code";
570
        $sql .= ", multicurrency_tx";
571
        $sql .= ", fk_facture_source";
572
        $sql .= ", fk_fac_rec_source";
573
        $sql .= ")";
574
        $sql .= " VALUES (";
575
        $sql .= "'(PROV)'";
576
        $sql .= ", '" . $this->db->escape($this->ref_supplier) . "'";
577
        $sql .= ", '" . $this->db->escape($this->ref_ext) . "'";
578
        $sql .= ", " . ((int) $conf->entity);
579
        $sql .= ", '" . $this->db->escape($this->type) . "'";
580
        $sql .= ", " . ((int) $this->subtype);
581
        $sql .= ", '" . $this->db->escape(isset($this->label) ? $this->label : (isset($this->libelle) ? $this->libelle : '')) . "'";
582
        $sql .= ", " . ((int) $this->socid);
583
        $sql .= ", '" . $this->db->idate($now) . "'";
584
        $sql .= ", '" . $this->db->idate($this->date) . "'";
585
        $sql .= ", " . ($this->vat_reverse_charge != '' ? ((int) $this->db->escape($this->vat_reverse_charge)) : 0);
586
        $sql .= ", " . ($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
587
        $sql .= ", " . ($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : "null");
588
        $sql .= ", " . ($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : "null");
589
        $sql .= ", " . ($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
590
        $sql .= ", '" . $this->db->escape($this->note_private) . "'";
591
        $sql .= ", '" . $this->db->escape($this->note_public) . "'";
592
        $sql .= ", " . ((int) $user->id) . ",";
593
        $sql .= $this->date_echeance != '' ? "'" . $this->db->idate($this->date_echeance) . "'" : "null";
594
        $sql .= ", " . (int) $this->fk_incoterms;
595
        $sql .= ", '" . $this->db->escape($this->location_incoterms) . "'";
596
        $sql .= ", " . (int) $this->fk_multicurrency;
597
        $sql .= ", '" . $this->db->escape($this->multicurrency_code) . "'";
598
        $sql .= ", " . (float) $this->multicurrency_tx;
599
        $sql .= ", " . ($this->fk_facture_source ? ((int) $this->fk_facture_source) : "null");
600
        $sql .= ", " . (isset($this->fk_fac_rec_source) ? $this->fk_fac_rec_source : "NULL");
601
        $sql .= ")";
602
603
        dol_syslog(get_only_class($this) . "::create", LOG_DEBUG);
604
        $resql = $this->db->query($sql);
605
        if ($resql) {
606
            $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX . 'facture_fourn');
607
608
            // Update ref with new one
609
            $this->ref = '(PROV' . $this->id . ')';
610
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . "facture_fourn SET ref='" . $this->db->escape($this->ref) . "' WHERE rowid=" . ((int) $this->id);
611
612
            dol_syslog(get_only_class($this) . "::create", LOG_DEBUG);
613
            $resql = $this->db->query($sql);
614
            if (!$resql) {
615
                $error++;
616
            }
617
618
            if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) {  // To use new linkedObjectsIds instead of old linked_objects
619
                $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
620
            }
621
622
            // Add object linked
623
            if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
624
                foreach ($this->linked_objects as $origin => $tmp_origin_id) {
625
                    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, ...))
626
                        foreach ($tmp_origin_id as $origin_id) {
627
                            $ret = $this->add_object_linked($origin, $origin_id);
628
                            if (!$ret) {
629
                                dol_print_error($this->db);
630
                                $error++;
631
                            }
632
                        }
633
                    } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
634
                        $origin_id = $tmp_origin_id;
635
                        $ret = $this->add_object_linked($origin, $origin_id);
636
                        if (!$ret) {
637
                            dol_print_error($this->db);
638
                            $error++;
639
                        }
640
                    }
641
                }
642
            }
643
644
            if (!$error && empty($this->fac_rec) && count($this->lines) && is_object($this->lines[0])) {    // If this->lines is array of InvoiceLines (preferred mode)
645
                dol_syslog("There is " . count($this->lines) . " lines that are invoice lines objects");
646
                foreach ($this->lines as $i => $val) {
647
                    $sql = 'INSERT INTO ' . MAIN_DB_PREFIX . 'facture_fourn_det (fk_facture_fourn, special_code, fk_remise_except)';
648
                    $sql .= " VALUES (" . ((int) $this->id) . ", " . ((int) $this->lines[$i]->special_code) . ", " . ($this->lines[$i]->fk_remise_except > 0 ? ((int) $this->lines[$i]->fk_remise_except) : 'NULL') . ')';
0 ignored issues
show
Bug Best Practice introduced by
The property fk_remise_except does not exist on Dolibarr\Code\Core\Classes\CommonInvoiceLine. Since you implemented __get, consider adding a @property annotation.
Loading history...
649
650
                    $resql_insert = $this->db->query($sql);
651
                    if ($resql_insert) {
652
                        $idligne = $this->db->last_insert_id(MAIN_DB_PREFIX . 'facture_fourn_det');
653
654
                        $res = $this->updateline(
655
                            $idligne,
656
                            $this->lines[$i]->desc ? $this->lines[$i]->desc : $this->lines[$i]->description,
657
                            $this->lines[$i]->subprice,
658
                            $this->lines[$i]->tva_tx . ($this->lines[$i]->vat_src_code ? ' (' . $this->lines[$i]->vat_src_code . ')' : ''),
659
                            $this->lines[$i]->localtax1_tx,
660
                            $this->lines[$i]->localtax2_tx,
661
                            $this->lines[$i]->qty,
662
                            $this->lines[$i]->fk_product,
663
                            'HT',
664
                            (!empty($this->lines[$i]->info_bits) ? $this->lines[$i]->info_bits : ''),
665
                            $this->lines[$i]->product_type,
666
                            $this->lines[$i]->remise_percent,
667
                            false,
668
                            $this->lines[$i]->date_start,
0 ignored issues
show
Bug Best Practice introduced by
The property date_start does not exist on Dolibarr\Code\Core\Classes\CommonInvoiceLine. Since you implemented __get, consider adding a @property annotation.
Loading history...
669
                            $this->lines[$i]->date_end,
0 ignored issues
show
Bug Best Practice introduced by
The property date_end does not exist on Dolibarr\Code\Core\Classes\CommonInvoiceLine. Since you implemented __get, consider adding a @property annotation.
Loading history...
670
                            $this->lines[$i]->array_options,
671
                            $this->lines[$i]->fk_unit,
672
                            $this->lines[$i]->multicurrency_subprice,
673
                            $this->lines[$i]->ref_supplier
0 ignored issues
show
Bug Best Practice introduced by
The property ref_supplier does not exist on Dolibarr\Code\Core\Classes\CommonInvoiceLine. Since you implemented __get, consider adding a @property annotation.
Loading history...
674
                        );
675
                    } else {
676
                        $this->error = $this->db->lasterror();
677
                        $this->db->rollback();
678
                        return -5;
679
                    }
680
                }
681
            } elseif (!$error && empty($this->fac_rec)) {   // If this->lines is an array of invoice line arrays
682
                dol_syslog("There is " . count($this->lines) . " lines that are array lines");
683
                foreach ($this->lines as $i => $val) {
684
                    $line = $this->lines[$i];
685
686
                    // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
687
                    //if (! is_object($line)) $line=json_decode(json_encode($line), false);  // convert recursively array into object.
688
                    if (!is_object($line)) {
689
                        $line = (object) $line;
690
                    }
691
692
                    $sql = 'INSERT INTO ' . MAIN_DB_PREFIX . 'facture_fourn_det (fk_facture_fourn, special_code, fk_remise_except)';
693
                    $sql .= " VALUES (" . ((int) $this->id) . ", " . ((int) $this->lines[$i]->special_code) . ", " . ($this->lines[$i]->fk_remise_except > 0 ? ((int) $this->lines[$i]->fk_remise_except) : 'NULL') . ')';
694
695
                    $resql_insert = $this->db->query($sql);
696
                    if ($resql_insert) {
697
                        $idligne = $this->db->last_insert_id(MAIN_DB_PREFIX . 'facture_fourn_det');
698
699
                        $this->updateline(
700
                            $idligne,
701
                            $line->desc ? $line->desc : $line->description,
702
                            $line->subprice,
703
                            $line->tva_tx,
704
                            $line->localtax1_tx,
705
                            $line->localtax2_tx,
706
                            $line->qty,
707
                            $line->fk_product,
708
                            'HT',
709
                            (!empty($line->info_bits) ? $line->info_bits : ''),
710
                            $line->product_type,
711
                            $line->remise_percent,
712
                            0,
713
                            $line->date_start,
714
                            $line->date_end,
715
                            $line->array_options,
716
                            $line->fk_unit,
717
                            $line->multicurrency_subprice,
718
                            $line->ref_supplier
719
                        );
720
                    } else {
721
                        $this->error = $this->db->lasterror();
722
                        $this->db->rollback();
723
                        return -5;
724
                    }
725
                }
726
            }
727
728
            /*
729
             * Insert lines of template invoices
730
             */
731
            if (! $error && $this->fac_rec > 0 && $_facrec instanceof FactureFournisseurRec) {
732
                foreach ($_facrec->lines as $i => $val) {
733
                    if ($_facrec->lines[$i]->fk_product) {
734
                        $prod = new Product($this->db);
735
                        $res = $prod->fetch($_facrec->lines[$i]->fk_product);
736
                    }
737
738
                    // For line from template invoice, we use data from template invoice
739
                    /*
740
                    $tva_tx = get_default_tva($mysoc,$soc,$prod->id);
741
                    $tva_npr = get_default_npr($mysoc,$soc,$prod->id);
742
                    if (empty($tva_tx)) $tva_npr=0;
743
                    $localtax1_tx=get_localtax($tva_tx,1,$soc,$mysoc,$tva_npr);
744
                    $localtax2_tx=get_localtax($tva_tx,2,$soc,$mysoc,$tva_npr);
745
                    */
746
                    $tva_tx = $_facrec->lines[$i]->tva_tx . ($_facrec->lines[$i]->vat_src_code ? '(' . $_facrec->lines[$i]->vat_src_code . ')' : '');
747
                    $tva_npr = $_facrec->lines[$i]->info_bits;
748
                    if (empty($tva_tx)) {
749
                        $tva_npr = 0;
750
                    }
751
                    $localtax1_tx = $_facrec->lines[$i]->localtax1_tx;
752
                    $localtax2_tx = $_facrec->lines[$i]->localtax2_tx;
753
754
                    $fk_product_fournisseur_price = empty($_facrec->lines[$i]->fk_product_fournisseur_price) ? null : $_facrec->lines[$i]->fk_product_fournisseur_price;
755
                    $buyprice = empty($_facrec->lines[$i]->buyprice) ? 0 : $_facrec->lines[$i]->buyprice;
756
757
                    // If buyprice not defined from template invoice, we try to guess the best value
758
                    if (! $buyprice && $_facrec->lines[$i]->fk_product > 0) {
759
                        $producttmp = new ProductFournisseur($this->db);
760
                        $producttmp->fetch($_facrec->lines[$i]->fk_product);
761
762
                        // If margin module defined on costprice, we try the costprice
763
                        // If not defined or if module margin defined and pmp and stock module enabled, we try pmp price
764
                        // else we get the best supplier price
765
                        if (getDolGlobalString('MARGIN_TYPE') == 'costprice' && !empty($producttmp->cost_price)) {
766
                            $buyprice = $producttmp->cost_price;
767
                        } elseif (isModEnabled('stock') && (getDolGlobalString('MARGIN_TYPE') == 'costprice' || getDolGlobalString('MARGIN_TYPE') == 'pmp') && !empty($producttmp->pmp)) {
768
                            $buyprice = $producttmp->pmp;
769
                        } else {
770
                            if ($producttmp->find_min_price_product_fournisseur($_facrec->lines[$i]->fk_product) > 0) {
771
                                if ($producttmp->product_fourn_price_id > 0) {
772
                                    $buyprice = price2num($producttmp->fourn_unitprice * (1 - $producttmp->fourn_remise_percent / 100) + $producttmp->fourn_remise, 'MU');
773
                                }
774
                            }
775
                        }
776
                    }
777
778
                    $result_insert = $this->addline(
779
                        $_facrec->lines[$i]->desc ? $_facrec->lines[$i]->desc : $_facrec->lines[$i]->description,
780
                        $_facrec->lines[$i]->pu_ht,
781
                        $tva_tx,
782
                        $localtax1_tx,
783
                        $localtax2_tx,
784
                        $_facrec->lines[$i]->qty,
785
                        $_facrec->lines[$i]->fk_product,
786
                        $_facrec->lines[$i]->remise_percent,
787
                        ($_facrec->lines[$i]->date_start == 1 && $this->date) ? $this->date : '',
788
                        ($_facrec->lines[$i]->date_end == 1 && $previousdaynextdatewhen) ? $previousdaynextdatewhen : '',
789
                        0,
790
                        $_facrec->lines[$i]->info_bits,
791
                        'HT',
792
                        0,
793
                        $_facrec->lines[$i]->rang,
794
                        false,
795
                        $_facrec->lines[$i]->array_options,
796
                        $_facrec->lines[$i]->fk_unit,
797
                        0,
798
                        0,
799
                        $_facrec->lines[$i]->ref_supplier,
800
                        $_facrec->lines[$i]->special_code,
801
                        0,
802
                        0
803
                    );
804
                    if ($result_insert < 0) {
805
                        $error++;
806
                        $this->error = $this->db->error();
807
                        break;
808
                    }
809
                }
810
            }
811
812
813
            // Update total price
814
            $result = $this->update_price(1);
815
            if ($result > 0) {
816
                // Actions on extra fields
817
                if (!$error) {
818
                    $result = $this->insertExtraFields(); // This also set $this->error or $this->errors if errors are found
819
                    if ($result < 0) {
820
                        $error++;
821
                    }
822
                }
823
824
                if (!$error) {
825
                    // Call trigger
826
                    $result = $this->call_trigger('BILL_SUPPLIER_CREATE', $user);
827
                    if ($result < 0) {
828
                        $error++;
829
                    }
830
                    // End call triggers
831
                }
832
833
                if (!$error) {
834
                    $this->db->commit();
835
                    return $this->id;
836
                } else {
837
                    $this->db->rollback();
838
                    return -4;
839
                }
840
            } else {
841
                $this->error = $langs->trans('FailedToUpdatePrice');
842
                $this->db->rollback();
843
                return -3;
844
            }
845
        } else {
846
            if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
847
                $this->error = $langs->trans('ErrorRefAlreadyExists');
848
                $this->db->rollback();
849
                return -1;
850
            } else {
851
                $this->error = $this->db->lasterror();
852
                $this->db->rollback();
853
                return -2;
854
            }
855
        }
856
    }
857
858
    /**
859
     *  Load object in memory from database
860
     *
861
     *  @param  int     $id         Id supplier invoice
862
     *  @param  string  $ref        Ref supplier invoice
863
     *  @param  string  $ref_ext    External reference of invoice
864
     *  @return int                 Return integer <0 if KO, >0 if OK, 0 if not found
865
     */
866
    public function fetch($id = 0, $ref = '', $ref_ext = '')
867
    {
868
        if (empty($id) && empty($ref) && empty($ref_ext)) {
869
            return -1;
870
        }
871
872
        $sql = "SELECT";
873
        $sql .= " t.rowid,";
874
        $sql .= " t.ref,";
875
        $sql .= " t.ref_supplier,";
876
        $sql .= " t.ref_ext,";
877
        $sql .= " t.entity,";
878
        $sql .= " t.type,";
879
        $sql .= " t.subtype,";
880
        $sql .= " t.fk_soc,";
881
        $sql .= " t.datec,";
882
        $sql .= " t.datef,";
883
        $sql .= " t.tms,";
884
        $sql .= " t.libelle as label,";
885
        $sql .= " t.paye,";
886
        $sql .= " t.close_code,";
887
        $sql .= " t.close_note,";
888
        $sql .= " t.tva,";
889
        $sql .= " t.localtax1,";
890
        $sql .= " t.localtax2,";
891
        $sql .= " t.total_ht,";
892
        $sql .= " t.total_tva,";
893
        $sql .= " t.total_ttc,";
894
        $sql .= " t.fk_statut as status,";
895
        $sql .= " t.fk_user_author,";
896
        $sql .= " t.fk_user_valid,";
897
        $sql .= " t.fk_facture_source,";
898
        $sql .= " t.vat_reverse_charge,";
899
        $sql .= " t.fk_fac_rec_source,";
900
        $sql .= " t.fk_projet as fk_project,";
901
        $sql .= " t.fk_cond_reglement,";
902
        $sql .= " t.fk_account,";
903
        $sql .= " t.fk_mode_reglement,";
904
        $sql .= " t.date_lim_reglement,";
905
        $sql .= " t.note_private,";
906
        $sql .= " t.note_public,";
907
        $sql .= " t.model_pdf,";
908
        $sql .= " t.last_main_doc,";
909
        $sql .= " t.import_key,";
910
        $sql .= " t.extraparams,";
911
        $sql .= " cr.code as cond_reglement_code, cr.libelle as cond_reglement_label, cr.libelle_facture as cond_reglement_doc,";
912
        $sql .= " p.code as mode_reglement_code, p.libelle as mode_reglement_label,";
913
        $sql .= ' s.nom as socnom, s.rowid as socid,';
914
        $sql .= ' t.fk_incoterms, t.location_incoterms,';
915
        $sql .= " i.libelle as label_incoterms,";
916
        $sql .= ' t.fk_transport_mode,';
917
        $sql .= ' t.fk_multicurrency, t.multicurrency_code, t.multicurrency_tx, t.multicurrency_total_ht, t.multicurrency_total_tva, t.multicurrency_total_ttc';
918
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'facture_fourn as t';
919
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON (t.fk_soc = s.rowid)";
920
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "c_payment_term as cr ON t.fk_cond_reglement = cr.rowid";
921
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "c_paiement as p ON t.fk_mode_reglement = p.id";
922
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_incoterms as i ON t.fk_incoterms = i.rowid';
923
        if ($id) {
924
            $sql .= " WHERE t.rowid = " . ((int) $id);
925
        } else {
926
            $sql .= ' WHERE t.entity IN (' . getEntity('supplier_invoice') . ')'; // Don't use entity if you use rowid
927
            if ($ref) {
928
                $sql .= " AND t.ref = '" . $this->db->escape($ref) . "'";
929
            }
930
            if ($ref_ext) {
931
                $sql .= " AND t.ref_ext = '" . $this->db->escape($ref_ext) . "'";
932
            }
933
        }
934
935
        dol_syslog(get_only_class($this) . "::fetch", LOG_DEBUG);
936
        $resql = $this->db->query($sql);
937
        if ($resql) {
938
            if ($this->db->num_rows($resql)) {
939
                $obj = $this->db->fetch_object($resql);
940
941
                $this->id = $obj->rowid;
942
                $this->ref = $obj->ref ? $obj->ref : $obj->rowid; // We take rowid if ref is empty for backward compatibility
943
944
                $this->ref_supplier         = $obj->ref_supplier;
945
                $this->ref_ext              = $obj->ref_ext;
946
                $this->entity               = $obj->entity;
947
                $this->type                 = empty($obj->type) ? self::TYPE_STANDARD : $obj->type;
948
                $this->subtype              = (int) $obj->subtype;
949
                $this->socid                = $obj->fk_soc;
950
                $this->datec                = $this->db->jdate($obj->datec);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->db->jdate($obj->datec) can also be of type string. However, the property $datec 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...
951
                $this->date                 = $this->db->jdate($obj->datef);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->db->jdate($obj->datef) 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...
952
                //$this->datep              = $this->db->jdate($obj->datef);
953
                $this->tms                  = $this->db->jdate($obj->tms);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->db->jdate($obj->tms) can also be of type string. However, the property $tms 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...
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$tms has been deprecated: Use $date_modification ( Ignorable by Annotation )

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

953
                /** @scrutinizer ignore-deprecated */ $this->tms                  = $this->db->jdate($obj->tms);

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...
954
                $this->libelle              = $obj->label; // deprecated
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Fourn\Clas...reFournisseur::$libelle has been deprecated: Use $label ( Ignorable by Annotation )

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

954
                /** @scrutinizer ignore-deprecated */ $this->libelle              = $obj->label; // 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...
955
                $this->label                = $obj->label;
956
                $this->paye                 = $obj->paye;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Fourn\Clas...ctureFournisseur::$paye has been deprecated: Use $paid ( Ignorable by Annotation )

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

956
                /** @scrutinizer ignore-deprecated */ $this->paye                 = $obj->paye;

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...
957
                $this->paid                 = $obj->paye;
958
                $this->close_code           = $obj->close_code;
959
                $this->close_note           = $obj->close_note;
960
                $this->total_localtax1      = $obj->localtax1;
961
                $this->total_localtax2      = $obj->localtax2;
962
                $this->total_ht             = $obj->total_ht;
963
                $this->total_tva            = $obj->total_tva;
964
                $this->total_ttc            = $obj->total_ttc;
965
                $this->status               = $obj->status;
966
                $this->statut               = $obj->status; // For backward compatibility
967
                $this->fk_statut            = $obj->status; // For backward compatibility
968
                $this->user_creation_id     = $obj->fk_user_author;
969
                $this->author               = $obj->fk_user_author; // deprecated
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Fourn\Clas...ureFournisseur::$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

969
                /** @scrutinizer ignore-deprecated */ $this->author               = $obj->fk_user_author; // 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...
970
                $this->user_validation_id   = $obj->fk_user_valid;
971
                $this->fk_facture_source    = $obj->fk_facture_source;
972
                $this->vat_reverse_charge   = empty($obj->vat_reverse_charge) ? 0 : 1;
973
                $this->fk_fac_rec_source    = $obj->fk_fac_rec_source;
974
                $this->fk_project           = $obj->fk_project;
975
                $this->cond_reglement_id    = $obj->fk_cond_reglement;
976
                $this->cond_reglement_code  = $obj->cond_reglement_code;
977
                $this->cond_reglement       = $obj->cond_reglement_label; // deprecated
0 ignored issues
show
Bug Best Practice introduced by
The property $cond_reglement is declared private in Dolibarr\Core\Base\CommonObject. Since you implement __set, consider adding a @property or @property-write.
Loading history...
Deprecated Code introduced by
The property Dolibarr\Core\Base\CommonObject::$cond_reglement has been deprecated: Use $cond_reglement_id instead - Kept for compatibility ( Ignorable by Annotation )

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

977
                /** @scrutinizer ignore-deprecated */ $this->cond_reglement       = $obj->cond_reglement_label; // 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...
978
                $this->cond_reglement_label = $obj->cond_reglement_label;
979
                $this->cond_reglement_doc   = $obj->cond_reglement_doc;
980
                $this->fk_account           = $obj->fk_account;
981
                $this->mode_reglement_id    = $obj->fk_mode_reglement;
982
                $this->mode_reglement_code  = $obj->mode_reglement_code;
983
                $this->mode_reglement       = $obj->mode_reglement_label;
984
                $this->date_echeance        = $this->db->jdate($obj->date_lim_reglement);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->db->jdate($obj->date_lim_reglement) can also be of type string. However, the property $date_echeance 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...
985
                $this->note                 = $obj->note_private; // deprecated
986
                $this->note_private         = $obj->note_private;
987
                $this->note_public          = $obj->note_public;
988
                $this->model_pdf            = $obj->model_pdf;
989
                $this->last_main_doc = $obj->last_main_doc;
990
                $this->import_key           = $obj->import_key;
991
992
                //Incoterms
993
                $this->fk_incoterms = $obj->fk_incoterms;
994
                $this->location_incoterms = $obj->location_incoterms;
995
                $this->label_incoterms = $obj->label_incoterms;
996
                $this->transport_mode_id = $obj->fk_transport_mode;
997
998
                // Multicurrency
999
                $this->fk_multicurrency = $obj->fk_multicurrency;
1000
                $this->multicurrency_code = $obj->multicurrency_code;
1001
                $this->multicurrency_tx = $obj->multicurrency_tx;
1002
                $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
1003
                $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
1004
                $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1005
1006
                $this->extraparams = isset($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
1007
1008
                $this->socid  = $obj->socid;
1009
1010
                // Retrieve all extrafield
1011
                // fetch optionals attributes and labels
1012
                $this->fetch_optionals();
1013
1014
                $result = $this->fetch_lines();
1015
                if ($result < 0) {
1016
                    $this->error = $this->db->lasterror();
1017
                    return -3;
1018
                }
1019
            } else {
1020
                $this->error = 'Bill with id ' . $id . ' not found';
1021
                dol_syslog(get_only_class($this) . '::fetch ' . $this->error);
1022
                return 0;
1023
            }
1024
1025
            $this->db->free($resql);
1026
            return 1;
1027
        } else {
1028
            $this->error = "Error " . $this->db->lasterror();
1029
            return -1;
1030
        }
1031
    }
1032
1033
1034
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1035
    /**
1036
     *  Load this->lines
1037
     *
1038
     *  @return     int         1 si ok, < 0 si erreur
1039
     */
1040
    public function fetch_lines()
1041
    {
1042
		// phpcs:enable
1043
        $this->lines = array();
1044
1045
        $sql = 'SELECT f.rowid, f.ref as ref_supplier, f.description as line_desc, f.date_start, f.date_end, f.pu_ht, f.pu_ttc, f.qty, f.remise_percent, f.vat_src_code, f.tva_tx';
1046
        $sql .= ', f.localtax1_tx, f.localtax2_tx, f.localtax1_type, f.localtax2_type, f.total_localtax1, f.total_localtax2, f.fk_facture_fourn, f.fk_remise_except';
1047
        $sql .= ', f.total_ht, f.tva as total_tva, f.total_ttc, f.fk_product, f.product_type, f.info_bits, f.rang, f.special_code, f.fk_parent_line, f.fk_unit';
1048
        $sql .= ', p.rowid as product_id, p.ref as product_ref, p.label as label, p.barcode as product_barcode, p.description as product_desc';
1049
        $sql .= ', f.fk_code_ventilation, f.fk_multicurrency, f.multicurrency_code, f.multicurrency_subprice, f.multicurrency_total_ht, f.multicurrency_total_tva, f.multicurrency_total_ttc';
1050
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'facture_fourn_det as f';
1051
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'product as p ON f.fk_product = p.rowid';
1052
        $sql .= ' WHERE fk_facture_fourn=' . ((int) $this->id);
1053
        $sql .= ' ORDER BY f.rang, f.rowid';
1054
1055
        dol_syslog(get_only_class($this) . "::fetch_lines", LOG_DEBUG);
1056
1057
        $resql_rows = $this->db->query($sql);
1058
        if ($resql_rows) {
1059
            $num_rows = $this->db->num_rows($resql_rows);
1060
            if ($num_rows) {
1061
                $i = 0;
1062
                while ($i < $num_rows) {
1063
                    $obj = $this->db->fetch_object($resql_rows);
1064
1065
                    $line = new SupplierInvoiceLine($this->db);
1066
1067
                    $line->id               = $obj->rowid;
1068
                    $line->rowid            = $obj->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

1068
                    /** @scrutinizer ignore-deprecated */ $line->rowid            = $obj->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...
1069
                    $line->description      = $obj->line_desc;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Fourn\Clas...voiceLine::$description has been deprecated: Use $desc ( Ignorable by Annotation )

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

1069
                    /** @scrutinizer ignore-deprecated */ $line->description      = $obj->line_desc;

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...
1070
                    $line->desc             = $obj->line_desc;
1071
                    $line->date_start       = $obj->date_start;
1072
                    $line->date_end         = $obj->date_end;
1073
                    $line->product_ref      = $obj->product_ref;
1074
                    $line->ref              = $obj->product_ref;
1075
                    $line->ref_supplier     = $obj->ref_supplier;
1076
                    $line->libelle          = $obj->label;
0 ignored issues
show
Bug Best Practice introduced by
The property libelle does not exist on Dolibarr\Code\Fourn\Classes\SupplierInvoiceLine. Since you implemented __set, consider adding a @property annotation.
Loading history...
1077
                    $line->label            = $obj->label;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Fourn\Clas...lierInvoiceLine::$label has been deprecated: Use $product_label ( Ignorable by Annotation )

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

1077
                    /** @scrutinizer ignore-deprecated */ $line->label            = $obj->label;

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...
1078
                    $line->product_barcode  = $obj->product_barcode;
1079
                    $line->product_desc     = $obj->product_desc;
1080
                    $line->subprice         = $obj->pu_ht;
1081
                    $line->pu_ht            = $obj->pu_ht;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Fourn\Clas...lierInvoiceLine::$pu_ht has been deprecated: Use $subprice ( Ignorable by Annotation )

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

1081
                    /** @scrutinizer ignore-deprecated */ $line->pu_ht            = $obj->pu_ht;

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...
1082
                    $line->pu_ttc           = $obj->pu_ttc;
1083
                    $line->vat_src_code     = $obj->vat_src_code;
1084
                    $line->tva_tx           = $obj->tva_tx;
1085
                    $line->localtax1_tx     = $obj->localtax1_tx;
1086
                    $line->localtax2_tx     = $obj->localtax2_tx;
1087
                    $line->localtax1_type   = $obj->localtax1_type;
1088
                    $line->localtax2_type   = $obj->localtax2_type;
1089
                    $line->qty              = $obj->qty;
1090
                    $line->remise_percent   = $obj->remise_percent;
1091
                    $line->fk_remise_except = $obj->fk_remise_except;
1092
                    //$line->tva            = $obj->total_tva; // deprecated
1093
                    $line->total_ht         = $obj->total_ht;
1094
                    $line->total_ttc        = $obj->total_ttc;
1095
                    $line->total_tva        = $obj->total_tva;
1096
                    $line->total_localtax1  = $obj->total_localtax1;
1097
                    $line->total_localtax2  = $obj->total_localtax2;
1098
                    $line->fk_facture_fourn = $obj->fk_facture_fourn;
1099
                    $line->fk_product       = $obj->fk_product;
1100
                    $line->product_type     = $obj->product_type;
1101
                    $line->product_label    = $obj->label;
1102
                    $line->info_bits        = $obj->info_bits;
1103
                    $line->fk_parent_line   = $obj->fk_parent_line;
1104
                    $line->special_code     = $obj->special_code;
1105
                    $line->rang             = $obj->rang;
1106
                    $line->fk_unit          = $obj->fk_unit;
1107
1108
                    // Accountancy
1109
                    $line->fk_accounting_account = $obj->fk_code_ventilation;
0 ignored issues
show
Bug Best Practice introduced by
The property fk_accounting_account does not exist on Dolibarr\Code\Fourn\Classes\SupplierInvoiceLine. Since you implemented __set, consider adding a @property annotation.
Loading history...
1110
1111
                    // Multicurrency
1112
                    $line->fk_multicurrency = $obj->fk_multicurrency;
1113
                    $line->multicurrency_code = $obj->multicurrency_code;
1114
                    $line->multicurrency_subprice = $obj->multicurrency_subprice;
1115
                    $line->multicurrency_total_ht = $obj->multicurrency_total_ht;
1116
                    $line->multicurrency_total_tva = $obj->multicurrency_total_tva;
1117
                    $line->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1118
1119
                    // Extra fields
1120
                    $line->fetch_optionals();
1121
1122
                    $this->lines[$i] = $line;
1123
1124
                    $i++;
1125
                }
1126
            }
1127
            $this->db->free($resql_rows);
1128
            return 1;
1129
        } else {
1130
            $this->error = $this->db->error();
1131
            dol_syslog(get_only_class($this) . "::fetch_lines - No lines:{$this->error} Error:{$this->error}", LOG_DEBUG);
1132
            return -3;
1133
        }
1134
    }
1135
1136
1137
    /**
1138
     *  Update database
1139
     *
1140
     *  @param  User    $user            User that modify
1141
     *  @param  int     $notrigger       0=launch triggers after, 1=disable triggers
1142
     *  @return int                      Return integer <0 if KO, >0 if OK
1143
     */
1144
    public function update($user = null, $notrigger = 0)
1145
    {
1146
        global $langs;
1147
        $error = 0;
1148
1149
        // Clean parameters
1150
        if (empty($this->type)) {
1151
            $this->type = self::TYPE_STANDARD;
1152
        }
1153
        if (isset($this->ref)) {
1154
            $this->ref = trim($this->ref);
1155
        }
1156
        if (isset($this->ref_supplier)) {
1157
            $this->ref_supplier = trim($this->ref_supplier);
1158
        }
1159
        if (isset($this->ref_ext)) {
1160
            $this->ref_ext = trim($this->ref_ext);
1161
        }
1162
        if (isset($this->entity)) {
1163
            $this->entity = (int) $this->entity;
1164
        }
1165
        if (isset($this->type)) {
1166
            $this->type = (int) $this->type;
1167
        }
1168
        if (isset($this->subtype)) {
1169
            $this->subtype = (int) $this->subtype;
1170
        }
1171
        if (isset($this->socid)) {
1172
            $this->socid = (int) $this->socid;
1173
        }
1174
        if (isset($this->label)) {
1175
            $this->label = trim($this->label);
1176
        }
1177
        if (isset($this->paid)) {
1178
            $this->paid = (int) (bool) $this->paye;
1179
            $this->paye = $this->paid;
1180
        } elseif (isset($this->paye)) {
1181
            $this->paid = (int) (bool) $this->paye;
1182
            $this->paye = $this->paid;
1183
        }
1184
        if (isset($this->close_code)) {
1185
            $this->close_code = trim($this->close_code);
1186
        }
1187
        if (isset($this->close_note)) {
1188
            $this->close_note = trim($this->close_note);
1189
        }
1190
        if (empty($this->total_ht)) {
1191
            $this->total_ht = 0;
1192
        }
1193
        if (empty($this->total_tva)) {
1194
            $this->total_tva = 0;
1195
        }
1196
        if (isset($this->total_ttc)) {
1197
            $this->total_ttc = (float) $this->total_ttc;
1198
        }
1199
        if (isset($this->status)) {
1200
            $this->status = (int) $this->status;
1201
            $this->statut = $this->status;
1202
        } elseif (isset($this->statut)) {
1203
            $this->status = (int) $this->statut;
1204
            $this->statut = $this->status;
1205
        }
1206
        if (isset($this->author)) {  // TODO: user_creation_id?
1207
            $this->author = (int) $this->author;
1208
        }
1209
        if (isset($this->fk_user_valid)) {
1210
            $this->fk_user_valid = trim($this->fk_user_valid);
1211
        }
1212
        if (isset($this->fk_facture_source)) {
1213
            $this->fk_facture_source = (int) $this->fk_facture_source;
1214
        }
1215
        if (isset($this->fk_project)) {
1216
            if (empty($this->fk_project)) {
1217
                $this->fk_project = 0;
1218
            } else {
1219
                $this->fk_project = (int) $this->fk_project;
1220
            }
1221
        }
1222
        if (isset($this->cond_reglement_id)) {
1223
            $this->cond_reglement_id = (int) $this->cond_reglement_id;
1224
        }
1225
        if (isset($this->note_private)) {
1226
            $this->note_private = trim($this->note_private);
1227
            $this->note = $this->note_private;
1228
        }
1229
        if (isset($this->note_public)) {
1230
            $this->note_public = trim($this->note_public);
1231
        }
1232
        if (isset($this->model_pdf)) {
1233
            $this->model_pdf = trim($this->model_pdf);
1234
        }
1235
        if (isset($this->import_key)) {
1236
            $this->import_key = trim($this->import_key);
1237
        }
1238
1239
1240
        // Check parameters
1241
        // Put here code to add control on parameters values
1242
1243
        // Update request
1244
        $sql = "UPDATE " . MAIN_DB_PREFIX . "facture_fourn SET";
1245
        $sql .= " ref=" . (isset($this->ref) ? "'" . $this->db->escape($this->ref) . "'" : "null") . ",";
1246
        $sql .= " ref_supplier=" . (isset($this->ref_supplier) ? "'" . $this->db->escape($this->ref_supplier) . "'" : "null") . ",";
1247
        $sql .= " ref_ext=" . (isset($this->ref_ext) ? "'" . $this->db->escape($this->ref_ext) . "'" : "null") . ",";
1248
        $sql .= " entity=" . (isset($this->entity) ? ((int) $this->entity) : "null") . ",";
1249
        $sql .= " type=" . (isset($this->type) ? ((int) $this->type) : "null") . ",";
1250
        $sql .= " subtype=" . ((int) $this->subtype) . ",";
1251
        $sql .= " fk_soc=" . (isset($this->socid) ? ((int) $this->socid) : "null") . ",";
1252
        $sql .= " datec=" . (dol_strlen($this->datec) != 0 ? "'" . $this->db->idate($this->datec) . "'" : 'null') . ",";
1253
        $sql .= " datef=" . (dol_strlen($this->date) != 0 ? "'" . $this->db->idate($this->date) . "'" : 'null') . ",";
1254
        if (dol_strlen($this->tms) != 0) {
1255
            $sql .= " tms=" . (dol_strlen($this->tms) != 0 ? "'" . $this->db->idate($this->tms) . "'" : 'null') . ",";
1256
        }
1257
        $sql .= " libelle=" . (isset($this->label) ? "'" . $this->db->escape($this->label) . "'" : "null") . ",";
1258
        $sql .= " paye=" . (isset($this->paid) ? ((int) $this->paid) : "0") . ",";
1259
        $sql .= " close_code=" . (isset($this->close_code) ? "'" . $this->db->escape($this->close_code) . "'" : "null") . ",";
1260
        $sql .= " close_note=" . (isset($this->close_note) ? "'" . $this->db->escape($this->close_note) . "'" : "null") . ",";
1261
        $sql .= " localtax1=" . (isset($this->total_localtax1) ? ((float) $this->total_localtax1) : "null") . ",";
1262
        $sql .= " localtax2=" . (isset($this->total_localtax2) ? ((float) $this->total_localtax2) : "null") . ",";
1263
        $sql .= " total_ht=" . (isset($this->total_ht) ? ((float) $this->total_ht) : "null") . ",";
1264
        $sql .= " total_tva=" . (isset($this->total_tva) ? ((float) $this->total_tva) : "null") . ",";
1265
        $sql .= " total_ttc=" . (isset($this->total_ttc) ? ((float) $this->total_ttc) : "null") . ",";
1266
        $sql .= " fk_statut=" . (isset($this->status) ? ((int) $this->status) : (isset($this->statut) ? ((int) $this->statut) : "null")) . ",";
1267
        $sql .= " fk_user_author=" . (isset($this->author) ? ((int) $this->author) : "null") . ",";
1268
        $sql .= " fk_user_valid=" . (isset($this->fk_user_valid) ? ((int) $this->fk_user_valid) : "null") . ",";
1269
        $sql .= " fk_facture_source=" . ($this->fk_facture_source ? ((int) $this->fk_facture_source) : "null") . ",";
1270
        $sql .= " vat_reverse_charge = " . ($this->vat_reverse_charge != '' ? ((int) $this->db->escape($this->vat_reverse_charge)) : 0) . ",";
1271
        $sql .= " fk_projet=" . (!empty($this->fk_project) ? ((int) $this->fk_project) : "null") . ",";
1272
        $sql .= " fk_cond_reglement=" . (isset($this->cond_reglement_id) ? ((int) $this->cond_reglement_id) : "null") . ",";
1273
        $sql .= " date_lim_reglement=" . (dol_strlen($this->date_echeance) != 0 ? "'" . $this->db->idate($this->date_echeance) . "'" : 'null') . ",";
1274
        $sql .= " note_private=" . (isset($this->note_private) ? "'" . $this->db->escape($this->note_private) . "'" : "null") . ",";
1275
        $sql .= " note_public=" . (isset($this->note_public) ? "'" . $this->db->escape($this->note_public) . "'" : "null") . ",";
1276
        $sql .= " model_pdf=" . (isset($this->model_pdf) ? "'" . $this->db->escape($this->model_pdf) . "'" : "null") . ",";
1277
        $sql .= " import_key=" . (isset($this->import_key) ? "'" . $this->db->escape($this->import_key) . "'" : "null");
1278
        $sql .= " WHERE rowid=" . ((int) $this->id);
1279
1280
        $this->db->begin();
1281
1282
        dol_syslog(get_only_class($this) . "::update", LOG_DEBUG);
1283
        $resql = $this->db->query($sql);
1284
1285
        if (!$resql) {
1286
            $error++;
1287
1288
            if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1289
                $this->errors[] = $langs->trans('ErrorRefAlreadyExists');
1290
            } else {
1291
                $this->errors[] = "Error " . $this->db->lasterror();
1292
            }
1293
        }
1294
1295
        if (!$error) {
1296
            $result = $this->insertExtraFields();
1297
            if ($result < 0) {
1298
                $error++;
1299
            }
1300
        }
1301
1302
        if (!$error) {
1303
            if (!$notrigger) {
1304
                // Call trigger
1305
                $result = $this->call_trigger('BILL_SUPPLIER_MODIFY', $user);
1306
                if ($result < 0) {
1307
                    $error++;
1308
                }
1309
                // End call triggers
1310
            }
1311
        }
1312
1313
        // Commit or rollback
1314
        if ($error) {
1315
            foreach ($this->errors as $errmsg) {
1316
                dol_syslog(get_only_class($this) . "::update " . $errmsg, LOG_ERR);
1317
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
1318
            }
1319
            $this->db->rollback();
1320
            return -1 * $error;
1321
        } else {
1322
            $this->db->commit();
1323
            return 1;
1324
        }
1325
    }
1326
1327
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1328
    /**
1329
     *    Add a discount line into an invoice (as an invoice line) using an existing absolute discount (Consume the discount)
1330
     *
1331
     *    @param     int    $idremise   Id of absolute discount
1332
     *    @return    int                >0 if OK, <0 if KO
1333
     */
1334
    public function insert_discount($idremise)
1335
    {
1336
		// phpcs:enable
1337
        global $conf, $langs;
1338
1339
        include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
1340
        include_once DOL_DOCUMENT_ROOT . '/core/class/discount.class.php';
1341
1342
        $this->db->begin();
1343
1344
        $remise = new DiscountAbsolute($this->db);
1345
        $result = $remise->fetch($idremise);
1346
1347
        if ($result > 0) {
1348
            if ($remise->fk_invoice_supplier) { // Protection against multiple submission
1349
                $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
1350
                $this->db->rollback();
1351
                return -5;
1352
            }
1353
1354
            $facligne = new SupplierInvoiceLine($this->db);
1355
            $facligne->fk_facture_fourn = $this->id;
1356
            $facligne->fk_remise_except = $remise->id;
1357
            $facligne->desc = $remise->description; // Description ligne
1358
            $facligne->vat_src_code = $remise->vat_src_code;
1359
            $facligne->tva_tx = $remise->tva_tx;
1360
            $facligne->subprice = -$remise->amount_ht;
1361
            $facligne->fk_product = 0; // Id produit predefini
1362
            $facligne->product_type = 0;
1363
            $facligne->qty = 1;
1364
            $facligne->remise_percent = 0;
1365
            $facligne->rang = -1;
1366
            $facligne->info_bits = 2;
1367
1368
            if (getDolGlobalString('MAIN_ADD_LINE_AT_POSITION')) {
1369
                $facligne->rang = 1;
1370
                $linecount = count($this->lines);
1371
                for ($ii = 1; $ii <= $linecount; $ii++) {
1372
                    $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
1373
                }
1374
            }
1375
1376
            // Get buy/cost price of invoice that is source of discount
1377
            if ($remise->fk_invoice_supplier_source > 0) {
1378
                $srcinvoice = new FactureFournisseur($this->db);
1379
                $srcinvoice->fetch($remise->fk_invoice_supplier_source);
1380
                $totalcostpriceofinvoice = 0;
1381
                include_once DOL_DOCUMENT_ROOT . '/core/class/html.formmargin.class.php'; // TODO Move this into commonobject
1382
                $formmargin = new FormMargin($this->db);
1383
                $arraytmp = $formmargin->getMarginInfosArray($srcinvoice, false);
1384
                $facligne->pa_ht = $arraytmp['pa_total'];
1385
            }
1386
1387
            $facligne->total_ht  = -$remise->amount_ht;
1388
            $facligne->total_tva = -$remise->amount_tva;
1389
            $facligne->total_ttc = -$remise->amount_ttc;
1390
1391
            $facligne->multicurrency_subprice = -$remise->multicurrency_subprice;
1392
            $facligne->multicurrency_total_ht = -$remise->multicurrency_total_ht;
1393
            $facligne->multicurrency_total_tva = -$remise->multicurrency_total_tva;
1394
            $facligne->multicurrency_total_ttc = -$remise->multicurrency_total_ttc;
1395
1396
            $lineid = $facligne->insert();
1397
            if ($lineid > 0) {
1398
                $result = $this->update_price(1);
1399
                if ($result > 0) {
1400
                    // Create link between discount and invoice line
1401
                    $result = $remise->link_to_invoice($lineid, 0);
1402
                    if ($result < 0) {
1403
                        $this->error = $remise->error;
1404
                        $this->db->rollback();
1405
                        return -4;
1406
                    }
1407
1408
                    $this->db->commit();
1409
                    return 1;
1410
                } else {
1411
                    $this->error = $facligne->error;
1412
                    $this->db->rollback();
1413
                    return -1;
1414
                }
1415
            } else {
1416
                $this->error = $facligne->error;
1417
                $this->db->rollback();
1418
                return -2;
1419
            }
1420
        } else {
1421
            $this->db->rollback();
1422
            return -3;
1423
        }
1424
    }
1425
1426
1427
    /**
1428
     *  Delete invoice from database
1429
     *
1430
     *  @param      User    $user           User object
1431
     *  @param      int     $notrigger      1=Does not execute triggers, 0= execute triggers
1432
     *  @return     int                     Return integer <0 if KO, >0 if OK
1433
     */
1434
    public function delete(User $user, $notrigger = 0)
1435
    {
1436
        global $conf;
1437
1438
        $rowid = $this->id;
1439
1440
        dol_syslog("FactureFournisseur::delete rowid=" . $rowid, LOG_DEBUG);
1441
1442
        // TODO Test if there is at least on payment. If yes, refuse to delete.
1443
1444
        $error = 0;
1445
        $this->db->begin();
1446
1447
        if (!$error && !$notrigger) {
1448
            // Call trigger
1449
            $result = $this->call_trigger('BILL_SUPPLIER_DELETE', $user);
1450
            if ($result < 0) {
1451
                $this->db->rollback();
1452
                return -1;
1453
            }
1454
            // Fin appel triggers
1455
        }
1456
1457
        if (!$error) {
1458
            // If invoice was converted into a discount not yet consumed, we remove discount
1459
            $sql = 'DELETE FROM ' . MAIN_DB_PREFIX . 'societe_remise_except';
1460
            $sql .= ' WHERE fk_invoice_supplier_source = ' . ((int) $rowid);
1461
            $sql .= ' AND fk_invoice_supplier_line IS NULL';
1462
            $resql = $this->db->query($sql);
1463
1464
            // If invoice has consumned discounts
1465
            $this->fetch_lines();
1466
            $list_rowid_det = array();
1467
            foreach ($this->lines as $key => $invoiceline) {
1468
                $list_rowid_det[] = $invoiceline->id;
1469
            }
1470
1471
            // Consumned discounts are freed
1472
            if (count($list_rowid_det)) {
1473
                $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'societe_remise_except';
1474
                $sql .= ' SET fk_invoice_supplier = NULL, fk_invoice_supplier_line = NULL';
1475
                $sql .= ' WHERE fk_invoice_supplier_line IN (' . $this->db->sanitize(implode(',', $list_rowid_det)) . ')';
1476
1477
                dol_syslog(get_only_class($this) . "::delete", LOG_DEBUG);
1478
                if (!$this->db->query($sql)) {
1479
                    $error++;
1480
                }
1481
            }
1482
        }
1483
1484
        if (!$error) {
1485
            $main = MAIN_DB_PREFIX . 'facture_fourn_det';
1486
            $ef = $main . "_extrafields";
1487
            $sqlef = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM " . $main . " WHERE fk_facture_fourn = " . ((int) $rowid) . ")";
1488
            $resqlef = $this->db->query($sqlef);
1489
            $sql = 'DELETE FROM ' . MAIN_DB_PREFIX . 'facture_fourn_det WHERE fk_facture_fourn = ' . ((int) $rowid);
1490
            dol_syslog(get_only_class($this) . "::delete", LOG_DEBUG);
1491
            $resql = $this->db->query($sql);
1492
            if ($resqlef && $resql) {
1493
                $sql = 'DELETE FROM ' . MAIN_DB_PREFIX . 'facture_fourn WHERE rowid = ' . ((int) $rowid);
1494
                dol_syslog(get_only_class($this) . "::delete", LOG_DEBUG);
1495
                $resql2 = $this->db->query($sql);
1496
                if (!$resql2) {
1497
                    $error++;
1498
                }
1499
            } else {
1500
                $error++;
1501
            }
1502
        }
1503
1504
        if (!$error) {
1505
            // Delete linked object
1506
            $res = $this->deleteObjectLinked();
1507
            if ($res < 0) {
1508
                $error++;
1509
            }
1510
        }
1511
1512
        if (!$error) {
1513
            // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
1514
            $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1515
            $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1516
1517
            // We remove directory
1518
            if ($conf->fournisseur->facture->dir_output) {
1519
                include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
1520
1521
                $ref = dol_sanitizeFileName($this->ref);
1522
                $dir = $conf->fournisseur->facture->dir_output . '/' . get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier') . $ref;
1523
                $file = $dir . "/" . $ref . ".pdf";
1524
                if (file_exists($file)) {
1525
                    if (!dol_delete_file($file, 0, 0, 0, $this)) { // For triggers
1526
                        $this->error = 'ErrorFailToDeleteFile';
1527
                        $error++;
1528
                    }
1529
                }
1530
                if (file_exists($dir)) {
1531
                    $res = @dol_delete_dir_recursive($dir);
1532
1533
                    if (!$res) {
1534
                        $this->error = 'ErrorFailToDeleteDir';
1535
                        $error++;
1536
                    }
1537
                }
1538
            }
1539
        }
1540
1541
        // Remove extrafields
1542
        if (!$error) {
1543
            $result = $this->deleteExtraFields();
1544
            if ($result < 0) {
1545
                $error++;
1546
                dol_syslog(get_only_class($this) . "::delete error -4 " . $this->error, LOG_ERR);
1547
            }
1548
        }
1549
1550
        if (!$error) {
1551
            dol_syslog(get_only_class($this) . "::delete $this->id by $user->id", LOG_DEBUG);
1552
            $this->db->commit();
1553
            return 1;
1554
        } else {
1555
            $this->error = $this->db->lasterror();
1556
            $this->db->rollback();
1557
            return -$error;
1558
        }
1559
    }
1560
1561
1562
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1563
    /**
1564
     *  Tag invoice as a paid invoice
1565
     *
1566
     *  @deprecated
1567
     *  @see setPaid()
1568
     *  @param  User    $user       Object user
1569
     *  @param  string  $close_code Code indicates whether the class has paid in full while payment is incomplete. Not implemented yet.
1570
     *  @param  string  $close_note Comment informs if the class has been paid while payment is incomplete. Not implemented yet.
1571
     *  @return int                 Return integer <0 si ko, >0 si ok
1572
     */
1573
    public function set_paid($user, $close_code = '', $close_note = '')
1574
    {
1575
		// phpcs:enable
1576
        dol_syslog(get_only_class($this) . "::set_paid is deprecated, use setPaid instead", LOG_NOTICE);
1577
        return $this->setPaid($user, $close_code, $close_note);
1578
    }
1579
1580
    /**
1581
     *  Tag invoice as a paid invoice
1582
     *
1583
     *  @param  User    $user       Object user
1584
     *  @param  string  $close_code Code indicates whether the class has paid in full while payment is incomplete. Not implemented yet.
1585
     *  @param  string  $close_note Comment informs if the class has been paid while payment is incomplete. Not implemented yet.
1586
     *  @return int<-1,1>           Return integer <0 si ko, >0 si ok
1587
     */
1588
    public function setPaid($user, $close_code = '', $close_note = '')
1589
    {
1590
        $error = 0;
1591
1592
        if ($this->paid != 1) {
1593
            $this->db->begin();
1594
1595
            $now = dol_now();
1596
1597
            dol_syslog("FactureFournisseur::setPaid", LOG_DEBUG);
1598
1599
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture_fourn SET';
1600
            $sql .= ' fk_statut = ' . self::STATUS_CLOSED;
1601
            if (!$close_code) {
1602
                $sql .= ', paye=1';
1603
            }
1604
            if ($close_code) {
1605
                $sql .= ", close_code='" . $this->db->escape($close_code) . "'";
1606
            }
1607
            if ($close_note) {
1608
                $sql .= ", close_note='" . $this->db->escape($close_note) . "'";
1609
            }
1610
            $sql .= ', fk_user_closing = ' . ((int) $user->id);
1611
            $sql .= ", date_closing = '" . $this->db->idate($now) . "'";
1612
            $sql .= ' WHERE rowid = ' . ((int) $this->id);
1613
1614
            $resql = $this->db->query($sql);
1615
            if ($resql) {
1616
                // Call trigger
1617
                $result = $this->call_trigger('BILL_SUPPLIER_PAYED', $user);
1618
                if ($result < 0) {
1619
                    $error++;
1620
                }
1621
                // End call triggers
1622
            } else {
1623
                $error++;
1624
                $this->error = $this->db->error();
1625
                dol_print_error($this->db);
1626
            }
1627
1628
            if (!$error) {
1629
                $this->db->commit();
1630
                return 1;
1631
            } else {
1632
                $this->db->rollback();
1633
                return -1;
1634
            }
1635
        } else {
1636
            return 0;
1637
        }
1638
    }
1639
1640
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1641
    /**
1642
     *  Tag the invoice as not fully paid + trigger call BILL_UNPAYED
1643
     *  Function used when a direct debit payment is refused,
1644
     *  or when the invoice was canceled and reopened.
1645
     *
1646
     *  @deprecated
1647
     *  @see setUnpaid()
1648
     *  @param      User    $user       Object user that change status
1649
     *  @return     int                 Return integer <0 si ok, >0 si ok
1650
     */
1651
    public function set_unpaid($user)
1652
    {
1653
		// phpcs:enable
1654
        dol_syslog(get_only_class($this) . "::set_unpaid is deprecated, use setUnpaid instead", LOG_NOTICE);
1655
        return $this->setUnpaid($user);
1656
    }
1657
1658
    /**
1659
     *  Tag the invoice as not fully paid + trigger call BILL_UNPAYED
1660
     *  Function used when a direct debit payment is refused,
1661
     *  or when the invoice was canceled and reopened.
1662
     *
1663
     *  @param      User    $user       Object user that change status
1664
     *  @return     int                 Return integer <0 si ok, >0 si ok
1665
     */
1666
    public function setUnpaid($user)
1667
    {
1668
        $error = 0;
1669
1670
        $this->db->begin();
1671
1672
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture_fourn';
1673
        $sql .= ' SET paye=0, fk_statut=' . self::STATUS_VALIDATED . ', close_code=null, close_note=null,';
1674
        $sql .= ' date_closing=null,';
1675
        $sql .= ' fk_user_closing=null';
1676
        $sql .= ' WHERE rowid = ' . ((int) $this->id);
1677
1678
        dol_syslog(get_only_class($this) . "::set_unpaid", LOG_DEBUG);
1679
        $resql = $this->db->query($sql);
1680
        if ($resql) {
1681
            // Call trigger
1682
            $result = $this->call_trigger('BILL_SUPPLIER_UNPAYED', $user);
1683
            if ($result < 0) {
1684
                $error++;
1685
            }
1686
            // End call triggers
1687
        } else {
1688
            $error++;
1689
            $this->error = $this->db->error();
1690
            dol_print_error($this->db);
1691
        }
1692
1693
        if (!$error) {
1694
            $this->db->commit();
1695
            return 1;
1696
        } else {
1697
            $this->db->rollback();
1698
            return -1;
1699
        }
1700
    }
1701
1702
    /**
1703
     *  Tag invoice as canceled, with no payment on it (example for replacement invoice or payment never received) + call trigger BILL_CANCEL
1704
     *  Warning, if option to decrease stock on invoice was set, this function does not change stock (it might be a cancel because
1705
     *  of no payment even if merchandises were sent).
1706
     *
1707
     *  @param  User    $user           Object user making change
1708
     *  @param  string  $close_code     Code of closing invoice (CLOSECODE_REPLACED, CLOSECODE_...)
1709
     *  @param  string  $close_note     Comment
1710
     *  @return int                     Return integer <0 if KO, >0 if OK
1711
     */
1712
    public function setCanceled($user, $close_code = '', $close_note = '')
1713
    {
1714
        dol_syslog(get_only_class($this) . "::setCanceled rowid=" . ((int)$this->id), LOG_DEBUG);
1715
1716
        $this->db->begin();
1717
1718
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture_fourn SET';
1719
        $sql .= ' fk_statut=' . self::STATUS_ABANDONED;
1720
        if ($close_code) {
1721
            $sql .= ", close_code='" . $this->db->escape($close_code) . "'";
1722
        }
1723
        if ($close_note) {
1724
            $sql .= ", close_note='" . $this->db->escape($close_note) . "'";
1725
        }
1726
        $sql .= " WHERE rowid = " . ((int) $this->id);
1727
1728
        $resql = $this->db->query($sql);
1729
        if ($resql) {
1730
            // Bound discounts are deducted from the invoice
1731
            // as they have not been used since the invoice is abandoned.
1732
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'societe_remise_except';
1733
            $sql .= ' SET fk_invoice_supplier = NULL';
1734
            $sql .= ' WHERE fk_invoice_supplier = ' . ((int) $this->id);
1735
1736
            $resql = $this->db->query($sql);
1737
            if ($resql) {
1738
                // Call trigger
1739
                $result = $this->call_trigger('BILL_SUPPLIER_CANCEL', $user);
1740
                if ($result < 0) {
1741
                    $this->db->rollback();
1742
                    return -1;
1743
                }
1744
                // End call triggers
1745
1746
                $this->db->commit();
1747
                return 1;
1748
            } else {
1749
                $this->error = $this->db->error() . " sql=" . $sql;
1750
                $this->db->rollback();
1751
                return -1;
1752
            }
1753
        } else {
1754
            $this->error = $this->db->error() . " sql=" . $sql;
1755
            $this->db->rollback();
1756
            return -2;
1757
        }
1758
    }
1759
1760
    /**
1761
     *  Tag invoice as validated + call trigger BILL_VALIDATE
1762
     *
1763
     *  @param  User    $user           Object user that validate
1764
     *  @param  string  $force_number   Reference to force on invoice
1765
     *  @param  int     $idwarehouse    Id of warehouse for stock change
1766
     *  @param  int     $notrigger      1=Does not execute triggers, 0= execute triggers
1767
     *  @return int                     Return integer <0 if KO, =0 if nothing to do, >0 if OK
1768
     */
1769
    public function validate($user, $force_number = '', $idwarehouse = 0, $notrigger = 0)
1770
    {
1771
        global $mysoc, $conf, $langs;
1772
1773
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
1774
1775
        $now = dol_now();
1776
1777
        $error = 0;
1778
        dol_syslog(get_only_class($this) . '::validate user=' . $user->id . ', force_number=' . $force_number . ', idwarehouse=' . $idwarehouse);
1779
1780
        // Force to have object complete for checks
1781
        $this->fetch_thirdparty();
1782
        $this->fetch_lines();
1783
1784
        // Check parameters
1785
        if ($this->status > self::STATUS_DRAFT) {   // This is to avoid to validate twice (avoid errors on logs and stock management)
1786
            dol_syslog(get_only_class($this) . "::validate no draft status", LOG_WARNING);
1787
            return 0;
1788
        }
1789
        if (preg_match('/^' . preg_quote($langs->trans("CopyOf") . ' ') . '/', $this->ref_supplier)) {
1790
            $langs->load("errors");
1791
            $this->error = $langs->trans("ErrorFieldFormat", $langs->transnoentities("RefSupplier")) . '. ' . $langs->trans('RemoveString', $langs->transnoentitiesnoconv("CopyOf"));
1792
            return -1;
1793
        }
1794
        if (count($this->lines) <= 0) {
1795
            $langs->load("errors");
1796
            $this->error = $langs->trans("ErrorObjectMustHaveLinesToBeValidated", $this->ref);
1797
            return -1;
1798
        }
1799
1800
        // Check for mandatory fields in thirdparty (defined into setup)
1801
        if (!empty($this->thirdparty) && is_object($this->thirdparty)) {
1802
            $array_to_check = array('IDPROF1', 'IDPROF2', 'IDPROF3', 'IDPROF4', 'IDPROF5', 'IDPROF6', 'EMAIL', 'ACCOUNTANCY_CODE_SUPPLIER');
1803
            foreach ($array_to_check as $key) {
1804
                $keymin = strtolower($key);
1805
                if ($keymin == 'accountancy_code_supplier') {
1806
                    $keymin = 'code_compta_fournisseur';
1807
                }
1808
                if (!property_exists($this->thirdparty, $keymin)) {
1809
                    continue;
1810
                }
1811
                $vallabel = $this->thirdparty->$keymin;
1812
1813
                $i = (int) preg_replace('/[^0-9]/', '', $key);
1814
                if ($i > 0) {
1815
                    if ($this->thirdparty->isACompany()) {
1816
                        // Check for mandatory prof id (but only if country is other than ours)
1817
                        if ($mysoc->country_id > 0 && $this->thirdparty->country_id == $mysoc->country_id) {
1818
                            $idprof_mandatory = 'SOCIETE_' . $key . '_INVOICE_MANDATORY';
1819
                            if (!$vallabel && getDolGlobalString($idprof_mandatory)) {
1820
                                $langs->load("errors");
1821
                                $this->error = $langs->trans('ErrorProdIdIsMandatory', $langs->transcountry('ProfId' . $i, $this->thirdparty->country_code)) . ' (' . $langs->trans("ForbiddenBySetupRules") . ') [' . $langs->trans('Company') . ' : ' . $this->thirdparty->name . ']';
1822
                                dol_syslog(__METHOD__ . ' ' . $this->error, LOG_ERR);
1823
                                return -1;
1824
                            }
1825
                        }
1826
                    }
1827
                } else {
1828
                    if ($key == 'EMAIL') {
1829
                        // Check for mandatory
1830
                        if (getDolGlobalString('SOCIETE_EMAIL_INVOICE_MANDATORY') && !isValidEmail($this->thirdparty->email)) {
1831
                            $langs->load("errors");
1832
                            $this->error = $langs->trans("ErrorBadEMail", $this->thirdparty->email) . ' (' . $langs->trans("ForbiddenBySetupRules") . ') [' . $langs->trans('Company') . ' : ' . $this->thirdparty->name . ']';
1833
                            dol_syslog(__METHOD__ . ' ' . $this->error, LOG_ERR);
1834
                            return -1;
1835
                        }
1836
                    } elseif ($key == 'ACCOUNTANCY_CODE_SUPPLIER') {
1837
                        // Check for mandatory
1838
                        if (getDolGlobalString('SOCIETE_ACCOUNTANCY_CODE_SUPPLIER_INVOICE_MANDATORY') && empty($this->thirdparty->code_compta_fournisseur)) {
1839
                            $langs->load("errors");
1840
                            $this->error = $langs->trans("ErrorAccountancyCodeSupplierIsMandatory", $this->thirdparty->name) . ' (' . $langs->trans("ForbiddenBySetupRules") . ')';
1841
                            dol_syslog(__METHOD__ . ' ' . $this->error, LOG_ERR);
1842
                            return -1;
1843
                        }
1844
                    }
1845
                }
1846
            }
1847
        }
1848
1849
        $this->db->begin();
1850
1851
        // Define new ref
1852
        if ($force_number) {
1853
            $num = $force_number;
1854
        } elseif (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
1855
            $num = $this->getNextNumRef($this->thirdparty);
1856
        } else {
1857
            $num = $this->ref;
1858
        }
1859
        $this->newref = dol_sanitizeFileName($num);
1860
1861
        $sql = "UPDATE " . MAIN_DB_PREFIX . "facture_fourn";
1862
        $sql .= " SET ref='" . $this->db->escape($num) . "', fk_statut = 1, fk_user_valid = " . ((int) $user->id) . ", date_valid = '" . $this->db->idate($now) . "'";
1863
        $sql .= " WHERE rowid = " . ((int) $this->id);
1864
1865
        dol_syslog(get_only_class($this) . "::validate", LOG_DEBUG);
1866
        $resql = $this->db->query($sql);
1867
        if ($resql) {
1868
            // Si on incrémente le produit principal et ses composants à la validation de facture fournisseur
1869
            if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL')) {
1870
                $langs->load("agenda");
1871
1872
                $cpt = count($this->lines);
1873
                for ($i = 0; $i < $cpt; $i++) {
1874
                    if ($this->lines[$i]->fk_product > 0) {
1875
                        $mouvP = new MouvementStock($this->db);
1876
                        $mouvP->origin = &$this;
1877
                        $mouvP->setOrigin($this->element, $this->id);
1878
                        // We increase stock for product
1879
                        $up_ht_disc = $this->lines[$i]->subprice;
1880
                        if (!empty($this->lines[$i]->remise_percent) && !getDolGlobalString('STOCK_EXCLUDE_DISCOUNT_FOR_PMP')) {
1881
                            $up_ht_disc = price2num($up_ht_disc * (100 - $this->lines[$i]->remise_percent) / 100, 'MU');
1882
                        }
1883
                        if ($this->type == FactureFournisseur::TYPE_CREDIT_NOTE) {
1884
                            $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $up_ht_disc, $langs->trans("InvoiceValidatedInDolibarr", $num));
1885
                        } else {
1886
                            $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $up_ht_disc, $langs->trans("InvoiceValidatedInDolibarr", $num));
1887
                        }
1888
                        if ($result < 0) {
1889
                            $this->error = $mouvP->error;
1890
                            if (count($mouvP->errors)) {
1891
                                $this->errors = $mouvP->errors;
1892
                            }
1893
                            return -2;
1894
                        }
1895
                    }
1896
                }
1897
            }
1898
1899
            // Triggers call
1900
            if (!$error && empty($notrigger)) {
1901
                // Call trigger
1902
                $result = $this->call_trigger('BILL_SUPPLIER_VALIDATE', $user);
1903
                if ($result < 0) {
1904
                    $error++;
1905
                }
1906
                // End call triggers
1907
            }
1908
1909
            if (!$error) {
1910
                $this->oldref = $this->ref;
1911
1912
                // Rename directory if dir was a temporary ref
1913
                if (preg_match('/^[\(]?PROV/i', $this->ref)) {
1914
                    // Now we rename also files into index
1915
                    $sql = 'UPDATE ' . MAIN_DB_PREFIX . "ecm_files set filename = CONCAT('" . $this->db->escape($this->newref) . "', SUBSTR(filename, " . (strlen($this->ref) + 1) . ")), filepath = 'fournisseur/facture/" . get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier') . $this->db->escape($this->newref) . "'";
1916
                    $sql .= " WHERE filename LIKE '" . $this->db->escape($this->ref) . "%' AND filepath = 'fournisseur/facture/" . get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier') . $this->db->escape($this->ref) . "' and entity = " . $conf->entity;
1917
                    $resql = $this->db->query($sql);
1918
                    if (!$resql) {
1919
                        $error++;
1920
                        $this->error = $this->db->lasterror();
1921
                    }
1922
                    $sql = 'UPDATE ' . MAIN_DB_PREFIX . "ecm_files set filepath = 'fournisseur/facture/" . get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier') . $this->db->escape($this->newref) . "'";
1923
                    $sql .= " WHERE filepath = 'fournisseur/facture/" . get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier') . $this->db->escape($this->ref) . "' and entity = " . $conf->entity;
1924
                    $resql = $this->db->query($sql);
1925
                    if (!$resql) {
1926
                        $error++;
1927
                        $this->error = $this->db->lasterror();
1928
                    }
1929
1930
                    // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
1931
                    $oldref = dol_sanitizeFileName($this->ref);
1932
                    $dirsource = $conf->fournisseur->facture->dir_output . '/' . get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier') . $oldref;
1933
                    $dirdest = $conf->fournisseur->facture->dir_output . '/' . get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier') . $this->newref;
1934
                    if (!$error && file_exists($dirsource)) {
1935
                        dol_syslog(get_only_class($this) . "::validate rename dir " . $dirsource . " into " . $dirdest);
1936
1937
                        if (@rename($dirsource, $dirdest)) {
1938
                            dol_syslog("Rename ok");
1939
                            // Rename docs starting with $oldref with $this->newref
1940
                            $listoffiles = dol_dir_list($conf->fournisseur->facture->dir_output . '/' . get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier') . $this->newref, 'files', 1, '^' . preg_quote($oldref, '/'));
1941
                            foreach ($listoffiles as $fileentry) {
1942
                                $dirsource = $fileentry['name'];
1943
                                $dirdest = preg_replace('/^' . preg_quote($oldref, '/') . '/', $this->newref, $dirsource);
1944
                                $dirsource = $fileentry['path'] . '/' . $dirsource;
1945
                                $dirdest = $fileentry['path'] . '/' . $dirdest;
1946
                                @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

1946
                                /** @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...
1947
                            }
1948
                        }
1949
                    }
1950
                }
1951
            }
1952
1953
            // Set new ref and define current statut
1954
            if (!$error) {
1955
                $this->ref = $this->newref;
1956
                $this->statut = self::STATUS_VALIDATED;
1957
                $this->status = self::STATUS_VALIDATED;
1958
                //$this->date_validation=$now; this is stored into log table
1959
            }
1960
1961
            if (!$error) {
1962
                $this->db->commit();
1963
                return 1;
1964
            } else {
1965
                $this->db->rollback();
1966
                return -1;
1967
            }
1968
        } else {
1969
            $this->error = $this->db->error();
1970
            $this->db->rollback();
1971
            return -1;
1972
        }
1973
    }
1974
1975
    /**
1976
     *  Set draft status
1977
     *
1978
     *  @param  User    $user           Object user that modify
1979
     *  @param  int     $idwarehouse    Id warehouse to use for stock change.
1980
     *  @param  int     $notrigger      1=Does not execute triggers, 0= execute triggers
1981
     *  @return int                     Return integer <0 if KO, >0 if OK
1982
     */
1983
    public function setDraft($user, $idwarehouse = -1, $notrigger = 0)
1984
    {
1985
        // phpcs:enable
1986
        global $conf, $langs;
1987
1988
        $error = 0;
1989
1990
        if ($this->status == self::STATUS_DRAFT) {
1991
            dol_syslog(__METHOD__ . " already draft status", LOG_WARNING);
1992
            return 0;
1993
        }
1994
1995
        dol_syslog(__METHOD__, LOG_DEBUG);
1996
1997
        $this->db->begin();
1998
1999
        $sql = "UPDATE " . MAIN_DB_PREFIX . "facture_fourn";
2000
        $sql .= " SET fk_statut = " . self::STATUS_DRAFT;
2001
        $sql .= " WHERE rowid = " . ((int) $this->id);
2002
2003
        $result = $this->db->query($sql);
2004
        if ($result) {
2005
            if (!$error) {
2006
                $this->oldcopy = clone $this;
2007
            }
2008
2009
            // Si on incremente le produit principal et ses composants a la validation de facture fournisseur, on decremente
2010
            if ($result >= 0 && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL')) {
2011
                $langs->load("agenda");
2012
2013
                $cpt = count($this->lines);
2014
                for ($i = 0; $i < $cpt; $i++) {
2015
                    if ($this->lines[$i]->fk_product > 0) {
2016
                        $mouvP = new MouvementStock($this->db);
2017
                        $mouvP->origin = &$this;
2018
                        $mouvP->setOrigin($this->element, $this->id);
2019
                        // We increase stock for product
2020
                        if ($this->type == FactureFournisseur::TYPE_CREDIT_NOTE) {
2021
                            $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceBackToDraftInDolibarr", $this->ref));
2022
                        } else {
2023
                            $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceBackToDraftInDolibarr", $this->ref));
2024
                        }
2025
                    }
2026
                }
2027
            }
2028
            // Triggers call
2029
            if (!$error && empty($notrigger)) {
2030
                // Call trigger
2031
                $result = $this->call_trigger('BILL_SUPPLIER_UNVALIDATE', $user);
2032
                if ($result < 0) {
2033
                    $error++;
2034
                }
2035
                // End call triggers
2036
            }
2037
            if ($error == 0) {
2038
                $this->db->commit();
2039
                return 1;
2040
            } else {
2041
                $this->db->rollback();
2042
                return -1;
2043
            }
2044
        } else {
2045
            $this->error = $this->db->error();
2046
            $this->db->rollback();
2047
            return -1;
2048
        }
2049
    }
2050
2051
2052
    /**
2053
     *  Adds an invoice line (associated with no predefined product/service)
2054
     *  The parameters are already supposed to be correct and with final values when calling
2055
     *  this method. Also, for the VAT rate, it must already have been defined by the caller by
2056
     *  by the get_default_tva method(vendor_company, buying company, idprod) and the desc must
2057
     *  already have the right value (the caller has to manage the multilanguage).
2058
     *
2059
     *  @param      string      $desc                   Description of the line
2060
     *  @param      double      $pu                     Unit price (HT or TTC according to price_base_type, > 0 even for credit note)
2061
     *  @param      double      $txtva                  Force Vat rate to use, -1 for auto.
2062
     *  @param      double      $txlocaltax1            LocalTax1 Rate
2063
     *  @param      double      $txlocaltax2            LocalTax2 Rate
2064
     *  @param      double      $qty                    Quantity
2065
     *  @param      int         $fk_product             Product/Service ID predefined
2066
     *  @param      double      $remise_percent         Percentage discount of the line
2067
     *  @param      int         $date_start             Service start date
2068
     *  @param      int         $date_end               Service expiry date
2069
     *  @param      int         $fk_code_ventilation    Accounting breakdown code
2070
     *  @param      int         $info_bits              Line type bits
2071
     *  @param      string      $price_base_type        HT or TTC
2072
     *  @param      int         $type                   Type of line (0=product, 1=service)
2073
     *  @param      int         $rang                   Position of line
2074
     *  @param      int         $notrigger              Disable triggers
2075
     *  @param      array       $array_options          extrafields array
2076
     *  @param      int|null    $fk_unit                Code of the unit to use. Null to use the default one
2077
     *  @param      int         $origin_id              id origin document
2078
     *  @param      double      $pu_devise              Amount in currency
2079
     *  @param      string      $ref_supplier           Supplier ref
2080
     *  @param      int         $special_code           Special code
2081
     *  @param      int         $fk_parent_line         Parent line id
2082
     *  @param      int         $fk_remise_except       Id discount used
2083
     *  @return     int                                 >0 if OK, <0 if KO
2084
     */
2085
    public function addline($desc, $pu, $txtva, $txlocaltax1, $txlocaltax2, $qty, $fk_product = 0, $remise_percent = 0, $date_start = 0, $date_end = 0, $fk_code_ventilation = 0, $info_bits = 0, $price_base_type = 'HT', $type = 0, $rang = -1, $notrigger = 0, $array_options = [], $fk_unit = null, $origin_id = 0, $pu_devise = 0, $ref_supplier = '', $special_code = 0, $fk_parent_line = 0, $fk_remise_except = 0)
2086
    {
2087
        global $langs, $mysoc;
2088
2089
        dol_syslog(get_only_class($this) . "::addline $desc,$pu,$qty,$txtva,$fk_product,$remise_percent,$date_start,$date_end,$fk_code_ventilation,$info_bits,$price_base_type,$type,$fk_unit,fk_remise_except=$fk_remise_except", LOG_DEBUG);
2090
        include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
2091
2092
        if ($this->status == self::STATUS_DRAFT) {
2093
            // Clean parameters
2094
            if (empty($remise_percent)) {
2095
                $remise_percent = 0;
2096
            }
2097
            if (empty($qty)) {
2098
                $qty = 0;
2099
            }
2100
            if (empty($info_bits)) {
2101
                $info_bits = 0;
2102
            }
2103
            if (empty($rang)) {
2104
                $rang = 0;
2105
            }
2106
            if (empty($fk_code_ventilation)) {
2107
                $fk_code_ventilation = 0;
2108
            }
2109
            if (empty($txtva)) {
2110
                $txtva = 0;
2111
            }
2112
            if (empty($txlocaltax1)) {
2113
                $txlocaltax1 = 0;
2114
            }
2115
            if (empty($txlocaltax2)) {
2116
                $txlocaltax2 = 0;
2117
            }
2118
2119
            $remise_percent = price2num($remise_percent);
2120
            $qty = price2num($qty);
2121
            $pu = price2num($pu);
2122
            if (!preg_match('/\((.*)\)/', (string) $txtva)) {
2123
                $txtva = price2num($txtva); // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
2124
            }
2125
            $txlocaltax1 = price2num($txlocaltax1);
2126
            $txlocaltax2 = price2num($txlocaltax2);
2127
2128
            if ($date_start && $date_end && $date_start > $date_end) {
2129
                $langs->load("errors");
2130
                $this->error = $langs->trans('ErrorStartDateGreaterEnd');
2131
                return -1;
2132
            }
2133
2134
            $this->db->begin();
2135
2136
            if ($fk_product > 0) {
2137
                if (getDolGlobalString('SUPPLIER_INVOICE_WITH_PREDEFINED_PRICES_ONLY')) {
2138
                    // Check quantity is enough
2139
                    dol_syslog(get_only_class($this) . "::addline we check supplier prices fk_product=" . $fk_product . " qty=" . $qty . " ref_supplier=" . $ref_supplier);
2140
                    $prod = new ProductFournisseur($this->db);
2141
                    if ($prod->fetch($fk_product) > 0) {
2142
                        $product_type = $prod->type;
2143
                        $label = $prod->label;
2144
                        $fk_prod_fourn_price = 0;
2145
2146
                        // We use 'none' instead of $ref_supplier, because $ref_supplier may not exists anymore. So we will take the first supplier price ok.
2147
                        // If we want a dedicated supplier price, we must provide $fk_prod_fourn_price.
2148
                        $result = $prod->get_buyprice($fk_prod_fourn_price, $qty, $fk_product, 'none', ($this->fk_soc ? $this->fk_soc : $this->socid)); // Search on couple $fk_prod_fourn_price/$qty first, then on triplet $qty/$fk_product/$ref_supplier/$this->fk_soc
2149
                        if ($result > 0) {
2150
                            if (empty($pu)) {
2151
                                $pu = $prod->fourn_pu; // Unit price supplier price set by get_buyprice
2152
                            }
2153
                            $ref_supplier = $prod->ref_supplier; // Ref supplier price set by get_buyprice
2154
                            // is remise percent not keyed but present for the product we add it
2155
                            if ($remise_percent == 0 && $prod->remise_percent != 0) {
2156
                                $remise_percent = $prod->remise_percent;
2157
                            }
2158
                        }
2159
                        if ($result == 0) {                   // If result == 0, we failed to found the supplier reference price
2160
                            $langs->load("errors");
2161
                            $this->error = "Ref " . $prod->ref . " " . $langs->trans("ErrorQtyTooLowForThisSupplier");
2162
                            $this->db->rollback();
2163
                            dol_syslog(get_only_class($this) . "::addline we did not found supplier price, so we can't guess unit price");
2164
                            //$pu    = $prod->fourn_pu;     // We do not overwrite unit price
2165
                            //$ref   = $prod->ref_fourn;    // We do not overwrite ref supplier price
2166
                            return -1;
2167
                        }
2168
                        if ($result == -1) {
2169
                            $langs->load("errors");
2170
                            $this->error = "Ref " . $prod->ref . " " . $langs->trans("ErrorQtyTooLowForThisSupplier");
2171
                            $this->db->rollback();
2172
                            dol_syslog(get_only_class($this) . "::addline result=" . $result . " - " . $this->error, LOG_DEBUG);
2173
                            return -1;
2174
                        }
2175
                        if ($result < -1) {
2176
                            $this->error = $prod->error;
2177
                            $this->db->rollback();
2178
                            dol_syslog(get_only_class($this) . "::addline result=" . $result . " - " . $this->error, LOG_ERR);
2179
                            return -1;
2180
                        }
2181
                    } else {
2182
                        $this->error = $prod->error;
2183
                        $this->db->rollback();
2184
                        return -1;
2185
                    }
2186
                }
2187
            } else {
2188
                $product_type = $type;
2189
            }
2190
2191
            if (isModEnabled("multicurrency") && $pu_devise > 0) {
2192
                $pu = 0;
2193
            }
2194
2195
            $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
2196
2197
            // Clean vat code
2198
            $reg = array();
2199
            $vat_src_code = '';
2200
            if (preg_match('/\((.*)\)/', $txtva, $reg)) {
2201
                $vat_src_code = $reg[1];
2202
                $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
2203
            }
2204
2205
            // Calcul du total TTC et de la TVA pour la ligne a partir de
2206
            // qty, pu, remise_percent et txtva
2207
            // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2208
            // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2209
2210
            $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $this->thirdparty, $localtaxes_type, 100, $this->multicurrency_tx, $pu_devise);
2211
            $total_ht  = $tabprice[0];
2212
            $total_tva = $tabprice[1];
2213
            $total_ttc = $tabprice[2];
2214
            $total_localtax1 = $tabprice[9];
2215
            $total_localtax2 = $tabprice[10];
2216
            $pu_ht = $tabprice[3];
2217
2218
            // MultiCurrency
2219
            $multicurrency_total_ht  = $tabprice[16];
2220
            $multicurrency_total_tva = $tabprice[17];
2221
            $multicurrency_total_ttc = $tabprice[18];
2222
            $pu_ht_devise = $tabprice[19];
2223
2224
            // Check parameters
2225
            if ($type < 0) {
2226
                return -1;
2227
            }
2228
2229
            if ($rang < 0) {
2230
                $rangmax = $this->line_max();
2231
                $rang = $rangmax + 1;
2232
            }
2233
2234
            // Insert line
2235
            $supplierinvoiceline = new SupplierInvoiceLine($this->db);
2236
2237
            $supplierinvoiceline->context = $this->context;
2238
2239
            $supplierinvoiceline->fk_facture_fourn = $this->id;
2240
            //$supplierinvoiceline->label=$label;   // deprecated
2241
            $supplierinvoiceline->desc = $desc;
2242
            $supplierinvoiceline->ref_supplier = $ref_supplier;
2243
2244
            $supplierinvoiceline->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...
2245
            $supplierinvoiceline->subprice = ($this->type == self::TYPE_CREDIT_NOTE ? -abs($pu_ht) : $pu_ht); // For credit note, unit price always negative, always positive otherwise
2246
2247
            $supplierinvoiceline->vat_src_code = $vat_src_code;
2248
            $supplierinvoiceline->tva_tx = $txtva;
0 ignored issues
show
Documentation Bug introduced by
It seems like $txtva can also be of type string. However, the property $tva_tx 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...
2249
            $supplierinvoiceline->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
2250
            $supplierinvoiceline->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
2251
            $supplierinvoiceline->localtax1_type = empty($localtaxes_type[0]) ? 0 : $localtaxes_type[0];
2252
            $supplierinvoiceline->localtax2_type = empty($localtaxes_type[2]) ? 0 : $localtaxes_type[2];
2253
2254
            $supplierinvoiceline->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
2255
            $supplierinvoiceline->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
2256
            $supplierinvoiceline->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
2257
            $supplierinvoiceline->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
2258
            $supplierinvoiceline->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
2259
2260
            $supplierinvoiceline->fk_product = $fk_product;
2261
            $supplierinvoiceline->product_type = $type;
2262
            $supplierinvoiceline->remise_percent = $remise_percent;
2263
            $supplierinvoiceline->date_start = $date_start;
2264
            $supplierinvoiceline->date_end = $date_end;
2265
            $supplierinvoiceline->fk_code_ventilation = $fk_code_ventilation;
2266
            $supplierinvoiceline->rang = $rang;
2267
            $supplierinvoiceline->info_bits = $info_bits;
2268
            $supplierinvoiceline->fk_remise_except = $fk_remise_except;
2269
2270
2271
            $supplierinvoiceline->special_code = (int) $special_code;
2272
            $supplierinvoiceline->fk_parent_line = $fk_parent_line;
2273
            $supplierinvoiceline->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

2273
            /** @scrutinizer ignore-deprecated */ $supplierinvoiceline->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...
2274
            $supplierinvoiceline->origin_id = $origin_id;
2275
            $supplierinvoiceline->fk_unit = $fk_unit;
2276
2277
            // Multicurrency
2278
            $supplierinvoiceline->fk_multicurrency = $this->fk_multicurrency;
2279
            $supplierinvoiceline->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...
2280
            $supplierinvoiceline->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
2281
2282
            $supplierinvoiceline->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
2283
            $supplierinvoiceline->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
2284
            $supplierinvoiceline->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
2285
2286
            if (is_array($array_options) && count($array_options) > 0) {
2287
                $supplierinvoiceline->array_options = $array_options;
2288
            }
2289
2290
            $result = $supplierinvoiceline->insert($notrigger);
2291
            if ($result > 0) {
2292
                // Reorder if child line
2293
                if (!empty($fk_parent_line)) {
2294
                    $this->line_order(true, 'DESC');
2295
                } elseif ($rang > 0 && $rang <= count($this->lines)) { // Update all rank of all other lines
2296
                    $linecount = count($this->lines);
2297
                    for ($ii = $rang; $ii <= $linecount; $ii++) {
2298
                        $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
2299
                    }
2300
                }
2301
2302
                // Mise a jour information denormalisees au niveau de la facture meme
2303
                $result = $this->update_price(1, 'auto', 0, $this->thirdparty); // The addline method is designed to add line from user input so total calculation with update_price must be done using 'auto' mode.
2304
                if ($result > 0) {
2305
                    $this->db->commit();
2306
                    return $supplierinvoiceline->id;
2307
                } else {
2308
                    $this->error = $this->db->error();
2309
                    $this->db->rollback();
2310
                    return -1;
2311
                }
2312
            } else {
2313
                $this->error = $supplierinvoiceline->error;
2314
                $this->errors = $supplierinvoiceline->errors;
2315
                $this->db->rollback();
2316
                return -2;
2317
            }
2318
        } else {
2319
            return 0;
2320
        }
2321
    }
2322
2323
    /**
2324
     * Update a line detail into database
2325
     *
2326
     * @param       int         $id                 Id of line invoice
2327
     * @param       string      $desc               Description of line
2328
     * @param       double      $pu                 Prix unitaire (HT ou TTC selon price_base_type)
2329
     * @param       double      $vatrate            VAT Rate (Can be '8.5', '8.5 (ABC)')
2330
     * @param       double      $txlocaltax1        LocalTax1 Rate
2331
     * @param       double      $txlocaltax2        LocalTax2 Rate
2332
     * @param       double      $qty                Quantity
2333
     * @param       int         $idproduct          Id produit
2334
     * @param       string      $price_base_type    HT or TTC
2335
     * @param       int         $info_bits          Miscellaneous information of line
2336
     * @param       int         $type               Type of line (0=product, 1=service)
2337
     * @param       double      $remise_percent     Percentage discount of the line
2338
     * @param       int         $notrigger          Disable triggers
2339
     * @param       int|string  $date_start         Date start of service
2340
     * @param       int|string  $date_end           Date end of service
2341
     * @param       array       $array_options      extrafields array
2342
     * @param       int|null    $fk_unit            Code of the unit to use. Null to use the default one
2343
     * @param       double      $pu_devise          Amount in currency
2344
     * @param       string      $ref_supplier       Supplier ref
2345
     * @param       int         $rang               Line rank
2346
     * @return      int<-1,1>                       Return integer <0 if KO, >0 if OK
2347
     */
2348
    public function updateline($id, $desc, $pu, $vatrate, $txlocaltax1 = 0, $txlocaltax2 = 0, $qty = 1, $idproduct = 0, $price_base_type = 'HT', $info_bits = 0, $type = 0, $remise_percent = 0, $notrigger = 0, $date_start = '', $date_end = '', $array_options = [], $fk_unit = null, $pu_devise = 0, $ref_supplier = '', $rang = 0)
2349
    {
2350
        global $mysoc, $langs;
2351
2352
        dol_syslog(get_only_class($this) . "::updateline $id,$desc,$pu,$vatrate,$qty,$idproduct,$price_base_type,$info_bits,$type,$remise_percent,$notrigger,$date_start,$date_end,$fk_unit,$pu_devise,$ref_supplier", LOG_DEBUG);
2353
        include_once DOL_DOCUMENT_ROOT . '/core/lib/price.lib.php';
2354
2355
        $pu = price2num($pu);
2356
        $qty = price2num($qty);
2357
        $remise_percent = (float) price2num($remise_percent);
2358
        $pu_devise = price2num($pu_devise);
2359
2360
        // Check parameters
2361
        //if (! is_numeric($pu) || ! is_numeric($qty)) return -1;
2362
        if ($type < 0) {
2363
            return -1;
2364
        }
2365
2366
        if ($date_start && $date_end && $date_start > $date_end) {
2367
            $langs->load("errors");
2368
            $this->error = $langs->trans('ErrorStartDateGreaterEnd');
2369
            return -1;
2370
        }
2371
2372
        // Clean parameters
2373
        if (empty($vatrate)) {
2374
            $vatrate = 0;
2375
        }
2376
        if (empty($txlocaltax1)) {
2377
            $txlocaltax1 = 0;
2378
        }
2379
        if (empty($txlocaltax2)) {
2380
            $txlocaltax2 = 0;
2381
        }
2382
2383
        $txlocaltax1 = (float) price2num($txlocaltax1);
2384
        $txlocaltax2 = (float) price2num($txlocaltax2);
2385
2386
        // Calcul du total TTC et de la TVA pour la ligne a partir de
2387
        // qty, pu, remise_percent et txtva
2388
        // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2389
        // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2390
2391
        $localtaxes_type = getLocalTaxesFromRate($vatrate, 0, $mysoc, $this->thirdparty);
2392
2393
        $reg = array();
2394
2395
        // Clean vat code
2396
        $vat_src_code = '';
2397
        if (preg_match('/\((.*)\)/', (string) $vatrate, $reg)) {
2398
            $vat_src_code = $reg[1];
2399
            $vatrate = preg_replace('/\s*\(.*\)/', '', (string) $vatrate); // Remove code into vatrate.
2400
        }
2401
2402
        $tabprice = calcul_price_total($qty, $pu, $remise_percent, $vatrate, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $this->thirdparty, $localtaxes_type, 100, $this->multicurrency_tx, $pu_devise);
2403
        $total_ht  = $tabprice[0];
2404
        $total_tva = $tabprice[1];
2405
        $total_ttc = $tabprice[2];
2406
        $pu_ht  = $tabprice[3];
2407
        $pu_tva = $tabprice[4];
2408
        $pu_ttc = $tabprice[5];
2409
        $total_localtax1 = $tabprice[9];
2410
        $total_localtax2 = $tabprice[10];
2411
2412
        // MultiCurrency
2413
        $multicurrency_total_ht = $tabprice[16];
2414
        $multicurrency_total_tva = $tabprice[17];
2415
        $multicurrency_total_ttc = $tabprice[18];
2416
        $pu_ht_devise = $tabprice[19];
2417
2418
        if (empty($info_bits)) {
2419
            $info_bits = 0;
2420
        }
2421
2422
        //Fetch current line from the database and then clone the object and set it in $oldline property
2423
        $line = new SupplierInvoiceLine($this->db);
2424
        $line->fetch($id);
2425
        $line->fetch_optionals();
2426
2427
        $staticline = clone $line;
2428
2429
        if ($idproduct) {
2430
            $product = new Product($this->db);
2431
            $result = $product->fetch($idproduct);
2432
            $product_type = $product->type;
2433
        } else {
2434
            $idproduct = $staticline->fk_product;
2435
            $product_type = $type;
2436
        }
2437
2438
        $line->oldline = $staticline;
2439
        $line->context = $this->context;
2440
2441
        $line->description = $desc;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Fourn\Clas...voiceLine::$description has been deprecated: Use $desc ( Ignorable by Annotation )

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

2441
        /** @scrutinizer ignore-deprecated */ $line->description = $desc;

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...
2442
        $line->desc = $desc;
2443
2444
        $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...
2445
        $line->subprice = ($this->type == self::TYPE_CREDIT_NOTE ? -abs($pu_ht) : $pu_ht); // For credit note, unit price always negative, always positive otherwise
2446
        $line->pu_ht = $line->subprice;  // deprecated
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Fourn\Clas...lierInvoiceLine::$pu_ht has been deprecated: Use $subprice ( Ignorable by Annotation )

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

2446
        /** @scrutinizer ignore-deprecated */ $line->pu_ht = $line->subprice;  // 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...
2447
        $line->pu_ttc = ($this->type == self::TYPE_CREDIT_NOTE ? -abs($pu_ttc) : $pu_ttc); // For credit note, unit price always negative, always positive otherwise
2448
2449
        $line->remise_percent = $remise_percent;
2450
        $line->ref_supplier = $ref_supplier;
2451
2452
        $line->date_start = $date_start;
2453
        $line->date_end = $date_end;
2454
2455
        $line->vat_src_code = $vat_src_code;
2456
        $line->tva_tx = $vatrate;
0 ignored issues
show
Documentation Bug introduced by
It seems like $vatrate can also be of type string. However, the property $tva_tx 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...
2457
        $line->localtax1_tx = $txlocaltax1;
2458
        $line->localtax2_tx = $txlocaltax2;
2459
        $line->localtax1_type = empty($localtaxes_type[0]) ? 0 : $localtaxes_type[0];
2460
        $line->localtax2_type = empty($localtaxes_type[2]) ? 0 : $localtaxes_type[2];
2461
2462
        $line->total_ht = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($total_ht) : $total_ht);
2463
        $line->total_tva = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($total_tva) : $total_tva);
2464
        $line->total_localtax1 = $total_localtax1;
2465
        $line->total_localtax2 = $total_localtax2;
2466
        $line->total_ttc = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($total_ttc) : $total_ttc);
2467
2468
        $line->fk_product = $idproduct;
2469
        $line->product_type = $product_type;
2470
        $line->info_bits = $info_bits;
2471
        $line->fk_unit = $fk_unit;
2472
        $line->rang = $rang;
2473
2474
        if (is_array($array_options) && count($array_options) > 0) {
2475
            // We replace values in this->line->array_options only for entries defined into $array_options
2476
            foreach ($array_options as $key => $value) {
2477
                $line->array_options[$key] = $array_options[$key];
2478
            }
2479
        }
2480
2481
        // Multicurrency
2482
        $line->multicurrency_subprice = $pu_ht_devise;
2483
        $line->multicurrency_total_ht = $multicurrency_total_ht;
2484
        $line->multicurrency_total_tva  = $multicurrency_total_tva;
2485
        $line->multicurrency_total_ttc  = $multicurrency_total_ttc;
2486
2487
        $res = $line->update($notrigger);
2488
2489
        if ($res < 1) {
2490
            $this->errors[] = $line->error;
2491
        } else {
2492
            // Update total price into invoice record
2493
            $res = $this->update_price('1', 'auto', 0, $this->thirdparty);
2494
        }
2495
2496
        return $res;
2497
    }
2498
2499
    /**
2500
     *  Delete a detail line from database
2501
     *
2502
     *  @param  int     $rowid          Id of line to delete
2503
     *  @param  int     $notrigger      1=Does not execute triggers, 0= execute triggers
2504
     *  @return int                     Return integer <0 if KO, >0 if OK
2505
     */
2506
    public function deleteLine($rowid, $notrigger = 0)
2507
    {
2508
        if (!$rowid) {
2509
            $rowid = $this->id;
2510
        }
2511
2512
        $this->db->begin();
2513
2514
        // Free the discount linked to a line of invoice
2515
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'societe_remise_except';
2516
        $sql .= ' SET fk_invoice_supplier_line = NULL';
2517
        $sql .= ' WHERE fk_invoice_supplier_line = ' . ((int) $rowid);
2518
2519
        dol_syslog(get_only_class($this) . "::deleteline", LOG_DEBUG);
2520
        $result = $this->db->query($sql);
2521
        if (!$result) {
2522
            $this->error = $this->db->error();
2523
            $this->db->rollback();
2524
            return -2;
2525
        }
2526
2527
        $line = new SupplierInvoiceLine($this->db);
2528
2529
        if ($line->fetch($rowid) < 1) {
2530
            return -1;
2531
        }
2532
2533
        $res = $line->delete($notrigger);
2534
2535
        if ($res < 1) {
2536
            $this->errors[] = $line->error;
2537
            $this->db->rollback();
2538
            return -3;
2539
        } else {
2540
            $res = $this->update_price(1);
2541
2542
            if ($res > 0) {
2543
                $this->db->commit();
2544
                return 1;
2545
            } else {
2546
                $this->db->rollback();
2547
                $this->error = $this->db->lasterror();
2548
                return -4;
2549
            }
2550
        }
2551
    }
2552
2553
2554
    /**
2555
     *  Loads the info order information into the invoice object
2556
     *
2557
     *  @param  int     $id         Id of the invoice to load
2558
     *  @return void
2559
     */
2560
    public function info($id)
2561
    {
2562
        $sql = 'SELECT c.rowid, datec, tms as datem, ';
2563
        $sql .= ' fk_user_author, fk_user_modif, fk_user_valid';
2564
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'facture_fourn as c';
2565
        $sql .= ' WHERE c.rowid = ' . ((int) $id);
2566
2567
        $result = $this->db->query($sql);
2568
        if ($result) {
2569
            if ($this->db->num_rows($result)) {
2570
                $obj = $this->db->fetch_object($result);
2571
2572
                $this->id = $obj->rowid;
2573
2574
                $this->user_creation_id = $obj->fk_user_author;
2575
                $this->user_validation_id = $obj->fk_user_valid;
2576
                $this->user_modification_id = $obj->fk_user_modif;
2577
                $this->date_creation     = $this->db->jdate($obj->datec);
2578
                $this->date_modification = $this->db->jdate($obj->datem);
2579
                //$this->date_validation   = $obj->datev; // This field is not available. Should be store into log table and using this function should be replaced with showing content of log (like for supplier orders)
2580
            }
2581
            $this->db->free($result);
2582
        } else {
2583
            dol_print_error($this->db);
2584
        }
2585
    }
2586
2587
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2588
    /**
2589
     *  Return list of replaceable invoices
2590
     *  Status valid or abandoned for other reason + not paid + no payment + not already replaced
2591
     *
2592
     *  @param      int     $socid      Thirdparty id
2593
     *  @return     array|int           Table of invoices ('id'=>id, 'ref'=>ref, 'status'=>status, 'paymentornot'=>0/1)
2594
     *                                  <0 if error
2595
     */
2596
    public function list_replacable_supplier_invoices($socid = 0)
2597
    {
2598
		// phpcs:enable
2599
        global $conf;
2600
2601
        $return = array();
2602
2603
        $sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut,";
2604
        $sql .= " ff.rowid as rowidnext";
2605
        $sql .= " FROM " . MAIN_DB_PREFIX . "facture_fourn as f";
2606
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "paiementfourn_facturefourn as pf ON f.rowid = pf.fk_facturefourn";
2607
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "facture_fourn as ff ON f.rowid = ff.fk_facture_source";
2608
        $sql .= " WHERE (f.fk_statut = " . self::STATUS_VALIDATED . " OR (f.fk_statut = " . self::STATUS_ABANDONED . " AND f.close_code = '" . self::CLOSECODE_ABANDONED . "'))";
2609
        $sql .= " AND f.entity = " . $conf->entity;
2610
        $sql .= " AND f.paye = 0"; // Pas classee payee completement
2611
        $sql .= " AND pf.fk_paiementfourn IS NULL"; // Aucun paiement deja fait
2612
        $sql .= " AND ff.fk_statut IS NULL"; // Renvoi vrai si pas facture de replacement
2613
        if ($socid > 0) {
2614
            $sql .= " AND f.fk_soc = " . ((int) $socid);
2615
        }
2616
        $sql .= " ORDER BY f.ref";
2617
2618
        dol_syslog(get_only_class($this) . "::list_replacable_supplier_invoices", LOG_DEBUG);
2619
        $resql = $this->db->query($sql);
2620
        if ($resql) {
2621
            while ($obj = $this->db->fetch_object($resql)) {
2622
                $return[$obj->rowid] = array(
2623
                    'id' => $obj->rowid,
2624
                    'ref' => $obj->ref,
2625
                    'status' => $obj->fk_statut
2626
                );
2627
            }
2628
            //print_r($return);
2629
            return $return;
2630
        } else {
2631
            $this->error = $this->db->error();
2632
            return -1;
2633
        }
2634
    }
2635
2636
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2637
    /**
2638
     *  Return list of qualifying invoices for correction by credit note
2639
     *  Invoices that respect the following rules are returned:
2640
     *  (validated + payment in progress) or classified (paid in full or paid in part) + not already replaced + not already having
2641
     *
2642
     *  @param      int     $socid      Thirdparty id
2643
     *  @return     array|int           Table of invoices ($id => array('ref'=>,'paymentornot'=>,'status'=>,'paye'=>)
2644
     *                                  <0 if error
2645
     */
2646
    public function list_qualified_avoir_supplier_invoices($socid = 0)
2647
    {
2648
		// phpcs:enable
2649
        global $conf;
2650
2651
        $return = array();
2652
2653
        $sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut, f.type, f.subtype, f.paye, pf.fk_paiementfourn";
2654
        $sql .= " FROM " . MAIN_DB_PREFIX . "facture_fourn as f";
2655
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "paiementfourn_facturefourn as pf ON f.rowid = pf.fk_facturefourn";
2656
        $sql .= " WHERE f.entity = " . $conf->entity;
2657
        $sql .= " AND f.fk_statut in (" . self::STATUS_VALIDATED . "," . self::STATUS_CLOSED . ")";
2658
        $sql .= " AND NOT EXISTS (SELECT rowid from " . MAIN_DB_PREFIX . "facture_fourn as ff WHERE f.rowid = ff.fk_facture_source";
2659
        $sql .= " AND ff.type=" . self::TYPE_REPLACEMENT . ")";
2660
        $sql .= " AND f.type != " . self::TYPE_CREDIT_NOTE; // Type non 2 si facture non avoir
2661
        if ($socid > 0) {
2662
            $sql .= " AND f.fk_soc = " . ((int) $socid);
2663
        }
2664
        $sql .= " ORDER BY f.ref";
2665
2666
        dol_syslog(get_only_class($this) . "::list_qualified_avoir_supplier_invoices", LOG_DEBUG);
2667
        $resql = $this->db->query($sql);
2668
        if ($resql) {
2669
            while ($obj = $this->db->fetch_object($resql)) {
2670
                $qualified = 0;
2671
                if ($obj->fk_statut == self::STATUS_VALIDATED) {
2672
                    $qualified = 1;
2673
                }
2674
                if ($obj->fk_statut == self::STATUS_CLOSED) {
2675
                    $qualified = 1;
2676
                }
2677
                if ($qualified) {
2678
                    $paymentornot = ($obj->fk_paiementfourn ? 1 : 0);
2679
                    $return[$obj->rowid] = array('ref' => $obj->ref, 'status' => $obj->fk_statut, 'type' => $obj->type, 'paye' => $obj->paye, 'paymentornot' => $paymentornot);
2680
                }
2681
            }
2682
2683
            return $return;
2684
        } else {
2685
            $this->error = $this->db->error();
2686
            return -1;
2687
        }
2688
    }
2689
2690
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2691
    /**
2692
     *  Load indicators for dashboard (this->nbtodo and this->nbtodolate)
2693
     *
2694
     *  @param      User    $user       Object user
2695
     *  @return WorkboardResponse|int Return integer <0 if KO, WorkboardResponse if OK
2696
     */
2697
    public function load_board($user)
2698
    {
2699
		// phpcs:enable
2700
        global $conf, $langs;
2701
2702
        $sql = 'SELECT ff.rowid, ff.date_lim_reglement as datefin, ff.fk_statut as status, ff.total_ht, ff.total_ttc';
2703
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'facture_fourn as ff';
2704
        if (!$user->hasRight("societe", "client", "voir") && !$user->socid) {
2705
            $sql .= " JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON ff.fk_soc = sc.fk_soc AND sc.fk_user = " . ((int) $user->id);
2706
        }
2707
        $sql .= ' WHERE ff.paye = 0';
2708
        $sql .= " AND ff.fk_statut IN (" . self::STATUS_VALIDATED . ")";
2709
        $sql .= " AND ff.entity = " . $conf->entity;
2710
        if ($user->socid) {
2711
            $sql .= ' AND ff.fk_soc = ' . ((int) $user->socid);
2712
        }
2713
2714
        $resql = $this->db->query($sql);
2715
        if ($resql) {
2716
            $langs->load("bills");
2717
            $now = dol_now();
2718
2719
            $response = new WorkboardResponse();
2720
            $response->warning_delay = $conf->facture->fournisseur->warning_delay / 60 / 60 / 24;
2721
            $response->label = $langs->trans("SupplierBillsToPay");
2722
            $response->labelShort = $langs->trans("StatusToPay");
2723
2724
            $response->url = constant('BASE_URL') . '/fourn/facture/list.php?search_status=1&mainmenu=billing&leftmenu=suppliers_bills';
2725
            $response->img = img_object($langs->trans("Bills"), "bill");
2726
2727
            $facturestatic = new FactureFournisseur($this->db);
2728
2729
            while ($obj = $this->db->fetch_object($resql)) {
2730
                $facturestatic->date_echeance = $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_echeance 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...
2731
                $facturestatic->statut = $obj->status;  // For backward compatibility
2732
                $facturestatic->status = $obj->status;
2733
2734
                $response->nbtodo++;
2735
                $response->total += $obj->total_ht;
2736
2737
                if ($facturestatic->hasDelay()) {
2738
                    $response->nbtodolate++;
2739
                    $response->url_late = constant('BASE_URL') . '/fourn/facture/list.php?search_option=late&mainmenu=billing&leftmenu=suppliers_bills';
2740
                }
2741
            }
2742
2743
            $this->db->free($resql);
2744
            return $response;
2745
        } else {
2746
            dol_print_error($this->db);
2747
            $this->error = $this->db->error();
2748
            return -1;
2749
        }
2750
    }
2751
2752
    /**
2753
     * getTooltipContentArray
2754
     *
2755
     * @param array{moretitle?:string} $params ex option, infologin
2756
     * @since v18
2757
     * @return array{picto:string,ref?:string,refsupplier?:string,label?:string,date?:string,date_echeance?:string,amountht?:string,total_ht?:string,totaltva?:string,amountlt1?:string,amountlt2?:string,amountrevenustamp?:string,totalttc?:string}
2758
     */
2759
    public function getTooltipContentArray($params)
2760
    {
2761
        global $conf, $langs, $mysoc;
2762
2763
        $langs->load('bills');
2764
2765
        $datas = [];
2766
        $moretitle = $params['moretitle'] ?? '';
2767
2768
        $picto = $this->picto;
2769
        if ($this->type == self::TYPE_REPLACEMENT) {
2770
            $picto .= 'r'; // Replacement invoice
2771
        }
2772
        if ($this->type == self::TYPE_CREDIT_NOTE) {
2773
            $picto .= 'a'; // Credit note
2774
        }
2775
        if ($this->type == self::TYPE_DEPOSIT) {
2776
            $picto .= 'd'; // Deposit invoice
2777
        }
2778
2779
        $datas['picto'] = img_picto('', $picto) . ' <u class="paddingrightonly">' . $langs->trans("SupplierInvoice") . '</u>';
2780
        if ($this->type == self::TYPE_REPLACEMENT) {
2781
            $datas['picto'] .= '<u class="paddingrightonly">' . $langs->transnoentitiesnoconv("InvoiceReplace") . '</u>';
2782
        } elseif ($this->type == self::TYPE_CREDIT_NOTE) {
2783
            $datas['picto'] .= '<u class="paddingrightonly">' . $langs->transnoentitiesnoconv("CreditNote") . '</u>';
2784
        } elseif ($this->type == self::TYPE_DEPOSIT) {
2785
            $datas['picto'] .= '<u class="paddingrightonly">' . $langs->transnoentitiesnoconv("Deposit") . '</u>';
2786
        }
2787
        if (isset($this->status)) {
2788
            $alreadypaid = -1;
2789
            if (isset($this->totalpaid)) {
2790
                $alreadypaid = $this->totalpaid;
2791
            }
2792
2793
            $datas['picto'] .= ' ' . $this->getLibStatut(5, $alreadypaid);
2794
        }
2795
        if ($moretitle) {
2796
            $datas['picto'] .= ' - ' . $moretitle;
2797
        }
2798
        if (!empty($this->ref)) {
2799
            $datas['ref'] = '<br><b>' . $langs->trans('Ref') . ':</b> ' . $this->ref;
2800
        }
2801
        if (!empty($this->ref_supplier)) {
2802
            $datas['refsupplier'] = '<br><b>' . $langs->trans('RefSupplier') . ':</b> ' . $this->ref_supplier;
2803
        }
2804
        if (!empty($this->label)) {
2805
            $datas['label'] = '<br><b>' . $langs->trans('Label') . ':</b> ' . $this->label;
2806
        }
2807
        if (!empty($this->date)) {
2808
            $datas['date'] = '<br><b>' . $langs->trans('Date') . ':</b> ' . dol_print_date($this->date, 'day');
2809
        }
2810
        if (!empty($this->date_echeance)) {
2811
            $datas['date_echeance'] = '<br><b>' . $langs->trans('DateDue') . ':</b> ' . dol_print_date($this->date_echeance, 'day');
2812
        }
2813
        if (!empty($this->total_ht)) {
2814
            $datas['amountht'] = '<br><b>' . $langs->trans('AmountHT') . ':</b> ' . price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
2815
        }
2816
        if (!empty($this->total_tva)) {
2817
            $datas['totaltva'] = '<br><b>' . $langs->trans('AmountVAT') . ':</b> ' . price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
2818
        }
2819
        if (!empty($this->total_localtax1) && $this->total_localtax1 != 0) {
2820
            // We keep test != 0 because $this->total_localtax1 can be '0.00000000'
2821
            $datas['amountlt1'] = '<br><b>' . $langs->transcountry('AmountLT1', $mysoc->country_code) . ':</b> ' . price($this->total_localtax1, 0, $langs, 0, -1, -1, $conf->currency);
2822
        }
2823
        if (!empty($this->total_localtax2) && $this->total_localtax2 != 0) {
2824
            $datas['amountlt2'] = '<br><b>' . $langs->transcountry('AmountLT2', $mysoc->country_code) . ':</b> ' . price($this->total_localtax2, 0, $langs, 0, -1, -1, $conf->currency);
2825
        }
2826
        if (!empty($this->revenuestamp)) {
2827
            $datas['amountrevenustamp'] = '<br><b>' . $langs->trans('RevenueStamp') . ':</b> ' . price($this->revenuestamp, 0, $langs, 0, -1, -1, $conf->currency);
2828
        }
2829
        if (!empty($this->total_ttc)) {
2830
            $datas['totalttc'] = '<br><b>' . $langs->trans('AmountTTC') . ':</b> ' . price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
2831
        }
2832
        return $datas;
2833
    }
2834
2835
    /**
2836
     *  Return clicable name (with picto eventually)
2837
     *
2838
     *  @param      int     $withpicto                  0=No picto, 1=Include picto into link, 2=Only picto
2839
     *  @param      string  $option                     Where point the link
2840
     *  @param      int     $max                        Max length of shown ref
2841
     *  @param      int     $short                      1=Return just URL
2842
     *  @param      string  $moretitle                  Add more text to title tooltip
2843
     *  @param      int     $notooltip                  1=Disable tooltip
2844
     *  @param      int     $save_lastsearch_value      -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
2845
     *  @param      int     $addlinktonotes             Add link to show notes
2846
     *  @return     string                              String with URL
2847
     */
2848
    public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $moretitle = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = 0)
2849
    {
2850
        global $langs, $conf, $user, $hookmanager;
2851
2852
        $result = '';
2853
2854
        if ($option == 'withdraw') {
2855
            $url = constant('BASE_URL') . '/compta/facture/prelevement.php?facid=' . $this->id . '&type=bank-transfer';
2856
        } elseif ($option == 'document') {
2857
            $url = constant('BASE_URL') . '/fourn/facture/document.php?facid=' . $this->id;
2858
        } else {
2859
            $url = constant('BASE_URL') . '/fourn/facture/card.php?facid=' . $this->id;
2860
        }
2861
2862
        if ($short) {
2863
            return $url;
2864
        }
2865
2866
        if ($option !== 'nolink') {
2867
            // Add param to save lastsearch_values or not
2868
            $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
2869
            if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
2870
                $add_save_lastsearch_values = 1;
2871
            }
2872
            if ($add_save_lastsearch_values) {
2873
                $url .= '&save_lastsearch_values=1';
2874
            }
2875
        }
2876
2877
        $picto = $this->picto;
2878
        if ($this->type == self::TYPE_REPLACEMENT) {
2879
            $picto .= 'r'; // Replacement invoice
2880
        }
2881
        if ($this->type == self::TYPE_CREDIT_NOTE) {
2882
            $picto .= 'a'; // Credit note
2883
        }
2884
        if ($this->type == self::TYPE_DEPOSIT) {
2885
            $picto .= 'd'; // Deposit invoice
2886
        }
2887
2888
        $params = [
2889
            'id' => $this->id,
2890
            'objecttype' => $this->element,
2891
            'option' => $option,
2892
            'moretitle' => $moretitle,
2893
        ];
2894
        $classfortooltip = 'classfortooltip';
2895
        $dataparams = '';
2896
        if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
2897
            $classfortooltip = 'classforajaxtooltip';
2898
            $dataparams = ' data-params="' . dol_escape_htmltag(json_encode($params)) . '"';
2899
            $label = '';
2900
        } else {
2901
            $label = implode($this->getTooltipContentArray($params));
2902
        }
2903
2904
        $ref = $this->ref;
2905
        if (empty($ref)) {
2906
            $ref = $this->id;
2907
        }
2908
2909
        $linkclose = '';
2910
        if (empty($notooltip)) {
2911
            if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2912
                $label = $langs->trans("ShowSupplierInvoice");
2913
                $linkclose .= ' alt="' . dol_escape_htmltag($label, 1) . '"';
2914
            }
2915
            $linkclose .= ($label ? ' title="' . dol_escape_htmltag($label, 1) . '"' : ' title="tocomplete"');
2916
            $linkclose .= $dataparams . ' class="' . $classfortooltip . '"';
2917
        }
2918
2919
        $linkstart = '<a href="' . $url . '"';
2920
        $linkstart .= $linkclose . '>';
2921
        $linkend = '</a>';
2922
2923
        $result .= $linkstart;
2924
        if ($withpicto) {
2925
            $result .= img_object(($notooltip ? '' : $label), ($picto ? $picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="' . (($withpicto != 2) ? 'paddingright ' : '') . '"'), 0, 0, $notooltip ? 0 : 1);
2926
        }
2927
        if ($withpicto != 2) {
2928
            $result .= ($max ? dol_trunc($ref, $max) : $ref);
2929
        }
2930
        $result .= $linkend;
2931
2932
        if ($addlinktonotes) {
2933
            $txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
2934
            if ($txttoshow) {
2935
                $notetoshow = $langs->trans("ViewPrivateNote") . ':<br>' . dol_string_nohtmltag($txttoshow, 1);
2936
                $result .= ' <span class="note inline-block">';
2937
                $result .= '<a href="' . constant('BASE_URL') . '/fourn/facture/note.php?id=' . $this->id . '" class="classfortooltip" title="' . dol_escape_htmltag($notetoshow) . '">';
2938
                $result .= img_picto('', 'note');
2939
                $result .= '</a>';
2940
                $result .= '</span>';
2941
            }
2942
        }
2943
        global $action;
2944
        $hookmanager->initHooks(array($this->element . 'dao'));
2945
        $parameters = array('id' => $this->id, 'getnomurl' => &$result);
2946
        $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2947
        if ($reshook > 0) {
2948
            $result = $hookmanager->resPrint;
2949
        } else {
2950
            $result .= $hookmanager->resPrint;
2951
        }
2952
        return $result;
2953
    }
2954
2955
    /**
2956
     *      Return next reference of supplier invoice not already used (or last reference)
2957
     *      according to numbering module defined into constant INVOICE_SUPPLIER_ADDON_NUMBER
2958
     *
2959
     *      @param     Societe      $soc        Thirdparty object
2960
     *      @param    string        $mode       'next' for next value or 'last' for last value
2961
     *      @return   string|-1                 Returns free reference or last reference, or '' or -1 if error
0 ignored issues
show
Documentation Bug introduced by
The doc comment string|-1 at position 2 could not be parsed: Unknown type name '-1' at position 2 in string|-1.
Loading history...
2962
     */
2963
    public function getNextNumRef($soc, $mode = 'next')
2964
    {
2965
        global $db, $langs, $conf;
2966
        $langs->load("orders");
2967
2968
        // Clean parameters (if not defined or using deprecated value)
2969
        if (!getDolGlobalString('INVOICE_SUPPLIER_ADDON_NUMBER')) {
2970
            $conf->global->INVOICE_SUPPLIER_ADDON_NUMBER = 'mod_facture_fournisseur_cactus';
2971
        }
2972
2973
        $mybool = false;
2974
2975
        $file = getDolGlobalString('INVOICE_SUPPLIER_ADDON_NUMBER') . ".php";
2976
        $classname = getDolGlobalString('INVOICE_SUPPLIER_ADDON_NUMBER');
2977
2978
        // Include file with class
2979
        $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
2980
2981
        foreach ($dirmodels as $reldir) {
2982
            $dir = dol_buildpath($reldir . "core/modules/supplier_invoice/");
2983
2984
            // Load file with numbering class (if found)
2985
            $mybool = ((bool) @include_once $dir . $file) || $mybool;
2986
        }
2987
2988
        if (!$mybool) {
2989
            dol_print_error(null, "Failed to include file " . $file);
2990
            return '';
2991
        }
2992
2993
        $obj = new $classname();
2994
        '@phan-var-force ModeleNumRefSuppliersInvoices $obj';
2995
        $numref = "";
2996
        $numref = $obj->getNextValue($soc, $this, $mode);
2997
2998
        if ($numref != "") {
2999
            return $numref;
3000
        } else {
3001
            $this->error = $obj->error;
3002
            return -1;
3003
        }
3004
    }
3005
3006
3007
    /**
3008
     *  Initialise an instance with random values.
3009
     *  Used to build previews or test instances.
3010
     *  id must be 0 if object instance is a specimen.
3011
     *
3012
     *  @param  string      $option     ''=Create a specimen invoice with lines, 'nolines'=No lines
3013
     *  @return int
3014
     */
3015
    public function initAsSpecimen($option = '')
3016
    {
3017
        global $langs, $conf;
3018
3019
        $now = dol_now();
3020
3021
        // Load array of products prodids
3022
        $num_prods = 0;
3023
        $prodids = array();
3024
3025
        $sql = "SELECT rowid";
3026
        $sql .= " FROM " . MAIN_DB_PREFIX . "product";
3027
        $sql .= " WHERE entity IN (" . getEntity('product') . ")";
3028
        $sql .= $this->db->plimit(100);
3029
3030
        $resql = $this->db->query($sql);
3031
        if ($resql) {
3032
            $num_prods = $this->db->num_rows($resql);
3033
            $i = 0;
3034
            while ($i < $num_prods) {
3035
                $i++;
3036
                $row = $this->db->fetch_row($resql);
3037
                $prodids[$i] = $row[0];
3038
            }
3039
        }
3040
3041
        // Initialise parameters
3042
        $this->id = 0;
3043
        $this->ref = 'SPECIMEN';
3044
        $this->ref_supplier = 'SUPPLIER_REF_SPECIMEN';
3045
        $this->specimen = 1;
3046
        $this->socid = 1;
3047
        $this->date = $now;
3048
        $this->date_lim_reglement = $this->date + 3600 * 24 * 30;
3049
        $this->cond_reglement_code = 'RECEP';
3050
        $this->mode_reglement_code = 'CHQ';
3051
3052
        $this->note_public = 'This is a comment (public)';
3053
        $this->note_private = 'This is a comment (private)';
3054
3055
        $this->multicurrency_tx = 1;
3056
        $this->multicurrency_code = $conf->currency;
3057
3058
        $xnbp = 0;
3059
        if (empty($option) || $option != 'nolines') {
3060
            // Lines
3061
            $nbp = 5;
3062
            while ($xnbp < $nbp) {
3063
                $line = new SupplierInvoiceLine($this->db);
3064
                $line->desc = $langs->trans("Description") . " " . $xnbp;
3065
                $line->qty = 1;
3066
                $line->subprice = 100;
3067
                $line->pu_ht = $line->subprice; // the canelle template use pu_ht and not subprice
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Fourn\Clas...lierInvoiceLine::$pu_ht has been deprecated: Use $subprice ( Ignorable by Annotation )

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

3067
                /** @scrutinizer ignore-deprecated */ $line->pu_ht = $line->subprice; // the canelle template use pu_ht and not subprice

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...
3068
                $line->price = 100;
0 ignored issues
show
Bug Best Practice introduced by
The property price does not exist on Dolibarr\Code\Fourn\Classes\SupplierInvoiceLine. Since you implemented __set, consider adding a @property annotation.
Loading history...
3069
                $line->tva_tx = 19.6;
3070
                $line->localtax1_tx = 0;
3071
                $line->localtax2_tx = 0;
3072
                if ($xnbp == 2) {
3073
                    $line->total_ht = 50;
3074
                    $line->total_ttc = 59.8;
3075
                    $line->total_tva = 9.8;
3076
                    $line->remise_percent = 50;
3077
                } else {
3078
                    $line->total_ht = 100;
3079
                    $line->total_ttc = 119.6;
3080
                    $line->total_tva = 19.6;
3081
                    $line->remise_percent = 0;
3082
                }
3083
3084
                if ($num_prods > 0) {
3085
                    $prodid = mt_rand(1, $num_prods);
3086
                    $line->fk_product = $prodids[$prodid];
3087
                }
3088
                $line->product_type = 0;
3089
3090
                $this->lines[$xnbp] = $line;
3091
3092
                $this->total_ht       += $line->total_ht;
3093
                $this->total_tva      += $line->total_tva;
3094
                $this->total_ttc      += $line->total_ttc;
3095
3096
                $xnbp++;
3097
            }
3098
        }
3099
3100
        $this->total_ht = $xnbp * 100;
3101
        $this->total_tva = $xnbp * 19.6;
3102
        $this->total_ttc = $xnbp * 119.6;
3103
3104
        return 1;
3105
    }
3106
3107
    /**
3108
     *      Load indicators for dashboard (this->nbtodo and this->nbtodolate)
3109
     *
3110
     *      @return         int     Return integer <0 if KO, >0 if OK
3111
     */
3112
    public function loadStateBoard()
3113
    {
3114
        global $conf, $user;
3115
3116
        $this->nb = array();
3117
3118
        $clause = "WHERE";
3119
3120
        $sql = "SELECT count(f.rowid) as nb";
3121
        $sql .= " FROM " . MAIN_DB_PREFIX . "facture_fourn as f";
3122
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON f.fk_soc = s.rowid";
3123
        if (!$user->hasRight("societe", "client", "voir") && !$user->socid) {
3124
            $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3125
            $sql .= " WHERE sc.fk_user = " . ((int) $user->id);
3126
            $clause = "AND";
3127
        }
3128
        $sql .= " " . $clause . " f.entity = " . $conf->entity;
3129
3130
        $resql = $this->db->query($sql);
3131
        if ($resql) {
3132
            while ($obj = $this->db->fetch_object($resql)) {
3133
                $this->nb["supplier_invoices"] = $obj->nb;
3134
            }
3135
            $this->db->free($resql);
3136
            return 1;
3137
        } else {
3138
            dol_print_error($this->db);
3139
            $this->error = $this->db->error();
3140
            return -1;
3141
        }
3142
    }
3143
3144
    /**
3145
     *  Load an object from its id and create a new one in database
3146
     *
3147
     *  @param      User    $user           User that clone
3148
     *  @param      int     $fromid         Id of object to clone
3149
     *  @param      int     $invertdetail   Reverse sign of amounts for lines
3150
     *  @return     int                     New id of clone
3151
     */
3152
    public function createFromClone(User $user, $fromid, $invertdetail = 0)
3153
    {
3154
        global $conf, $langs;
3155
3156
        $error = 0;
3157
3158
        $object = new FactureFournisseur($this->db);
3159
3160
        $this->db->begin();
3161
3162
        // Load source object
3163
        $object->fetch($fromid);
3164
        $object->id = 0;
3165
        $object->statut = self::STATUS_DRAFT;   // For backward compatibility
3166
        $object->status = self::STATUS_DRAFT;
3167
3168
        $object->fetch_thirdparty(); // We need it to recalculate VAT localtaxes according to main sale taxes and vendor
3169
3170
        // Clear fields
3171
        $object->ref_supplier       = (empty($this->ref_supplier) ? $langs->trans("CopyOf") . ' ' . $object->ref_supplier : $this->ref_supplier);
3172
        $object->author             = $user->id;  // FIXME? user_validation_id is replacement for author
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Fourn\Clas...ureFournisseur::$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

3172
        /** @scrutinizer ignore-deprecated */ $object->author             = $user->id;  // FIXME? user_validation_id is replacement for 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...
3173
        $object->user_validation_id = 0;  // FIXME?  user_validation_id is replacement for author
3174
        $object->fk_facture_source  = 0;
3175
        $object->date_creation      = '';
3176
        $object->date_validation    = '';
3177
        $object->date               = (empty($this->date) ? dol_now() : $this->date);
3178
        $object->ref_client         = '';
3179
        $object->close_code         = '';
3180
        $object->close_note         = '';
3181
        if (getDolGlobalInt('MAIN_DONT_KEEP_NOTE_ON_CLONING') == 1) {
3182
            $object->note_private = '';
3183
            $object->note_public = '';
3184
        }
3185
3186
        $object->date_echeance = $object->calculate_date_lim_reglement();
0 ignored issues
show
Documentation Bug introduced by
It seems like $object->calculate_date_lim_reglement() can also be of type string. However, the property $date_echeance 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...
3187
3188
        // Loop on each line of new invoice
3189
        foreach ($object->lines as $i => $line) {
3190
            if (isset($object->lines[$i]->info_bits) && ($object->lines[$i]->info_bits & 0x02) == 0x02) {   // We do not clone line of discounts
3191
                unset($object->lines[$i]);
3192
            }
3193
        }
3194
3195
        // Create clone
3196
        $object->context['createfromclone'] = 'createfromclone';
3197
        $result = $object->create($user);
3198
3199
        // Other options
3200
        if ($result < 0) {
3201
            $this->error = $object->error;
3202
            $this->errors = $object->errors;
3203
            $error++;
3204
        }
3205
3206
        if (!$error) {
3207
        }
3208
3209
        unset($object->context['createfromclone']);
3210
3211
        // End
3212
        if (!$error) {
3213
            $this->db->commit();
3214
            return $object->id;
3215
        } else {
3216
            $this->db->rollback();
3217
            return -1;
3218
        }
3219
    }
3220
3221
    /**
3222
     *  Create a document onto disk according to template model.
3223
     *
3224
     *  @param      string      $modele         Force template to use ('' to not force)
3225
     *  @param      Translate   $outputlangs    Object lang a utiliser pour traduction
3226
     *  @param      int         $hidedetails    Hide details of lines
3227
     *  @param      int         $hidedesc       Hide description
3228
     *  @param      int         $hideref        Hide ref
3229
     *  @param      ?array<string,mixed>    $moreparams     Array to provide more information
3230
     *  @return     int<-1,1>                   Return integer <0 if KO, 0 if nothing done, >0 if OK
3231
     */
3232
    public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3233
    {
3234
        global $langs;
3235
3236
        $langs->load("suppliers");
3237
        $outputlangs->load("products");
3238
3239
        // Set the model on the model name to use
3240
        if (empty($modele)) {
3241
            if (getDolGlobalString('INVOICE_SUPPLIER_ADDON_PDF')) {
3242
                $modele = getDolGlobalString('INVOICE_SUPPLIER_ADDON_PDF');
3243
            } else {
3244
                $modele = ''; // No default value. For supplier invoice, we allow to disable all PDF generation
3245
            }
3246
        }
3247
3248
        if (empty($modele)) {
3249
            return 0;
3250
        } else {
3251
            $modelpath = "core/modules/supplier_invoice/doc/";
3252
3253
            return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3254
        }
3255
    }
3256
3257
    /**
3258
     * Returns the rights used for this class
3259
     * @return int
3260
     */
3261
    public function getRights()
3262
    {
3263
        global $user;
3264
3265
        return $user->hasRight("fournisseur", "facture");
3266
    }
3267
3268
    /**
3269
     * Function used to replace a thirdparty id with another one.
3270
     *
3271
     * @param   DoliDB  $dbs        Database handler, because function is static we name it $dbs not $db to avoid breaking coding test
3272
     * @param   int     $origin_id  Old thirdparty id
3273
     * @param   int     $dest_id    New thirdparty id
3274
     * @return  bool
3275
     */
3276
    public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
3277
    {
3278
        $tables = array(
3279
            'facture_fourn'
3280
        );
3281
3282
        return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
3283
    }
3284
3285
    /**
3286
     * Function used to replace a product id with another one.
3287
     *
3288
     * @param DoliDB $db Database handler
3289
     * @param int $origin_id Old product id
3290
     * @param int $dest_id New product id
3291
     * @return bool
3292
     */
3293
    public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
3294
    {
3295
        $tables = array(
3296
            'facture_fourn_det'
3297
        );
3298
3299
        return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
3300
    }
3301
3302
    /**
3303
     * Is the payment of the supplier invoice having a delay?
3304
     *
3305
     * @return bool
3306
     */
3307
    public function hasDelay()
3308
    {
3309
        global $conf;
3310
3311
        $now = dol_now();
3312
3313
        if (!$this->date_echeance) {
3314
            return false;
3315
        }
3316
3317
        $status = isset($this->status) ? $this->status : $this->statut;
3318
3319
        return ($status == self::STATUS_VALIDATED) && ($this->date_echeance < ($now - $conf->facture->fournisseur->warning_delay));
3320
    }
3321
3322
    /**
3323
     * Is credit note used
3324
     *
3325
     * @return bool
3326
     */
3327
    public function isCreditNoteUsed()
3328
    {
3329
        $isUsed = false;
3330
3331
        $sql = "SELECT fk_invoice_supplier FROM " . MAIN_DB_PREFIX . "societe_remise_except WHERE fk_invoice_supplier_source = " . ((int) $this->id);
3332
        $resql = $this->db->query($sql);
3333
        if (!empty($resql)) {
3334
            $obj = $this->db->fetch_object($resql);
3335
            if (!empty($obj->fk_invoice_supplier)) {
3336
                $isUsed = true;
3337
            }
3338
        }
3339
3340
        return $isUsed;
3341
    }
3342
    /**
3343
     *  Return clicable link of object (with eventually picto)
3344
     *
3345
     *  @param      string      $option                 Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
3346
     *  @param      ?array{selected?:int<0,1>}  $arraydata  Array of data
0 ignored issues
show
Documentation Bug introduced by
The doc comment ?array{selected?:int<0,1>} at position 4 could not be parsed: Expected '}' at position 4, but found 'int'.
Loading history...
3347
     *  @return     string                              HTML Code for Kanban thumb.
3348
     */
3349
    public function getKanbanView($option = '', $arraydata = null)
3350
    {
3351
        global $langs;
3352
3353
        $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
3354
3355
        $picto = $this->picto;
3356
        if ($this->type == self::TYPE_REPLACEMENT) {
3357
            $picto .= 'r'; // Replacement invoice
3358
        }
3359
        if ($this->type == self::TYPE_CREDIT_NOTE) {
3360
            $picto .= 'a'; // Credit note
3361
        }
3362
        if ($this->type == self::TYPE_DEPOSIT) {
3363
            $picto .= 'd'; // Deposit invoice
3364
        }
3365
3366
        $return = '<div class="box-flex-item box-flex-grow-zero">';
3367
        $return .= '<div class="info-box info-box-sm">';
3368
        $return .= '<span class="info-box-icon bg-infobox-action">';
3369
        $return .= img_picto('', $picto);
3370
        $return .= '</span>';
3371
        $return .= '<div class="info-box-content">';
3372
        $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . (method_exists($this, 'getNomUrl') ? $this->getNomUrl(1) : $this->ref) . '</span>';
3373
        if ($selected >= 0) {
3374
            $return .= '<input id="cb' . $this->id . '" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="' . $this->id . '"' . ($selected ? ' checked="checked"' : '') . '>';
3375
        }
3376
        if (!empty($arraydata['thirdparty'])) {
3377
            $return .= '<br><span class="info-box-label">' . $arraydata['thirdparty'] . '</span>';
3378
        }
3379
        if (property_exists($this, 'date')) {
3380
            $return .= '<br><span class="info-box-label">' . dol_print_date($this->date, 'day') . '</span>';
3381
        }
3382
        if (property_exists($this, 'total_ht')) {
3383
            $return .= ' &nbsp; <span class="info-box-label amount" title="' . dol_escape_htmltag($langs->trans("AmountHT")) . '">' . price($this->total_ht);
3384
            $return .= ' ' . $langs->trans("HT");
3385
            $return .= '</span>';
3386
        }
3387
        if (method_exists($this, 'getLibStatut')) {
3388
            $alreadypaid = (empty($arraydata['alreadypaid']) ? 0 : $arraydata['alreadypaid']);
3389
            $return .= '<br><div class="info-box-status">' . $this->getLibStatut(3, $alreadypaid) . '</div>';
3390
        }
3391
        $return .= '</div>';
3392
        $return .= '</div>';
3393
        $return .= '</div>';
3394
        return $return;
3395
    }
3396
3397
    /**
3398
     *  Change the option VAT reverse charge
3399
     *
3400
     *  @param      int     $vatreversecharge   0 = Off, 1 = On
3401
     *  @return     int                         1 if OK, 0 if KO
3402
     */
3403
    public function setVATReverseCharge($vatreversecharge)
3404
    {
3405
        if (!$this->table_element) {
3406
            dol_syslog(get_only_class($this) . "::setVATReverseCharge was called on object with property table_element not defined", LOG_ERR);
3407
            return -1;
3408
        }
3409
3410
        dol_syslog(get_only_class($this) . '::setVATReverseCharge(' . $vatreversecharge . ')');
3411
3412
        $sql = "UPDATE " . MAIN_DB_PREFIX . $this->table_element;
3413
        $sql .= " SET vat_reverse_charge = " . ((int) $vatreversecharge);
3414
        $sql .= " WHERE rowid=" . ((int) $this->id);
3415
3416
        if ($this->db->query($sql)) {
3417
            $this->vat_reverse_charge = ($vatreversecharge == 0) ? 0 : 1;
3418
            return 1;
3419
        } else {
3420
            dol_syslog(get_only_class($this) . '::setVATReverseCharge Error ', LOG_DEBUG);
3421
            $this->error = $this->db->error();
3422
            return 0;
3423
        }
3424
    }
3425
3426
    /**
3427
     *  Send reminders by emails for supplier invoices validated that are due.
3428
     *  CAN BE A CRON TASK
3429
     *
3430
     *  @param  int         $nbdays             Delay before due date (or after if delay is negative)
3431
     *  @param  string      $paymentmode        '' or 'all' by default (no filter), or 'LIQ', 'CHQ', CB', ...
3432
     *  @param  int|string  $template           Name (or id) of email template (Must be a template of type 'invoice_supplier_send')
3433
     *  @param  string      $datetouse          'duedate' (default) or 'invoicedate'
3434
     *  @param  string      $forcerecipient     Force email of recipient (for example to send the email to an accountant supervisor instead of the customer)
3435
     *  @return int                             0 if OK, <>0 if KO (this function is used also by cron so only 0 is OK)
3436
     */
3437
    public function sendEmailsRemindersOnSupplierInvoiceDueDate($nbdays = 0, $paymentmode = 'all', $template = '', $datetouse = 'duedate', $forcerecipient = '')
3438
    {
3439
        global $conf, $langs, $user;
3440
3441
        $this->output = '';
3442
        $this->error = '';
3443
        $nbMailSend = 0;
3444
3445
        $error = 0;
3446
        $errorsMsg = array();
3447
3448
        $langs->load('bills');
3449
3450
        if (!isModEnabled(empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD) ? 'fournisseur' : 'supplier_invoice')) {   // Should not happen. If module disabled, cron job should not be visible.
3451
            $this->output .= $langs->trans('ModuleNotEnabled', $langs->transnoentitiesnoconv('Suppliers'));
3452
            return 0;
3453
        }
3454
        if (!in_array($datetouse, array('duedate', 'invoicedate'))) {
3455
            $this->output .= 'Bad value for parameter datetouse. Must be "duedate" or "invoicedate"';
3456
            return 0;
3457
        }
3458
3459
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/date.lib.php';
3460
        $formmail = new FormMail($this->db);
3461
3462
        $now = dol_now();
3463
        $tmpidate = dol_get_first_hour(dol_time_plus_duree($now, $nbdays, 'd'), 'gmt');
3464
3465
        $tmpinvoice = new FactureFournisseur($this->db);
3466
3467
        dol_syslog(__METHOD__ . " start", LOG_INFO);
3468
3469
        // Select all action comm reminder
3470
        $sql = "SELECT rowid as id FROM " . MAIN_DB_PREFIX . "facture_fourn as f";
3471
        if (!empty($paymentmode) && $paymentmode != 'all') {
3472
            $sql .= ", " . MAIN_DB_PREFIX . "c_paiement as cp";
3473
        }
3474
        $sql .= " WHERE f.paye = 0";    // Only unpaid
3475
        $sql .= " AND f.fk_statut = " . self::STATUS_VALIDATED;   // Only validated status
3476
        if ($datetouse == 'invoicedate') {
3477
            $sql .= " AND f.datef = '" . $this->db->idate($tmpidate, 'gmt') . "'";
3478
        } else {
3479
            $sql .= " AND f.date_lim_reglement = '" . $this->db->idate($tmpidate, 'gmt') . "'";
3480
        }
3481
        $sql .= " AND f.entity IN (" . getEntity('supplier_invoice', 0) . ")";  // One batch process only one company (no sharing)
3482
        if (!empty($paymentmode) && $paymentmode != 'all') {
3483
            $sql .= " AND f.fk_mode_reglement = cp.id AND cp.code = '" . $this->db->escape($paymentmode) . "'";
3484
        }
3485
        // TODO Add a filter to check there is no payment started yet
3486
        if ($datetouse == 'invoicedate') {
3487
            $sql .= $this->db->order("datef", "ASC");
3488
        } else {
3489
            $sql .= $this->db->order("date_lim_reglement", "ASC");
3490
        }
3491
3492
        $resql = $this->db->query($sql);
3493
3494
        $stmpidate = dol_print_date($tmpidate, 'day', 'gmt');
3495
        if ($datetouse == 'invoicedate') {
3496
            $this->output .= $langs->transnoentitiesnoconv("SearchValidatedSupplierInvoicesWithDate", $stmpidate);
3497
        } else {
3498
            $this->output .= $langs->transnoentitiesnoconv("SearchUnpaidSupplierInvoicesWithDueDate", $stmpidate);
3499
        }
3500
        if (!empty($paymentmode) && $paymentmode != 'all') {
3501
            $this->output .= ' (' . $langs->transnoentitiesnoconv("PaymentMode") . ' ' . $paymentmode . ')';
3502
        }
3503
        $this->output .= '<br>';
3504
3505
        if ($resql) {
3506
            while ($obj = $this->db->fetch_object($resql)) {
3507
                if (!$error) {
3508
                    // Load event
3509
                    $res = $tmpinvoice->fetch($obj->id);
3510
                    if ($res > 0) {
3511
                        $tmpinvoice->fetch_thirdparty();
3512
3513
                        $outputlangs = new Translate('', $conf);
3514
                        if ($tmpinvoice->thirdparty->default_lang) {
3515
                            $outputlangs->setDefaultLang($tmpinvoice->thirdparty->default_lang);
3516
                            $outputlangs->loadLangs(array("main", "suppliers"));
3517
                        } else {
3518
                            $outputlangs = $langs;
3519
                        }
3520
3521
                        // Select email template according to language of recipient
3522
                        $templateId = 0;
3523
                        $templateLabel = '';
3524
                        if (empty($template) || $template == 'EmailTemplateCode') {
3525
                            $templateLabel = '(SendingReminderEmailOnUnpaidSupplierInvoice)';
3526
                        } else {
3527
                            if (is_numeric($template)) {
3528
                                $templateId = $template;
3529
                            } else {
3530
                                $templateLabel = $template;
3531
                            }
3532
                        }
3533
3534
                        $arraymessage = $formmail->getEMailTemplate($this->db, 'invoice_supplier_send', $user, $outputlangs, $templateId, 1, $templateLabel);
3535
                        if (is_numeric($arraymessage) && $arraymessage <= 0) {
3536
                            $langs->load("errors");
3537
                            $this->output .= $langs->trans('ErrorFailedToFindEmailTemplate', $template);
3538
                            return 0;
3539
                        }
3540
3541
                        // PREPARE EMAIL
3542
                        $errormesg = '';
3543
3544
                        // Make substitution in email content
3545
                        $substitutionarray = getCommonSubstitutionArray($outputlangs, 0, '', $tmpinvoice);
3546
3547
                        complete_substitutions_array($substitutionarray, $outputlangs, $tmpinvoice);
3548
3549
                        // Topic
3550
                        $sendTopic = make_substitutions(empty($arraymessage->topic) ? $outputlangs->transnoentitiesnoconv('InformationMessage') : $arraymessage->topic, $substitutionarray, $outputlangs, 1);
3551
3552
                        // Content
3553
                        $content = $outputlangs->transnoentitiesnoconv($arraymessage->content);
3554
3555
                        $sendContent = make_substitutions($content, $substitutionarray, $outputlangs, 1);
3556
3557
                        // Recipient
3558
                        $to = array();
3559
                        if ($forcerecipient) {  // If a recipient was forced
3560
                            $to = array($forcerecipient);
3561
                        } else {
3562
                            $res = $tmpinvoice->fetch_thirdparty();
3563
                            $recipient = $tmpinvoice->thirdparty;
3564
                            if ($res > 0) {
3565
                                $tmparraycontact = $tmpinvoice->liste_contact(-1, 'internal', 0, 'SALESREPFOLL');
3566
                                if (is_array($tmparraycontact) && count($tmparraycontact) > 0) {
3567
                                    foreach ($tmparraycontact as $data_email) {
3568
                                        if (!empty($data_email['email'])) {
3569
                                            $to[] = $data_email['email'];
3570
                                        }
3571
                                    }
3572
                                }
3573
                                if (empty($to) && !empty($recipient->email)) {
3574
                                    $to[] = $recipient->email;
3575
                                }
3576
                                if (empty($to)) {
3577
                                    $errormesg = "Failed to send remind to thirdparty id=" . $tmpinvoice->socid . ". No email defined for supplier invoice or customer.";
3578
                                    $error++;
3579
                                }
3580
                            } else {
3581
                                $errormesg = "Failed to load recipient with thirdparty id=" . $tmpinvoice->socid;
3582
                                $error++;
3583
                            }
3584
                        }
3585
3586
                        // Sender
3587
                        $from = getDolGlobalString('MAIN_MAIL_EMAIL_FROM');
3588
                        if (!empty($arraymessage->email_from)) {    // If a sender is defined into template, we use it in priority
3589
                            $from = $arraymessage->email_from;
3590
                        }
3591
                        if (empty($from)) {
3592
                            $errormesg = "Failed to get sender into global setup MAIN_MAIL_EMAIL_FROM";
3593
                            $error++;
3594
                        }
3595
3596
                        if (!$error && !empty($to)) {
3597
                            $this->db->begin();
3598
3599
                            $to = implode(',', $to);
3600
                            if (!empty($arraymessage->email_to)) {  // If a recipient is defined into template, we add it
3601
                                $to = $to . ',' . $arraymessage->email_to;
3602
                            }
3603
3604
                            // Errors Recipient
3605
                            $errors_to = $conf->global->MAIN_MAIL_ERRORS_TO;
3606
3607
                            $trackid = 'inv' . $tmpinvoice->id;
3608
                            $sendcontext = 'standard';
3609
3610
                            $email_tocc = '';
3611
                            if (!empty($arraymessage->email_tocc)) {    // If a CC is defined into template, we use it
3612
                                $email_tocc = $arraymessage->email_tocc;
3613
                            }
3614
3615
                            $email_tobcc = '';
3616
                            if (!empty($arraymessage->email_tobcc)) {   // If a BCC is defined into template, we use it
3617
                                $email_tobcc = $arraymessage->email_tobcc;
3618
                            }
3619
3620
                            // Mail Creation
3621
                            $cMailFile = new CMailFile($sendTopic, $to, $from, $sendContent, array(), array(), array(), $email_tocc, $email_tobcc, 0, 1, $errors_to, '', $trackid, '', $sendcontext, '');
3622
3623
                            // Sending Mail
3624
                            if ($cMailFile->sendfile()) {
3625
                                $nbMailSend++;
3626
3627
                                // Add a line into event table
3628
                                
3629
                                // Insert record of emails sent
3630
                                $actioncomm = new ActionComm($this->db);
3631
3632
                                $actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
3633
                                $actioncomm->socid = $tmpinvoice->thirdparty->id; // To link to a company
3634
                                $actioncomm->contact_id = 0;
3635
3636
                                $actioncomm->code = 'AC_EMAIL';
3637
                                $actioncomm->label = 'sendEmailsRemindersOnInvoiceDueDateOK (nbdays=' . $nbdays . ' paymentmode=' . $paymentmode . ' template=' . $template . ' datetouse=' . $datetouse . ' forcerecipient=' . $forcerecipient . ')';
3638
                                $actioncomm->note_private = $sendContent;
3639
                                $actioncomm->fk_project = $tmpinvoice->fk_project;
3640
                                $actioncomm->datep = dol_now();
3641
                                $actioncomm->datef = $actioncomm->datep;
3642
                                $actioncomm->percentage = -1; // Not applicable
3643
                                $actioncomm->authorid = $user->id; // User saving action
3644
                                $actioncomm->userownerid = $user->id; // Owner of action
3645
                                // Fields when action is an email (content should be added into note)
3646
                                $actioncomm->email_msgid = $cMailFile->msgid;
3647
                                $actioncomm->email_subject = $sendTopic;
3648
                                $actioncomm->email_from = $from;
3649
                                $actioncomm->email_sender = '';
3650
                                $actioncomm->email_to = $to;
3651
                                //$actioncomm->email_tocc = $sendtocc;
3652
                                //$actioncomm->email_tobcc = $sendtobcc;
3653
                                //$actioncomm->email_subject = $subject;
3654
                                $actioncomm->errors_to = $errors_to;
3655
3656
                                $actioncomm->elementtype = 'invoice_supplier';
3657
                                $actioncomm->fk_element = $tmpinvoice->id;
3658
3659
                                //$actioncomm->extraparams = $extraparams;
3660
3661
                                $actioncomm->create($user);
3662
                            } else {
3663
                                $errormesg = $cMailFile->error . ' : ' . $to;
3664
                                $error++;
3665
3666
                                // Add a line into event table
3667
                                
3668
                                // Insert record of emails sent
3669
                                $actioncomm = new ActionComm($this->db);
3670
3671
                                $actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
3672
                                $actioncomm->socid = $tmpinvoice->thirdparty->id; // To link to a company
3673
                                $actioncomm->contact_id = 0;
3674
3675
                                $actioncomm->code = 'AC_EMAIL';
3676
                                $actioncomm->label = 'sendEmailsRemindersOnInvoiceDueDateKO';
3677
                                $actioncomm->note_private = $errormesg;
3678
                                $actioncomm->fk_project = $tmpinvoice->fk_project;
3679
                                $actioncomm->datep = dol_now();
3680
                                $actioncomm->datef = $actioncomm->datep;
3681
                                $actioncomm->percentage = -1; // Not applicable
3682
                                $actioncomm->authorid = $user->id; // User saving action
3683
                                $actioncomm->userownerid = $user->id; // Owner of action
3684
                                // Fields when action is an email (content should be added into note)
3685
                                $actioncomm->email_msgid = $cMailFile->msgid;
3686
                                $actioncomm->email_from = $from;
3687
                                $actioncomm->email_sender = '';
3688
                                $actioncomm->email_to = $to;
3689
                                //$actioncomm->email_tocc = $sendtocc;
3690
                                //$actioncomm->email_tobcc = $sendtobcc;
3691
                                //$actioncomm->email_subject = $subject;
3692
                                $actioncomm->errors_to = $errors_to;
3693
3694
                                //$actioncomm->extraparams = $extraparams;
3695
3696
                                $actioncomm->create($user);
3697
                            }
3698
3699
                            $this->db->commit();    // We always commit
3700
                        }
3701
3702
                        if ($errormesg) {
3703
                            $errorsMsg[] = $errormesg;
3704
                        }
3705
                    } else {
3706
                        $errorsMsg[] = 'Failed to fetch record invoice with ID = ' . $obj->id;
3707
                        $error++;
3708
                    }
3709
                }
3710
            }
3711
        } else {
3712
            $error++;
3713
        }
3714
3715
        if (!$error) {
3716
            $this->output .= 'Nb of emails sent : ' . $nbMailSend;
3717
3718
            dol_syslog(__METHOD__ . " end - " . $this->output, LOG_INFO);
3719
3720
            return 0;
3721
        } else {
3722
            $this->error = 'Nb of emails sent : ' . $nbMailSend . ', ' . (empty($errorsMsg) ? $error : implode(', ', $errorsMsg));
3723
3724
            dol_syslog(__METHOD__ . " end - " . $this->error, LOG_INFO);
3725
3726
            return $error;
3727
        }
3728
    }
3729
}
3730