Passed
Push — main ( f1540e...02d90d )
by Rafael
45:15
created

FactureFournisseur::replaceProduct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 3
dl 0
loc 7
rs 10
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 DoliModules\Supplier\Model;
37
38
use DoliModules\Billing\Model\CommonInvoice;
39
use DoliCore\Model\WorkboardResponse;
40
41
/**
42
 *  \file       htdocs/fourn/class/fournisseur.facture.class.php
43
 *  \ingroup    fournisseur,facture
44
 *  \brief      File of class to manage suppliers invoices
45
 */
46
47
/**
48
 *  Class to manage suppliers invoices
49
 */
50
class FactureFournisseur extends CommonInvoice
51
{
52
    /**
53
     * Standard invoice
54
     */
55
    const TYPE_STANDARD = 0;
56
    /**
57
     * Replacement invoice
58
     */
59
    const TYPE_REPLACEMENT = 1;
60
    /**
61
     * Credit note invoice
62
     */
63
    const TYPE_CREDIT_NOTE = 2;
64
    /**
65
     * Deposit invoice
66
     */
67
    const TYPE_DEPOSIT = 3;
68
    /**
69
     * Draft
70
     */
71
    const STATUS_DRAFT = 0;
72
    /**
73
     * Validated (need to be paid)
74
     */
75
    const STATUS_VALIDATED = 1;
76
    /**
77
     * Classified paid.
78
     * If paid partially, $this->close_code can be:
79
     * - CLOSECODE_DISCOUNTVAT
80
     * - CLOSECODE_BADCREDIT
81
     * If paid completely, this->close_code will be null
82
     */
83
    const STATUS_CLOSED = 2;
84
    /**
85
     * Classified abandoned and no payment done.
86
     * $this->close_code can be:
87
     * - CLOSECODE_BADCREDIT
88
     * - CLOSECODE_ABANDONED
89
     * - CLOSECODE_REPLACED
90
     */
91
    const STATUS_ABANDONED = 3;
92
    const CLOSECODE_DISCOUNTVAT = 'discount_vat';
93
    const CLOSECODE_BADCREDIT = 'badsupplier';
94
    const CLOSECODE_ABANDONED = 'abandon';
95
    const CLOSECODE_REPLACED = 'replaced';
96
    /**
97
     * @var string ID to identify managed object
98
     */
99
    public $element = 'invoice_supplier';
100
101
    //Check constants for types
102
    /**
103
     * @var string Name of table without prefix where object is stored
104
     */
105
    public $table_element = 'facture_fourn';
106
    /**
107
     * @var string    Name of subtable line
108
     */
109
    public $table_element_line = 'facture_fourn_det';
110
    /**
111
     * @var string Field with ID of parent key if this field has a parent
112
     */
113
    public $fk_element = 'fk_facture_fourn';
114
    /**
115
     * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
116
     */
117
    public $picto = 'supplier_invoice';
118
    /**
119
     * 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
120
     * @var int
121
     */
122
    public $ismultientitymanaged = 1;
123
    /**
124
     * 0=Default, 1=View may be restricted to sales representative only if no permission to see all or to company of
125
     * external user if external user
126
     * @var integer
127
     */
128
    public $restrictiononfksoc = 1;
129
    /**
130
     * @var int ID
131
     */
132
    public $rowid;
133
    /**
134
     * @var string Ref
135
     */
136
    public $ref;
137
    /**
138
     * @var string Ref supplier
139
     */
140
    public $ref_supplier;
141
    /**
142
     * @var string  Label of invoice
143
     * @deprecated  Use $label
144
     */
145
    public $libelle;
146
    /**
147
     * @var string Label of invoice
148
     */
149
    public $label;
150
    public $type = self::TYPE_STANDARD;
151
152
    // Warning: Do not set default value into property definition. it must stay null.
153
    // For example to avoid to have substitution done when object is generic and not yet defined.
154
    /**
155
     * Supplier invoice status
156
     * @var int
157
     * @deprecated
158
     * @see $status
159
     */
160
    public $statut;
161
    /**
162
     * Supplier invoice status
163
     * @var int
164
     * @see FactureFournisseur::STATUS_DRAFT, FactureFournisseur::STATUS_VALIDATED, FactureFournisseur::STATUS_PAID,
165
     *      FactureFournisseur::STATUS_ABANDONED
166
     */
167
    public $status;
168
    /**
169
     * Supplier invoice status
170
     * @var int
171
     * @deprecated
172
     * @see $status
173
     */
174
    public $fk_statut;
175
    /**
176
     * Set to 1 if the invoice is completely paid, otherwise is 0
177
     * @var int
178
     * @deprecated Use $paid
179
     */
180
    public $paye;
181
    /**
182
     * Set to 1 if the invoice is completely paid, otherwise is 0
183
     * @var int
184
     */
185
    public $paid;
186
    /**
187
     * @deprecated  Use $user_creation_id
188
     */
189
    public $author;
190
    /**
191
     * Date creation record (datec)
192
     *
193
     * @var integer
194
     */
195
    public $datec;
196
    /**
197
     * Max payment date (date_echeance)
198
     *
199
     * @var integer
200
     */
201
    public $date_echeance;
202
    /**
203
     * @var double $amount
204
     * @deprecated
205
     */
206
    public $amount = 0;
207
    /**
208
     * @var double $remise
209
     * @deprecated
210
     */
211
    public $remise = 0;
212
    /**
213
     * @var float tva
214
     * @deprecated Use $total_tva
215
     */
216
    public $tva;
217
    public $localtax1;     // default bank account
218
    public $localtax2;
219
    public $total_ht;
220
    public $total_tva;
221
    public $total_localtax1;
222
    public $total_localtax2;
223
224
    //! id of source invoice if replacement invoice or credit note
225
    public $total_ttc;
226
    /**
227
     * @deprecated
228
     * @see $note_private, $note_public
229
     */
230
    public $note;
231
    public $note_private;
232
    public $note_public;
233
    public $propalid;
234
/**
235
     * @var int ID
236
     */
237
    public $fk_account;
238
    /**
239
     * @var int Transport mode id
240
     */
241
    public $transport_mode_id;
242
    /**
243
     * @var int<0,1>  VAT reverse charge can be used on the invoice
244
     */
245
    public $vat_reverse_charge;
246
    public $extraparams = [];
247
    /**
248
     * Invoice lines
249
     * @var SupplierInvoiceLine[]
250
     */
251
    public $lines = [];
252
    /**
253
     * @deprecated
254
     */
255
    public $fournisseur;
256
    /**
257
     * @var int ID
258
     */
259
    public $fk_facture_source;
260
    public $fac_rec;
261
    public $fk_fac_rec_source;
262
    public $fields = [
263
        'rowid' => ['type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 10],
264
        'ref' => ['type' => 'varchar(255)', 'label' => 'Ref', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'showoncombobox' => 1, 'position' => 15],
265
        'ref_supplier' => ['type' => 'varchar(255)', 'label' => 'RefSupplier', 'enabled' => 1, 'visible' => -1, 'position' => 20],
266
        'entity' => ['type' => 'integer', 'label' => 'Entity', 'default' => 1, 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 25, 'index' => 1],
267
        'ref_ext' => ['type' => 'varchar(255)', 'label' => 'RefExt', 'enabled' => 1, 'visible' => 0, 'position' => 30],
268
        'type' => ['type' => 'smallint(6)', 'label' => 'Type', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 35],
269
        'subtype' => ['type' => 'smallint(6)', 'label' => 'InvoiceSubtype', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 36],
270
        'fk_soc' => ['type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 'isModEnabled("societe")', 'visible' => -1, 'notnull' => 1, 'position' => 40],
271
        'datec' => ['type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -1, 'position' => 45],
272
        'datef' => ['type' => 'date', 'label' => 'Date', 'enabled' => 1, 'visible' => -1, 'position' => 50],
273
        'tms' => ['type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 55],
274
        'libelle' => ['type' => 'varchar(255)', 'label' => 'Label', 'enabled' => 1, 'visible' => -1, 'position' => 60],
275
        'paye' => ['type' => 'smallint(6)', 'label' => 'Paye', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 65],
276
        'amount' => ['type' => 'double(24,8)', 'label' => 'Amount', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 70],
277
        'remise' => ['type' => 'double(24,8)', 'label' => 'Discount', 'enabled' => 1, 'visible' => -1, 'position' => 75],
278
        'close_code' => ['type' => 'varchar(16)', 'label' => 'CloseCode', 'enabled' => 1, 'visible' => -1, 'position' => 80],
279
        'close_note' => ['type' => 'varchar(128)', 'label' => 'CloseNote', 'enabled' => 1, 'visible' => -1, 'position' => 85],
280
        'tva' => ['type' => 'double(24,8)', 'label' => 'Tva', 'enabled' => 1, 'visible' => -1, 'position' => 90],
281
        'localtax1' => ['type' => 'double(24,8)', 'label' => 'Localtax1', 'enabled' => 1, 'visible' => -1, 'position' => 95],
282
        'localtax2' => ['type' => 'double(24,8)', 'label' => 'Localtax2', 'enabled' => 1, 'visible' => -1, 'position' => 100],
283
        'total_ht' => ['type' => 'double(24,8)', 'label' => 'TotalHT', 'enabled' => 1, 'visible' => -1, 'position' => 105],
284
        'total_tva' => ['type' => 'double(24,8)', 'label' => 'TotalVAT', 'enabled' => 1, 'visible' => -1, 'position' => 110],
285
        'total_ttc' => ['type' => 'double(24,8)', 'label' => 'TotalTTC', 'enabled' => 1, 'visible' => -1, 'position' => 115],
286
        'fk_user_author' => ['type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => -1, 'position' => 125],
287
        'fk_user_modif' => ['type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'position' => 130],
288
        'fk_user_valid' => ['type' => 'integer:User:user/class/user.class.php', 'label' => 'UserValidation', 'enabled' => 1, 'visible' => -1, 'position' => 135],
289
        'fk_facture_source' => ['type' => 'integer', 'label' => 'Fk facture source', 'enabled' => 1, 'visible' => -1, 'position' => 140],
290
        'fk_projet' => ['type' => 'integer:Project:projet/class/project.class.php:1:fk_statut=1', 'label' => 'Project', 'enabled' => "isModEnabled('project')", 'visible' => -1, 'position' => 145],
291
        'fk_account' => ['type' => 'integer', 'label' => 'Account', 'enabled' => 'isModEnabled("bank")', 'visible' => -1, 'position' => 150],
292
        'fk_cond_reglement' => ['type' => 'integer', 'label' => 'PaymentTerm', 'enabled' => 1, 'visible' => -1, 'position' => 155],
293
        'fk_mode_reglement' => ['type' => 'integer', 'label' => 'PaymentMode', 'enabled' => 1, 'visible' => -1, 'position' => 160],
294
        'date_lim_reglement' => ['type' => 'date', 'label' => 'DateLimReglement', 'enabled' => 1, 'visible' => -1, 'position' => 165],
295
        'note_private' => ['type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 170],
296
        'note_public' => ['type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 175],
297
        'model_pdf' => ['type' => 'varchar(255)', 'label' => 'ModelPdf', 'enabled' => 1, 'visible' => 0, 'position' => 180],
298
        'extraparams' => ['type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => -1, 'position' => 190],
299
        'fk_incoterms' => ['type' => 'integer', 'label' => 'IncotermCode', 'enabled' => 1, 'visible' => -1, 'position' => 195],
300
        'location_incoterms' => ['type' => 'varchar(255)', 'label' => 'IncotermLocation', 'enabled' => 1, 'visible' => -1, 'position' => 200],
301
        'fk_multicurrency' => ['type' => 'integer', 'label' => 'MulticurrencyId', 'enabled' => 1, 'visible' => -1, 'position' => 205],
302
        'multicurrency_code' => ['type' => 'varchar(255)', 'label' => 'MulticurrencyCode', 'enabled' => 1, 'visible' => -1, 'position' => 210],
303
        'multicurrency_tx' => ['type' => 'double(24,8)', 'label' => 'MulticurrencyRate', 'enabled' => 1, 'visible' => -1, 'position' => 215],
304
        'multicurrency_total_ht' => ['type' => 'double(24,8)', 'label' => 'MulticurrencyTotalHT', 'enabled' => 1, 'visible' => -1, 'position' => 220],
305
        'multicurrency_total_tva' => ['type' => 'double(24,8)', 'label' => 'MulticurrencyTotalVAT', 'enabled' => 1, 'visible' => -1, 'position' => 225],
306
        'multicurrency_total_ttc' => ['type' => 'double(24,8)', 'label' => 'MulticurrencyTotalTTC', 'enabled' => 1, 'visible' => -1, 'position' => 230],
307
        'date_pointoftax' => ['type' => 'date', 'label' => 'Date pointoftax', 'enabled' => 1, 'visible' => -1, 'position' => 235],
308
        'date_valid' => ['type' => 'date', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -1, 'position' => 240],
309
        'last_main_doc' => ['type' => 'varchar(255)', 'label' => 'Last main doc', 'enabled' => 1, 'visible' => -1, 'position' => 245],
310
        'fk_statut' => ['type' => 'smallint(6)', 'label' => 'Status', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 500],
311
        'import_key' => ['type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'position' => 900],
312
    ];
313
    /**
314
     * {@inheritdoc}
315
     */
316
    protected $table_ref_field = 'ref';
317
318
    /**
319
     *  Constructor
320
     *
321
     * @param DoliDB $db Database handler
0 ignored issues
show
Bug introduced by
The type DoliModules\Supplier\Model\DoliDB was not found. Did you mean DoliDB? If so, make sure to prefix the type with \.
Loading history...
322
     */
323
    public function __construct($db)
324
    {
325
        $this->db = $db;
326
    }
327
328
    /**
329
     * Function used to replace a thirdparty id with another one.
330
     *
331
     * @param DoliDB $dbs       Database handler, because function is static we name it $dbs not $db to avoid breaking
332
     *                          coding test
333
     * @param int    $origin_id Old thirdparty id
334
     * @param int    $dest_id   New thirdparty id
335
     *
336
     * @return  bool
337
     */
338
    public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
339
    {
340
        $tables = [
341
            'facture_fourn',
342
        ];
343
344
        return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
345
    }
346
347
    /**
348
     * Function used to replace a product id with another one.
349
     *
350
     * @param DoliDB $db        Database handler
351
     * @param int    $origin_id Old product id
352
     * @param int    $dest_id   New product id
353
     *
354
     * @return bool
355
     */
356
    public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
357
    {
358
        $tables = [
359
            'facture_fourn_det',
360
        ];
361
362
        return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
363
    }
364
365
366
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
367
368
    /**
369
     *    Add a discount line into an invoice (as an invoice line) using an existing absolute discount (Consume the
370
     *    discount)
371
     *
372
     * @param int $idremise Id of absolute discount
373
     *
374
     * @return    int                >0 if OK, <0 if KO
375
     */
376
    public function insert_discount($idremise)
377
    {
378
        // phpcs:enable
379
        global $conf, $langs;
380
381
        include_once BASE_PATH . '/../Dolibarr/Lib/Price.php';
382
        include_once DOL_DOCUMENT_ROOT . '/core/class/discount.class.php';
383
384
        $this->db->begin();
385
386
        $remise = new DiscountAbsolute($this->db);
0 ignored issues
show
Bug introduced by
The type DoliModules\Supplier\Model\DiscountAbsolute was not found. Did you mean DiscountAbsolute? If so, make sure to prefix the type with \.
Loading history...
387
        $result = $remise->fetch($idremise);
388
389
        if ($result > 0) {
390
            if ($remise->fk_invoice_supplier) { // Protection against multiple submission
391
                $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
392
                $this->db->rollback();
393
                return -5;
394
            }
395
396
            $facligne = new SupplierInvoiceLine($this->db);
397
            $facligne->fk_facture_fourn = $this->id;
398
            $facligne->fk_remise_except = $remise->id;
399
            $facligne->desc = $remise->description; // Description ligne
400
            $facligne->vat_src_code = $remise->vat_src_code;
401
            $facligne->tva_tx = $remise->tva_tx;
402
            $facligne->subprice = -$remise->amount_ht;
403
            $facligne->fk_product = 0; // Id produit predefini
404
            $facligne->product_type = 0;
405
            $facligne->qty = 1;
406
            $facligne->remise_percent = 0;
407
            $facligne->rang = -1;
408
            $facligne->info_bits = 2;
409
410
            if (getDolGlobalString('MAIN_ADD_LINE_AT_POSITION')) {
411
                $facligne->rang = 1;
412
                $linecount = count($this->lines);
413
                for ($ii = 1; $ii <= $linecount; $ii++) {
414
                    $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
415
                }
416
            }
417
418
            // Get buy/cost price of invoice that is source of discount
419
            if ($remise->fk_invoice_supplier_source > 0) {
420
                $srcinvoice = new FactureFournisseur($this->db);
421
                $srcinvoice->fetch($remise->fk_invoice_supplier_source);
422
                $totalcostpriceofinvoice = 0;
423
                include_once DOL_DOCUMENT_ROOT . '/core/class/html.formmargin.class.php'; // TODO Move this into commonobject
424
                $formmargin = new FormMargin($this->db);
425
                $arraytmp = $formmargin->getMarginInfosArray($srcinvoice, false);
426
                $facligne->pa_ht = $arraytmp['pa_total'];
427
            }
428
429
            $facligne->total_ht = -$remise->amount_ht;
430
            $facligne->total_tva = -$remise->amount_tva;
431
            $facligne->total_ttc = -$remise->amount_ttc;
432
433
            $facligne->multicurrency_subprice = -$remise->multicurrency_subprice;
434
            $facligne->multicurrency_total_ht = -$remise->multicurrency_total_ht;
435
            $facligne->multicurrency_total_tva = -$remise->multicurrency_total_tva;
436
            $facligne->multicurrency_total_ttc = -$remise->multicurrency_total_ttc;
437
438
            $lineid = $facligne->insert();
439
            if ($lineid > 0) {
440
                $result = $this->update_price(1);
441
                if ($result > 0) {
442
                    // Create link between discount and invoice line
443
                    $result = $remise->link_to_invoice($lineid, 0);
444
                    if ($result < 0) {
445
                        $this->error = $remise->error;
446
                        $this->db->rollback();
447
                        return -4;
448
                    }
449
450
                    $this->db->commit();
451
                    return 1;
452
                } else {
453
                    $this->error = $facligne->error;
454
                    $this->db->rollback();
455
                    return -1;
456
                }
457
            } else {
458
                $this->error = $facligne->error;
459
                $this->db->rollback();
460
                return -2;
461
            }
462
        } else {
463
            $this->db->rollback();
464
            return -3;
465
        }
466
    }
467
468
    /**
469
     *  Load object in memory from database
470
     *
471
     * @param int    $id      Id supplier invoice
472
     * @param string $ref     Ref supplier invoice
473
     * @param string $ref_ext External reference of invoice
474
     *
475
     * @return int                 Return integer <0 if KO, >0 if OK, 0 if not found
476
     */
477
    public function fetch($id = 0, $ref = '', $ref_ext = '')
478
    {
479
        if (empty($id) && empty($ref) && empty($ref_ext)) {
480
            return -1;
481
        }
482
483
        $sql = "SELECT";
484
        $sql .= " t.rowid,";
485
        $sql .= " t.ref,";
486
        $sql .= " t.ref_supplier,";
487
        $sql .= " t.ref_ext,";
488
        $sql .= " t.entity,";
489
        $sql .= " t.type,";
490
        $sql .= " t.subtype,";
491
        $sql .= " t.fk_soc,";
492
        $sql .= " t.datec,";
493
        $sql .= " t.datef,";
494
        $sql .= " t.tms,";
495
        $sql .= " t.libelle as label,";
496
        $sql .= " t.paye,";
497
        $sql .= " t.close_code,";
498
        $sql .= " t.close_note,";
499
        $sql .= " t.tva,";
500
        $sql .= " t.localtax1,";
501
        $sql .= " t.localtax2,";
502
        $sql .= " t.total_ht,";
503
        $sql .= " t.total_tva,";
504
        $sql .= " t.total_ttc,";
505
        $sql .= " t.fk_statut as status,";
506
        $sql .= " t.fk_user_author,";
507
        $sql .= " t.fk_user_valid,";
508
        $sql .= " t.fk_facture_source,";
509
        $sql .= " t.vat_reverse_charge,";
510
        $sql .= " t.fk_fac_rec_source,";
511
        $sql .= " t.fk_projet as fk_project,";
512
        $sql .= " t.fk_cond_reglement,";
513
        $sql .= " t.fk_account,";
514
        $sql .= " t.fk_mode_reglement,";
515
        $sql .= " t.date_lim_reglement,";
516
        $sql .= " t.note_private,";
517
        $sql .= " t.note_public,";
518
        $sql .= " t.model_pdf,";
519
        $sql .= " t.import_key,";
520
        $sql .= " t.extraparams,";
521
        $sql .= " cr.code as cond_reglement_code, cr.libelle as cond_reglement_label, cr.libelle_facture as cond_reglement_doc,";
522
        $sql .= " p.code as mode_reglement_code, p.libelle as mode_reglement_label,";
523
        $sql .= ' s.nom as socnom, s.rowid as socid,';
524
        $sql .= ' t.fk_incoterms, t.location_incoterms,';
525
        $sql .= " i.libelle as label_incoterms,";
526
        $sql .= ' t.fk_transport_mode,';
527
        $sql .= ' t.fk_multicurrency, t.multicurrency_code, t.multicurrency_tx, t.multicurrency_total_ht, t.multicurrency_total_tva, t.multicurrency_total_ttc';
528
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'facture_fourn as t';
529
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON (t.fk_soc = s.rowid)";
530
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "c_payment_term as cr ON t.fk_cond_reglement = cr.rowid";
531
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "c_paiement as p ON t.fk_mode_reglement = p.id";
532
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'c_incoterms as i ON t.fk_incoterms = i.rowid';
533
        if ($id) {
534
            $sql .= " WHERE t.rowid = " . ((int) $id);
535
        } else {
536
            $sql .= ' WHERE t.entity IN (' . getEntity('supplier_invoice') . ')'; // Don't use entity if you use rowid
537
            if ($ref) {
538
                $sql .= " AND t.ref = '" . $this->db->escape($ref) . "'";
539
            }
540
            if ($ref_ext) {
541
                $sql .= " AND t.ref_ext = '" . $this->db->escape($ref_ext) . "'";
542
            }
543
        }
544
545
        dol_syslog(get_class($this) . "::fetch", LOG_DEBUG);
546
        $resql = $this->db->query($sql);
547
        if ($resql) {
548
            if ($this->db->num_rows($resql)) {
549
                $obj = $this->db->fetch_object($resql);
550
551
                $this->id = $obj->rowid;
552
                $this->ref = $obj->ref ? $obj->ref : $obj->rowid; // We take rowid if ref is empty for backward compatibility
553
554
                $this->ref_supplier = $obj->ref_supplier;
555
                $this->ref_ext = $obj->ref_ext;
556
                $this->entity = $obj->entity;
557
                $this->type = empty($obj->type) ? self::TYPE_STANDARD : $obj->type;
558
                $this->subtype = $obj->subtype;
559
                $this->socid = $obj->fk_soc;
560
                $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...
561
                $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...
562
                //$this->datep              = $this->db->jdate($obj->datef);
563
                $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 DoliCore\Base\GenericDocument::$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

563
                /** @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...
564
                $this->libelle = $obj->label; // deprecated
0 ignored issues
show
Deprecated Code introduced by
The property DoliModules\Supplier\Mod...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

564
                /** @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...
565
                $this->label = $obj->label;
566
                $this->paye = $obj->paye;
0 ignored issues
show
Deprecated Code introduced by
The property DoliModules\Supplier\Mod...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

566
                /** @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...
567
                $this->paid = $obj->paye;
568
                $this->close_code = $obj->close_code;
569
                $this->close_note = $obj->close_note;
570
                $this->total_localtax1 = $obj->localtax1;
571
                $this->total_localtax2 = $obj->localtax2;
572
                $this->total_ht = $obj->total_ht;
573
                $this->total_tva = $obj->total_tva;
574
                $this->total_ttc = $obj->total_ttc;
575
                $this->status = $obj->status;
576
                $this->statut = $obj->status; // For backward compatibility
577
                $this->fk_statut = $obj->status; // For backward compatibility
578
                $this->user_creation_id = $obj->fk_user_author;
579
                $this->author = $obj->fk_user_author; // deprecated
0 ignored issues
show
Deprecated Code introduced by
The property DoliModules\Supplier\Mod...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

579
                /** @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...
580
                $this->user_validation_id = $obj->fk_user_valid;
581
                $this->fk_facture_source = $obj->fk_facture_source;
582
                $this->vat_reverse_charge = empty($obj->vat_reverse_charge) ? 0 : 1;
583
                $this->fk_fac_rec_source = $obj->fk_fac_rec_source;
584
                $this->fk_project = $obj->fk_project;
585
                $this->cond_reglement_id = $obj->fk_cond_reglement;
586
                $this->cond_reglement_code = $obj->cond_reglement_code;
587
                $this->cond_reglement = $obj->cond_reglement_label; // deprecated
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$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

587
                /** @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...
588
                $this->cond_reglement_label = $obj->cond_reglement_label;
589
                $this->cond_reglement_doc = $obj->cond_reglement_doc;
590
                $this->fk_account = $obj->fk_account;
591
                $this->mode_reglement_id = $obj->fk_mode_reglement;
592
                $this->mode_reglement_code = $obj->mode_reglement_code;
593
                $this->mode_reglement = $obj->mode_reglement_label;
594
                $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...
595
                $this->note = $obj->note_private; // deprecated
596
                $this->note_private = $obj->note_private;
597
                $this->note_public = $obj->note_public;
598
                $this->model_pdf = $obj->model_pdf;
599
                $this->import_key = $obj->import_key;
600
601
                //Incoterms
602
                $this->fk_incoterms = $obj->fk_incoterms;
603
                $this->location_incoterms = $obj->location_incoterms;
604
                $this->label_incoterms = $obj->label_incoterms;
605
                $this->transport_mode_id = $obj->fk_transport_mode;
606
607
                // Multicurrency
608
                $this->fk_multicurrency = $obj->fk_multicurrency;
609
                $this->multicurrency_code = $obj->multicurrency_code;
610
                $this->multicurrency_tx = $obj->multicurrency_tx;
611
                $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
612
                $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
613
                $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
614
615
                $this->extraparams = isset($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : [];
616
617
                $this->socid = $obj->socid;
618
619
                // Retrieve all extrafield
620
                // fetch optionals attributes and labels
621
                $this->fetch_optionals();
622
623
                $result = $this->fetch_lines();
624
                if ($result < 0) {
625
                    $this->error = $this->db->lasterror();
626
                    return -3;
627
                }
628
            } else {
629
                $this->error = 'Bill with id ' . $id . ' not found';
630
                dol_syslog(get_class($this) . '::fetch ' . $this->error);
631
                return 0;
632
            }
633
634
            $this->db->free($resql);
635
            return 1;
636
        } else {
637
            $this->error = "Error " . $this->db->lasterror();
638
            return -1;
639
        }
640
    }
641
642
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
643
644
    /**
645
     *  Load this->lines
646
     *
647
     * @return     int         1 si ok, < 0 si erreur
648
     */
649
    public function fetch_lines()
650
    {
651
        // phpcs:enable
652
        $this->lines = [];
653
654
        $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';
655
        $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';
656
        $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';
657
        $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';
658
        $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';
659
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'facture_fourn_det as f';
660
        $sql .= ' LEFT JOIN ' . MAIN_DB_PREFIX . 'product as p ON f.fk_product = p.rowid';
661
        $sql .= ' WHERE fk_facture_fourn=' . ((int) $this->id);
662
        $sql .= ' ORDER BY f.rang, f.rowid';
663
664
        dol_syslog(get_class($this) . "::fetch_lines", LOG_DEBUG);
665
666
        $resql_rows = $this->db->query($sql);
667
        if ($resql_rows) {
668
            $num_rows = $this->db->num_rows($resql_rows);
669
            if ($num_rows) {
670
                $i = 0;
671
                while ($i < $num_rows) {
672
                    $obj = $this->db->fetch_object($resql_rows);
673
674
                    $line = new SupplierInvoiceLine($this->db);
675
676
                    $line->id = $obj->rowid;
677
                    $line->rowid = $obj->rowid;
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocumentLine::$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

677
                    /** @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...
678
                    $line->description = $obj->line_desc;
0 ignored issues
show
Deprecated Code introduced by
The property DoliModules\Supplier\Mod...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

678
                    /** @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...
679
                    $line->desc = $obj->line_desc;
680
                    $line->date_start = $obj->date_start;
681
                    $line->date_end = $obj->date_end;
682
                    $line->product_ref = $obj->product_ref;
683
                    $line->ref = $obj->product_ref;
684
                    $line->ref_supplier = $obj->ref_supplier;
685
                    $line->libelle = $obj->label;
686
                    $line->label = $obj->label;
687
                    $line->product_barcode = $obj->product_barcode;
688
                    $line->product_desc = $obj->product_desc;
689
                    $line->subprice = $obj->pu_ht;
690
                    $line->pu_ht = $obj->pu_ht;
0 ignored issues
show
Deprecated Code introduced by
The property DoliModules\Supplier\Mod...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

690
                    /** @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...
691
                    $line->pu_ttc = $obj->pu_ttc;
692
                    $line->vat_src_code = $obj->vat_src_code;
693
                    $line->tva_tx = $obj->tva_tx;
694
                    $line->localtax1_tx = $obj->localtax1_tx;
695
                    $line->localtax2_tx = $obj->localtax2_tx;
696
                    $line->localtax1_type = $obj->localtax1_type;
697
                    $line->localtax2_type = $obj->localtax2_type;
698
                    $line->qty = $obj->qty;
699
                    $line->remise_percent = $obj->remise_percent;
700
                    $line->fk_remise_except = $obj->fk_remise_except;
701
                    //$line->tva            = $obj->total_tva; // deprecated
702
                    $line->total_ht = $obj->total_ht;
703
                    $line->total_ttc = $obj->total_ttc;
704
                    $line->total_tva = $obj->total_tva;
705
                    $line->total_localtax1 = $obj->total_localtax1;
706
                    $line->total_localtax2 = $obj->total_localtax2;
707
                    $line->fk_facture_fourn = $obj->fk_facture_fourn;
708
                    $line->fk_product = $obj->fk_product;
709
                    $line->product_type = $obj->product_type;
710
                    $line->product_label = $obj->label;
711
                    $line->info_bits = $obj->info_bits;
712
                    $line->fk_parent_line = $obj->fk_parent_line;
713
                    $line->special_code = $obj->special_code;
714
                    $line->rang = $obj->rang;
715
                    $line->fk_unit = $obj->fk_unit;
716
717
                    // Accountancy
718
                    $line->fk_accounting_account = $obj->fk_code_ventilation;
0 ignored issues
show
Bug introduced by
The property fk_accounting_account does not exist on DoliModules\Supplier\Model\SupplierInvoiceLine. Did you mean fk_account?
Loading history...
719
720
                    // Multicurrency
721
                    $line->fk_multicurrency = $obj->fk_multicurrency;
722
                    $line->multicurrency_code = $obj->multicurrency_code;
723
                    $line->multicurrency_subprice = $obj->multicurrency_subprice;
724
                    $line->multicurrency_total_ht = $obj->multicurrency_total_ht;
725
                    $line->multicurrency_total_tva = $obj->multicurrency_total_tva;
726
                    $line->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
727
728
                    // Extra fields
729
                    $line->fetch_optionals();
730
731
                    $this->lines[$i] = $line;
732
733
                    $i++;
734
                }
735
            }
736
            $this->db->free($resql_rows);
737
            return 1;
738
        } else {
739
            $this->error = $this->db->error();
740
            dol_syslog(get_class($this) . "::fetch_lines - No lines:{$this->error} Error:{$this->error}", LOG_DEBUG);
741
            return -3;
742
        }
743
    }
744
745
    /**
746
     *  Tag invoice as a paid invoice
747
     *
748
     * @param User   $user       Object user
749
     * @param string $close_code Code indicates whether the class has paid in full while payment is incomplete. Not
750
     *                           implemented yet.
751
     * @param string $close_note Comment informs if the class has been paid while payment is incomplete. Not
752
     *                           implemented yet.
753
     *
754
     * @return int                 Return integer <0 si ko, >0 si ok
755
     * @deprecated
756
     * @see setPaid()
757
     */
758
    public function set_paid($user, $close_code = '', $close_note = '')
759
    {
760
        // phpcs:enable
761
        dol_syslog(get_class($this) . "::set_paid is deprecated, use setPaid instead", LOG_NOTICE);
762
        return $this->setPaid($user, $close_code, $close_note);
763
    }
764
765
766
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
767
768
    /**
769
     *  Tag invoice as a paid invoice
770
     *
771
     * @param User   $user       Object user
772
     * @param string $close_code Code indicates whether the class has paid in full while payment is incomplete. Not
773
     *                           implemented yet.
774
     * @param string $close_note Comment informs if the class has been paid while payment is incomplete. Not
775
     *                           implemented yet.
776
     *
777
     * @return int                 Return integer <0 si ko, >0 si ok
778
     */
779
    public function setPaid($user, $close_code = '', $close_note = '')
780
    {
781
        $error = 0;
782
783
        if ($this->paye != 1) {
0 ignored issues
show
Deprecated Code introduced by
The property DoliModules\Supplier\Mod...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

783
        if (/** @scrutinizer ignore-deprecated */ $this->paye != 1) {

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...
784
            $this->db->begin();
785
786
            $now = dol_now();
787
788
            dol_syslog("FactureFournisseur::setPaid", LOG_DEBUG);
789
790
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture_fourn SET';
791
            $sql .= ' fk_statut = ' . self::STATUS_CLOSED;
792
            if (!$close_code) {
793
                $sql .= ', paye=1';
794
            }
795
            if ($close_code) {
796
                $sql .= ", close_code='" . $this->db->escape($close_code) . "'";
797
            }
798
            if ($close_note) {
799
                $sql .= ", close_note='" . $this->db->escape($close_note) . "'";
800
            }
801
            $sql .= ', fk_user_closing = ' . ((int) $user->id);
802
            $sql .= ", date_closing = '" . $this->db->idate($now) . "'";
803
            $sql .= ' WHERE rowid = ' . ((int) $this->id);
804
805
            $resql = $this->db->query($sql);
806
            if ($resql) {
807
                // Call trigger
808
                $result = $this->call_trigger('BILL_SUPPLIER_PAYED', $user);
809
                if ($result < 0) {
810
                    $error++;
811
                }
812
                // End call triggers
813
            } else {
814
                $error++;
815
                $this->error = $this->db->error();
816
                dol_print_error($this->db);
817
            }
818
819
            if (!$error) {
820
                $this->db->commit();
821
                return 1;
822
            } else {
823
                $this->db->rollback();
824
                return -1;
825
            }
826
        } else {
827
            return 0;
828
        }
829
    }
830
831
    /**
832
     *  Tag the invoice as not fully paid + trigger call BILL_UNPAYED
833
     *  Function used when a direct debit payment is refused,
834
     *  or when the invoice was canceled and reopened.
835
     *
836
     * @param User $user Object user that change status
837
     *
838
     * @return     int                 Return integer <0 si ok, >0 si ok
839
     * @deprecated
840
     * @see setUnpaid()
841
     */
842
    public function set_unpaid($user)
843
    {
844
        // phpcs:enable
845
        dol_syslog(get_class($this) . "::set_unpaid is deprecated, use setUnpaid instead", LOG_NOTICE);
846
        return $this->setUnpaid($user);
847
    }
848
849
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
850
851
    /**
852
     *  Tag the invoice as not fully paid + trigger call BILL_UNPAYED
853
     *  Function used when a direct debit payment is refused,
854
     *  or when the invoice was canceled and reopened.
855
     *
856
     * @param User $user Object user that change status
857
     *
858
     * @return     int                 Return integer <0 si ok, >0 si ok
859
     */
860
    public function setUnpaid($user)
861
    {
862
        $error = 0;
863
864
        $this->db->begin();
865
866
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture_fourn';
867
        $sql .= ' SET paye=0, fk_statut=' . self::STATUS_VALIDATED . ', close_code=null, close_note=null,';
868
        $sql .= ' date_closing=null,';
869
        $sql .= ' fk_user_closing=null';
870
        $sql .= ' WHERE rowid = ' . ((int) $this->id);
871
872
        dol_syslog(get_class($this) . "::set_unpaid", LOG_DEBUG);
873
        $resql = $this->db->query($sql);
874
        if ($resql) {
875
            // Call trigger
876
            $result = $this->call_trigger('BILL_SUPPLIER_UNPAYED', $user);
877
            if ($result < 0) {
878
                $error++;
879
            }
880
            // End call triggers
881
        } else {
882
            $error++;
883
            $this->error = $this->db->error();
884
            dol_print_error($this->db);
885
        }
886
887
        if (!$error) {
888
            $this->db->commit();
889
            return 1;
890
        } else {
891
            $this->db->rollback();
892
            return -1;
893
        }
894
    }
895
896
    /**
897
     *  Tag invoice as canceled, with no payment on it (example for replacement invoice or payment never received) +
898
     *  call trigger BILL_CANCEL Warning, if option to decrease stock on invoice was set, this function does not change
899
     *  stock (it might be a cancel because of no payment even if merchandises were sent).
900
     *
901
     * @param User   $user       Object user making change
902
     * @param string $close_code Code of closing invoice (CLOSECODE_REPLACED, CLOSECODE_...)
903
     * @param string $close_note Comment
904
     *
905
     * @return int                     Return integer <0 if KO, >0 if OK
906
     */
907
    public function setCanceled($user, $close_code = '', $close_note = '')
908
    {
909
        dol_syslog(get_class($this) . "::setCanceled rowid=" . ((int) $this->id), LOG_DEBUG);
910
911
        $this->db->begin();
912
913
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'facture_fourn SET';
914
        $sql .= ' fk_statut=' . self::STATUS_ABANDONED;
915
        if ($close_code) {
916
            $sql .= ", close_code='" . $this->db->escape($close_code) . "'";
917
        }
918
        if ($close_note) {
919
            $sql .= ", close_note='" . $this->db->escape($close_note) . "'";
920
        }
921
        $sql .= " WHERE rowid = " . ((int) $this->id);
922
923
        $resql = $this->db->query($sql);
924
        if ($resql) {
925
            // Bound discounts are deducted from the invoice
926
            // as they have not been used since the invoice is abandoned.
927
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'societe_remise_except';
928
            $sql .= ' SET fk_invoice_supplier = NULL';
929
            $sql .= ' WHERE fk_invoice_supplier = ' . ((int) $this->id);
930
931
            $resql = $this->db->query($sql);
932
            if ($resql) {
933
                // Call trigger
934
                $result = $this->call_trigger('BILL_SUPPLIER_CANCEL', $user);
935
                if ($result < 0) {
936
                    $this->db->rollback();
937
                    return -1;
938
                }
939
                // End call triggers
940
941
                $this->db->commit();
942
                return 1;
943
            } else {
944
                $this->error = $this->db->error() . " sql=" . $sql;
945
                $this->db->rollback();
946
                return -1;
947
            }
948
        } else {
949
            $this->error = $this->db->error() . " sql=" . $sql;
950
            $this->db->rollback();
951
            return -2;
952
        }
953
    }
954
955
    /**
956
     *  Tag invoice as validated + call trigger BILL_VALIDATE
957
     *
958
     * @param User   $user         Object user that validate
959
     * @param string $force_number Reference to force on invoice
960
     * @param int    $idwarehouse  Id of warehouse for stock change
961
     * @param int    $notrigger    1=Does not execute triggers, 0= execute triggers
962
     *
963
     * @return int                     Return integer <0 if KO, =0 if nothing to do, >0 if OK
964
     */
965
    public function validate($user, $force_number = '', $idwarehouse = 0, $notrigger = 0)
966
    {
967
        global $mysoc, $conf, $langs;
968
969
        require_once BASE_PATH . '/../Dolibarr/Lib/Files.php';
970
971
        $now = dol_now();
972
973
        $error = 0;
974
        dol_syslog(get_class($this) . '::validate user=' . $user->id . ', force_number=' . $force_number . ', idwarehouse=' . $idwarehouse);
975
976
        // Force to have object complete for checks
977
        $this->fetch_thirdparty();
978
        $this->fetch_lines();
979
980
        // Check parameters
981
        if ($this->statut > self::STATUS_DRAFT) {   // This is to avoid to validate twice (avoid errors on logs and stock management)
982
            dol_syslog(get_class($this) . "::validate no draft status", LOG_WARNING);
983
            return 0;
984
        }
985
        if (preg_match('/^' . preg_quote($langs->trans("CopyOf") . ' ') . '/', $this->ref_supplier)) {
986
            $langs->load("errors");
987
            $this->error = $langs->trans("ErrorFieldFormat", $langs->transnoentities("RefSupplier")) . '. ' . $langs->trans('RemoveString', $langs->transnoentitiesnoconv("CopyOf"));
988
            return -1;
989
        }
990
        if (count($this->lines) <= 0) {
991
            $langs->load("errors");
992
            $this->error = $langs->trans("ErrorObjectMustHaveLinesToBeValidated", $this->ref);
993
            return -1;
994
        }
995
996
        // Check for mandatory fields in thirdparty (defined into setup)
997
        if (!empty($this->thirdparty) && is_object($this->thirdparty)) {
998
            $array_to_check = ['IDPROF1', 'IDPROF2', 'IDPROF3', 'IDPROF4', 'IDPROF5', 'IDPROF6', 'EMAIL', 'ACCOUNTANCY_CODE_SUPPLIER'];
999
            foreach ($array_to_check as $key) {
1000
                $keymin = strtolower($key);
1001
                if ($keymin == 'accountancy_code_supplier') {
1002
                    $keymin = 'code_compta_fournisseur';
1003
                }
1004
                if (!property_exists($this->thirdparty, $keymin)) {
1005
                    continue;
1006
                }
1007
                $vallabel = $this->thirdparty->$keymin;
1008
1009
                $i = (int) preg_replace('/[^0-9]/', '', $key);
1010
                if ($i > 0) {
1011
                    if ($this->thirdparty->isACompany()) {
1012
                        // Check for mandatory prof id (but only if country is other than ours)
1013
                        if ($mysoc->country_id > 0 && $this->thirdparty->country_id == $mysoc->country_id) {
1014
                            $idprof_mandatory = 'SOCIETE_' . $key . '_INVOICE_MANDATORY';
1015
                            if (!$vallabel && getDolGlobalString($idprof_mandatory)) {
1016
                                $langs->load("errors");
1017
                                $this->error = $langs->trans('ErrorProdIdIsMandatory', $langs->transcountry('ProfId' . $i, $this->thirdparty->country_code)) . ' (' . $langs->trans("ForbiddenBySetupRules") . ') [' . $langs->trans('Company') . ' : ' . $this->thirdparty->name . ']';
1018
                                dol_syslog(__METHOD__ . ' ' . $this->error, LOG_ERR);
1019
                                return -1;
1020
                            }
1021
                        }
1022
                    }
1023
                } else {
1024
                    if ($key == 'EMAIL') {
1025
                        // Check for mandatory
1026
                        if (getDolGlobalString('SOCIETE_EMAIL_INVOICE_MANDATORY') && !isValidEmail($this->thirdparty->email)) {
1027
                            $langs->load("errors");
1028
                            $this->error = $langs->trans("ErrorBadEMail", $this->thirdparty->email) . ' (' . $langs->trans("ForbiddenBySetupRules") . ') [' . $langs->trans('Company') . ' : ' . $this->thirdparty->name . ']';
1029
                            dol_syslog(__METHOD__ . ' ' . $this->error, LOG_ERR);
1030
                            return -1;
1031
                        }
1032
                    } elseif ($key == 'ACCOUNTANCY_CODE_SUPPLIER') {
1033
                        // Check for mandatory
1034
                        if (getDolGlobalString('SOCIETE_ACCOUNTANCY_CODE_SUPPLIER_INVOICE_MANDATORY') && empty($this->thirdparty->code_compta_fournisseur)) {
1035
                            $langs->load("errors");
1036
                            $this->error = $langs->trans("ErrorAccountancyCodeSupplierIsMandatory", $this->thirdparty->name) . ' (' . $langs->trans("ForbiddenBySetupRules") . ')';
1037
                            dol_syslog(__METHOD__ . ' ' . $this->error, LOG_ERR);
1038
                            return -1;
1039
                        }
1040
                    }
1041
                }
1042
            }
1043
        }
1044
1045
        $this->db->begin();
1046
1047
        // Define new ref
1048
        if ($force_number) {
1049
            $num = $force_number;
1050
        } elseif (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
1051
            $num = $this->getNextNumRef($this->thirdparty);
1052
        } else {
1053
            $num = $this->ref;
1054
        }
1055
        $this->newref = dol_sanitizeFileName($num);
1056
1057
        $sql = "UPDATE " . MAIN_DB_PREFIX . "facture_fourn";
1058
        $sql .= " SET ref='" . $this->db->escape($num) . "', fk_statut = 1, fk_user_valid = " . ((int) $user->id) . ", date_valid = '" . $this->db->idate($now) . "'";
1059
        $sql .= " WHERE rowid = " . ((int) $this->id);
1060
1061
        dol_syslog(get_class($this) . "::validate", LOG_DEBUG);
1062
        $resql = $this->db->query($sql);
1063
        if ($resql) {
1064
            // Si on incrémente le produit principal et ses composants à la validation de facture fournisseur
1065
            if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL')) {
1066
                require_once DOL_DOCUMENT_ROOT . '/product/stock/class/mouvementstock.class.php';
1067
                $langs->load("agenda");
1068
1069
                $cpt = count($this->lines);
1070
                for ($i = 0; $i < $cpt; $i++) {
1071
                    if ($this->lines[$i]->fk_product > 0) {
1072
                        $mouvP = new MouvementStock($this->db);
0 ignored issues
show
Bug introduced by
The type DoliModules\Supplier\Model\MouvementStock was not found. Did you mean MouvementStock? If so, make sure to prefix the type with \.
Loading history...
1073
                        $mouvP->origin = &$this;
1074
                        $mouvP->setOrigin($this->element, $this->id);
1075
                        // We increase stock for product
1076
                        $up_ht_disc = $this->lines[$i]->subprice;
1077
                        if (!empty($this->lines[$i]->remise_percent) && !getDolGlobalString('STOCK_EXCLUDE_DISCOUNT_FOR_PMP')) {
1078
                            $up_ht_disc = price2num($up_ht_disc * (100 - $this->lines[$i]->remise_percent) / 100, 'MU');
1079
                        }
1080
                        if ($this->type == FactureFournisseur::TYPE_CREDIT_NOTE) {
1081
                            $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $up_ht_disc, $langs->trans("InvoiceValidatedInDolibarr", $num));
1082
                        } else {
1083
                            $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $up_ht_disc, $langs->trans("InvoiceValidatedInDolibarr", $num));
1084
                        }
1085
                        if ($result < 0) {
1086
                            $this->error = $mouvP->error;
1087
                            if (count($mouvP->errors)) {
1088
                                $this->errors = $mouvP->errors;
1089
                            }
1090
                            return -2;
1091
                        }
1092
                    }
1093
                }
1094
            }
1095
1096
            // Triggers call
1097
            if (!$error && empty($notrigger)) {
1098
                // Call trigger
1099
                $result = $this->call_trigger('BILL_SUPPLIER_VALIDATE', $user);
1100
                if ($result < 0) {
1101
                    $error++;
1102
                }
1103
                // End call triggers
1104
            }
1105
1106
            if (!$error) {
1107
                $this->oldref = $this->ref;
1108
1109
                // Rename directory if dir was a temporary ref
1110
                if (preg_match('/^[\(]?PROV/i', $this->ref)) {
1111
                    // Now we rename also files into index
1112
                    $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) . "'";
1113
                    $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;
1114
                    $resql = $this->db->query($sql);
1115
                    if (!$resql) {
1116
                        $error++;
1117
                        $this->error = $this->db->lasterror();
1118
                    }
1119
                    $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) . "'";
1120
                    $sql .= " WHERE filepath = 'fournisseur/facture/" . get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier') . $this->db->escape($this->ref) . "' and entity = " . $conf->entity;
1121
                    $resql = $this->db->query($sql);
1122
                    if (!$resql) {
1123
                        $error++;
1124
                        $this->error = $this->db->lasterror();
1125
                    }
1126
1127
                    // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
1128
                    $oldref = dol_sanitizeFileName($this->ref);
1129
                    $dirsource = $conf->fournisseur->facture->dir_output . '/' . get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier') . $oldref;
1130
                    $dirdest = $conf->fournisseur->facture->dir_output . '/' . get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier') . $this->newref;
1131
                    if (!$error && file_exists($dirsource)) {
1132
                        dol_syslog(get_class($this) . "::validate rename dir " . $dirsource . " into " . $dirdest);
1133
1134
                        if (@rename($dirsource, $dirdest)) {
1135
                            dol_syslog("Rename ok");
1136
                            // Rename docs starting with $oldref with $this->newref
1137
                            $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, '/'));
1138
                            foreach ($listoffiles as $fileentry) {
1139
                                $dirsource = $fileentry['name'];
1140
                                $dirdest = preg_replace('/^' . preg_quote($oldref, '/') . '/', $this->newref, $dirsource);
1141
                                $dirsource = $fileentry['path'] . '/' . $dirsource;
1142
                                $dirdest = $fileentry['path'] . '/' . $dirdest;
1143
                                @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

1143
                                /** @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...
1144
                            }
1145
                        }
1146
                    }
1147
                }
1148
            }
1149
1150
            // Set new ref and define current statut
1151
            if (!$error) {
1152
                $this->ref = $this->newref;
1153
                $this->statut = self::STATUS_VALIDATED;
1154
                //$this->date_validation=$now; this is stored into log table
1155
            }
1156
1157
            if (!$error) {
1158
                $this->db->commit();
1159
                return 1;
1160
            } else {
1161
                $this->db->rollback();
1162
                return -1;
1163
            }
1164
        } else {
1165
            $this->error = $this->db->error();
1166
            $this->db->rollback();
1167
            return -1;
1168
        }
1169
    }
1170
1171
    /**
1172
     *      Return next reference of supplier invoice not already used (or last reference)
1173
     *      according to numbering module defined into constant INVOICE_SUPPLIER_ADDON_NUMBER
1174
     *
1175
     * @param Societe $soc  Thirdparty object
1176
     * @param string  $mode 'next' for next value or 'last' for last value
1177
     *
1178
     * @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...
1179
     */
1180
    public function getNextNumRef($soc, $mode = 'next')
1181
    {
1182
        global $db, $langs, $conf;
1183
        $langs->load("orders");
1184
1185
        // Clean parameters (if not defined or using deprecated value)
1186
        if (!getDolGlobalString('INVOICE_SUPPLIER_ADDON_NUMBER')) {
1187
            $conf->global->INVOICE_SUPPLIER_ADDON_NUMBER = 'mod_facture_fournisseur_cactus';
1188
        }
1189
1190
        $mybool = false;
1191
1192
        $file = getDolGlobalString('INVOICE_SUPPLIER_ADDON_NUMBER') . ".php";
1193
        $classname = getDolGlobalString('INVOICE_SUPPLIER_ADDON_NUMBER');
1194
1195
        // Include file with class
1196
        $dirmodels = array_merge(['/'], (array) $conf->modules_parts['models']);
1197
1198
        foreach ($dirmodels as $reldir) {
1199
            $dir = dol_buildpath($reldir . "core/modules/supplier_invoice/");
1200
1201
            // Load file with numbering class (if found)
1202
            $mybool |= @include_once $dir . $file;
1203
        }
1204
1205
        if ($mybool === false) {
1206
            dol_print_error(null, "Failed to include file " . $file);
1207
            return '';
1208
        }
1209
1210
        $obj = new $classname();
1211
        $numref = "";
1212
        $numref = $obj->getNextValue($soc, $this, $mode);
1213
1214
        if ($numref != "") {
1215
            return $numref;
1216
        } else {
1217
            $this->error = $obj->error;
1218
            return -1;
1219
        }
1220
    }
1221
1222
    /**
1223
     *  Set draft status
1224
     *
1225
     * @param User $user        Object user that modify
1226
     * @param int  $idwarehouse Id warehouse to use for stock change.
1227
     * @param int  $notrigger   1=Does not execute triggers, 0= execute triggers
1228
     *
1229
     * @return int                     Return integer <0 if KO, >0 if OK
1230
     */
1231
    public function setDraft($user, $idwarehouse = -1, $notrigger = 0)
1232
    {
1233
        // phpcs:enable
1234
        global $conf, $langs;
1235
1236
        $error = 0;
1237
1238
        if ($this->statut == self::STATUS_DRAFT) {
1239
            dol_syslog(__METHOD__ . " already draft status", LOG_WARNING);
1240
            return 0;
1241
        }
1242
1243
        dol_syslog(__METHOD__, LOG_DEBUG);
1244
1245
        $this->db->begin();
1246
1247
        $sql = "UPDATE " . MAIN_DB_PREFIX . "facture_fourn";
1248
        $sql .= " SET fk_statut = " . self::STATUS_DRAFT;
1249
        $sql .= " WHERE rowid = " . ((int) $this->id);
1250
1251
        $result = $this->db->query($sql);
1252
        if ($result) {
1253
            if (!$error) {
1254
                $this->oldcopy = clone $this;
0 ignored issues
show
Documentation Bug introduced by
It seems like clone $this of type DoliModules\Supplier\Model\FactureFournisseur is incompatible with the declared type CommonObject of property $oldcopy.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
1255
            }
1256
1257
            // Si on incremente le produit principal et ses composants a la validation de facture fournisseur, on decremente
1258
            if ($result >= 0 && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL')) {
1259
                require_once DOL_DOCUMENT_ROOT . '/product/stock/class/mouvementstock.class.php';
1260
                $langs->load("agenda");
1261
1262
                $cpt = count($this->lines);
1263
                for ($i = 0; $i < $cpt; $i++) {
1264
                    if ($this->lines[$i]->fk_product > 0) {
1265
                        $mouvP = new MouvementStock($this->db);
1266
                        $mouvP->origin = &$this;
1267
                        $mouvP->setOrigin($this->element, $this->id);
1268
                        // We increase stock for product
1269
                        if ($this->type == FactureFournisseur::TYPE_CREDIT_NOTE) {
1270
                            $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceBackToDraftInDolibarr", $this->ref));
1271
                        } else {
1272
                            $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceBackToDraftInDolibarr", $this->ref));
1273
                        }
1274
                    }
1275
                }
1276
            }
1277
            // Triggers call
1278
            if (!$error && empty($notrigger)) {
1279
                // Call trigger
1280
                $result = $this->call_trigger('BILL_SUPPLIER_UNVALIDATE', $user);
1281
                if ($result < 0) {
1282
                    $error++;
1283
                }
1284
                // End call triggers
1285
            }
1286
            if ($error == 0) {
1287
                $this->db->commit();
1288
                return 1;
1289
            } else {
1290
                $this->db->rollback();
1291
                return -1;
1292
            }
1293
        } else {
1294
            $this->error = $this->db->error();
1295
            $this->db->rollback();
1296
            return -1;
1297
        }
1298
    }
1299
1300
    /**
1301
     *  Delete a detail line from database
1302
     *
1303
     * @param int $rowid     Id of line to delete
1304
     * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
1305
     *
1306
     * @return int                     Return integer <0 if KO, >0 if OK
1307
     */
1308
    public function deleteLine($rowid, $notrigger = 0)
1309
    {
1310
        if (!$rowid) {
1311
            $rowid = $this->id;
1312
        }
1313
1314
        $this->db->begin();
1315
1316
        // Free the discount linked to a line of invoice
1317
        $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'societe_remise_except';
1318
        $sql .= ' SET fk_invoice_supplier_line = NULL';
1319
        $sql .= ' WHERE fk_invoice_supplier_line = ' . ((int) $rowid);
1320
1321
        dol_syslog(get_class($this) . "::deleteline", LOG_DEBUG);
1322
        $result = $this->db->query($sql);
1323
        if (!$result) {
1324
            $this->error = $this->db->error();
1325
            $this->db->rollback();
1326
            return -2;
1327
        }
1328
1329
        $line = new SupplierInvoiceLine($this->db);
1330
1331
        if ($line->fetch($rowid) < 1) {
1332
            return -1;
1333
        }
1334
1335
        $res = $line->delete($notrigger);
1336
1337
        if ($res < 1) {
1338
            $this->errors[] = $line->error;
1339
            $this->db->rollback();
1340
            return -3;
1341
        } else {
1342
            $res = $this->update_price(1);
1343
1344
            if ($res > 0) {
1345
                $this->db->commit();
1346
                return 1;
1347
            } else {
1348
                $this->db->rollback();
1349
                $this->error = $this->db->lasterror();
1350
                return -4;
1351
            }
1352
        }
1353
    }
1354
1355
    /**
1356
     *  Delete invoice from database
1357
     *
1358
     * @param User $user      User object
1359
     * @param int  $notrigger 1=Does not execute triggers, 0= execute triggers
1360
     *
1361
     * @return     int                     Return integer <0 if KO, >0 if OK
1362
     */
1363
    public function delete(User $user, $notrigger = 0)
1364
    {
1365
        global $conf;
1366
1367
        $rowid = $this->id;
1368
1369
        dol_syslog("FactureFournisseur::delete rowid=" . $rowid, LOG_DEBUG);
1370
1371
        // TODO Test if there is at least on payment. If yes, refuse to delete.
1372
1373
        $error = 0;
1374
        $this->db->begin();
1375
1376
        if (!$error && !$notrigger) {
1377
            // Call trigger
1378
            $result = $this->call_trigger('BILL_SUPPLIER_DELETE', $user);
1379
            if ($result < 0) {
1380
                $this->db->rollback();
1381
                return -1;
1382
            }
1383
            // Fin appel triggers
1384
        }
1385
1386
        if (!$error) {
1387
            // If invoice was converted into a discount not yet consumed, we remove discount
1388
            $sql = 'DELETE FROM ' . MAIN_DB_PREFIX . 'societe_remise_except';
1389
            $sql .= ' WHERE fk_invoice_supplier_source = ' . ((int) $rowid);
1390
            $sql .= ' AND fk_invoice_supplier_line IS NULL';
1391
            $resql = $this->db->query($sql);
1392
1393
            // If invoice has consumned discounts
1394
            $this->fetch_lines();
1395
            $list_rowid_det = [];
1396
            foreach ($this->lines as $key => $invoiceline) {
1397
                $list_rowid_det[] = $invoiceline->rowid;
1398
            }
1399
1400
            // Consumned discounts are freed
1401
            if (count($list_rowid_det)) {
1402
                $sql = 'UPDATE ' . MAIN_DB_PREFIX . 'societe_remise_except';
1403
                $sql .= ' SET fk_invoice_supplier = NULL, fk_invoice_supplier_line = NULL';
1404
                $sql .= ' WHERE fk_invoice_supplier_line IN (' . $this->db->sanitize(implode(',', $list_rowid_det)) . ')';
1405
1406
                dol_syslog(get_class($this) . "::delete", LOG_DEBUG);
1407
                if (!$this->db->query($sql)) {
1408
                    $error++;
1409
                }
1410
            }
1411
        }
1412
1413
        if (!$error) {
1414
            $main = MAIN_DB_PREFIX . 'facture_fourn_det';
1415
            $ef = $main . "_extrafields";
1416
            $sqlef = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM " . $main . " WHERE fk_facture_fourn = " . ((int) $rowid) . ")";
1417
            $resqlef = $this->db->query($sqlef);
1418
            $sql = 'DELETE FROM ' . MAIN_DB_PREFIX . 'facture_fourn_det WHERE fk_facture_fourn = ' . ((int) $rowid);
1419
            dol_syslog(get_class($this) . "::delete", LOG_DEBUG);
1420
            $resql = $this->db->query($sql);
1421
            if ($resqlef && $resql) {
1422
                $sql = 'DELETE FROM ' . MAIN_DB_PREFIX . 'facture_fourn WHERE rowid = ' . ((int) $rowid);
1423
                dol_syslog(get_class($this) . "::delete", LOG_DEBUG);
1424
                $resql2 = $this->db->query($sql);
1425
                if (!$resql2) {
1426
                    $error++;
1427
                }
1428
            } else {
1429
                $error++;
1430
            }
1431
        }
1432
1433
        if (!$error) {
1434
            // Delete linked object
1435
            $res = $this->deleteObjectLinked();
1436
            if ($res < 0) {
1437
                $error++;
1438
            }
1439
        }
1440
1441
        if (!$error) {
1442
            // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
1443
            $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1444
            $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1445
1446
            // We remove directory
1447
            if ($conf->fournisseur->facture->dir_output) {
1448
                include_once BASE_PATH . '/../Dolibarr/Lib/Files.php';
1449
1450
                $ref = dol_sanitizeFileName($this->ref);
1451
                $dir = $conf->fournisseur->facture->dir_output . '/' . get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier') . $ref;
1452
                $file = $dir . "/" . $ref . ".pdf";
1453
                if (file_exists($file)) {
1454
                    if (!dol_delete_file($file, 0, 0, 0, $this)) { // For triggers
1455
                        $this->error = 'ErrorFailToDeleteFile';
1456
                        $error++;
1457
                    }
1458
                }
1459
                if (file_exists($dir)) {
1460
                    $res = @dol_delete_dir_recursive($dir);
1461
1462
                    if (!$res) {
1463
                        $this->error = 'ErrorFailToDeleteDir';
1464
                        $error++;
1465
                    }
1466
                }
1467
            }
1468
        }
1469
1470
        // Remove extrafields
1471
        if (!$error) {
1472
            $result = $this->deleteExtraFields();
1473
            if ($result < 0) {
1474
                $error++;
1475
                dol_syslog(get_class($this) . "::delete error -4 " . $this->error, LOG_ERR);
1476
            }
1477
        }
1478
1479
        if (!$error) {
1480
            dol_syslog(get_class($this) . "::delete $this->id by $user->id", LOG_DEBUG);
1481
            $this->db->commit();
1482
            return 1;
1483
        } else {
1484
            $this->error = $this->db->lasterror();
1485
            $this->db->rollback();
1486
            return -$error;
1487
        }
1488
    }
1489
1490
    /**
1491
     *  Loads the info order information into the invoice object
1492
     *
1493
     * @param int $id Id of the invoice to load
1494
     *
1495
     * @return void
1496
     */
1497
    public function info($id)
1498
    {
1499
        $sql = 'SELECT c.rowid, datec, tms as datem, ';
1500
        $sql .= ' fk_user_author, fk_user_modif, fk_user_valid';
1501
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'facture_fourn as c';
1502
        $sql .= ' WHERE c.rowid = ' . ((int) $id);
1503
1504
        $result = $this->db->query($sql);
1505
        if ($result) {
1506
            if ($this->db->num_rows($result)) {
1507
                $obj = $this->db->fetch_object($result);
1508
1509
                $this->id = $obj->rowid;
1510
1511
                $this->user_creation_id = $obj->fk_user_author;
1512
                $this->user_validation_id = $obj->fk_user_valid;
1513
                $this->user_modification_id = $obj->fk_user_modif;
1514
                $this->date_creation = $this->db->jdate($obj->datec);
1515
                $this->date_modification = $this->db->jdate($obj->datem);
1516
                //$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)
1517
            }
1518
            $this->db->free($result);
1519
        } else {
1520
            dol_print_error($this->db);
1521
        }
1522
    }
1523
1524
    /**
1525
     *  Return list of replaceable invoices
1526
     *  Status valid or abandoned for other reason + not paid + no payment + not already replaced
1527
     *
1528
     * @param int $socid Thirdparty id
1529
     *
1530
     * @return     array|int           Table of invoices ('id'=>id, 'ref'=>ref, 'status'=>status, 'paymentornot'=>0/1)
1531
     *                                  <0 if error
1532
     */
1533
    public function list_replacable_supplier_invoices($socid = 0)
1534
    {
1535
        // phpcs:enable
1536
        global $conf;
1537
1538
        $return = [];
1539
1540
        $sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut,";
1541
        $sql .= " ff.rowid as rowidnext";
1542
        $sql .= " FROM " . MAIN_DB_PREFIX . "facture_fourn as f";
1543
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "paiementfourn_facturefourn as pf ON f.rowid = pf.fk_facturefourn";
1544
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "facture_fourn as ff ON f.rowid = ff.fk_facture_source";
1545
        $sql .= " WHERE (f.fk_statut = " . self::STATUS_VALIDATED . " OR (f.fk_statut = " . self::STATUS_ABANDONED . " AND f.close_code = '" . self::CLOSECODE_ABANDONED . "'))";
1546
        $sql .= " AND f.entity = " . $conf->entity;
1547
        $sql .= " AND f.paye = 0"; // Pas classee payee completement
1548
        $sql .= " AND pf.fk_paiementfourn IS NULL"; // Aucun paiement deja fait
1549
        $sql .= " AND ff.fk_statut IS NULL"; // Renvoi vrai si pas facture de replacement
1550
        if ($socid > 0) {
1551
            $sql .= " AND f.fk_soc = " . ((int) $socid);
1552
        }
1553
        $sql .= " ORDER BY f.ref";
1554
1555
        dol_syslog(get_class($this) . "::list_replacable_supplier_invoices", LOG_DEBUG);
1556
        $resql = $this->db->query($sql);
1557
        if ($resql) {
1558
            while ($obj = $this->db->fetch_object($resql)) {
1559
                $return[$obj->rowid] = [
1560
                    'id' => $obj->rowid,
1561
                    'ref' => $obj->ref,
1562
                    'status' => $obj->fk_statut,
1563
                ];
1564
            }
1565
            //print_r($return);
1566
            return $return;
1567
        } else {
1568
            $this->error = $this->db->error();
1569
            return -1;
1570
        }
1571
    }
1572
1573
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1574
1575
    /**
1576
     *  Return list of qualifying invoices for correction by credit note
1577
     *  Invoices that respect the following rules are returned:
1578
     *  (validated + payment in progress) or classified (paid in full or paid in part) + not already replaced + not
1579
     *  already having
1580
     *
1581
     * @param int $socid Thirdparty id
1582
     *
1583
     * @return     array|int           Table of invoices ($id => array('ref'=>,'paymentornot'=>,'status'=>,'paye'=>)
1584
     *                                  <0 if error
1585
     */
1586
    public function list_qualified_avoir_supplier_invoices($socid = 0)
1587
    {
1588
        // phpcs:enable
1589
        global $conf;
1590
1591
        $return = [];
1592
1593
        $sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut, f.type, f.subtype, f.paye, pf.fk_paiementfourn";
1594
        $sql .= " FROM " . MAIN_DB_PREFIX . "facture_fourn as f";
1595
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "paiementfourn_facturefourn as pf ON f.rowid = pf.fk_facturefourn";
1596
        $sql .= " WHERE f.entity = " . $conf->entity;
1597
        $sql .= " AND f.fk_statut in (" . self::STATUS_VALIDATED . "," . self::STATUS_CLOSED . ")";
1598
        $sql .= " AND NOT EXISTS (SELECT rowid from " . MAIN_DB_PREFIX . "facture_fourn as ff WHERE f.rowid = ff.fk_facture_source";
1599
        $sql .= " AND ff.type=" . self::TYPE_REPLACEMENT . ")";
1600
        $sql .= " AND f.type != " . self::TYPE_CREDIT_NOTE; // Type non 2 si facture non avoir
1601
        if ($socid > 0) {
1602
            $sql .= " AND f.fk_soc = " . ((int) $socid);
1603
        }
1604
        $sql .= " ORDER BY f.ref";
1605
1606
        dol_syslog(get_class($this) . "::list_qualified_avoir_supplier_invoices", LOG_DEBUG);
1607
        $resql = $this->db->query($sql);
1608
        if ($resql) {
1609
            while ($obj = $this->db->fetch_object($resql)) {
1610
                $qualified = 0;
1611
                if ($obj->fk_statut == self::STATUS_VALIDATED) {
1612
                    $qualified = 1;
1613
                }
1614
                if ($obj->fk_statut == self::STATUS_CLOSED) {
1615
                    $qualified = 1;
1616
                }
1617
                if ($qualified) {
1618
                    $paymentornot = ($obj->fk_paiementfourn ? 1 : 0);
1619
                    $return[$obj->rowid] = ['ref' => $obj->ref, 'status' => $obj->fk_statut, 'type' => $obj->type, 'paye' => $obj->paye, 'paymentornot' => $paymentornot];
1620
                }
1621
            }
1622
1623
            return $return;
1624
        } else {
1625
            $this->error = $this->db->error();
1626
            return -1;
1627
        }
1628
    }
1629
1630
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1631
1632
    /**
1633
     *  Load indicators for dashboard (this->nbtodo and this->nbtodolate)
1634
     *
1635
     * @param User $user Object user
1636
     *
1637
     * @return WorkboardResponse|int Return integer <0 if KO, WorkboardResponse if OK
1638
     */
1639
    public function load_board($user)
1640
    {
1641
        // phpcs:enable
1642
        global $conf, $langs;
1643
1644
        $sql = 'SELECT ff.rowid, ff.date_lim_reglement as datefin, ff.fk_statut as status, ff.total_ht, ff.total_ttc';
1645
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'facture_fourn as ff';
1646
        if (!$user->hasRight("societe", "client", "voir") && !$user->socid) {
1647
            $sql .= " JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON ff.fk_soc = sc.fk_soc AND sc.fk_user = " . ((int) $user->id);
1648
        }
1649
        $sql .= ' WHERE ff.paye = 0';
1650
        $sql .= " AND ff.fk_statut IN (" . self::STATUS_VALIDATED . ")";
1651
        $sql .= " AND ff.entity = " . $conf->entity;
1652
        if ($user->socid) {
1653
            $sql .= ' AND ff.fk_soc = ' . ((int) $user->socid);
1654
        }
1655
1656
        $resql = $this->db->query($sql);
1657
        if ($resql) {
1658
            $langs->load("bills");
1659
            $now = dol_now();
1660
1661
            $response = new WorkboardResponse();
1662
            $response->warning_delay = $conf->facture->fournisseur->warning_delay / 60 / 60 / 24;
1663
            $response->label = $langs->trans("SupplierBillsToPay");
1664
            $response->labelShort = $langs->trans("StatusToPay");
1665
1666
            $response->url = DOL_URL_ROOT . '/fourn/facture/list.php?search_status=1&mainmenu=billing&leftmenu=suppliers_bills';
1667
            $response->img = img_object($langs->trans("Bills"), "bill");
1668
1669
            $facturestatic = new FactureFournisseur($this->db);
1670
1671
            while ($obj = $this->db->fetch_object($resql)) {
1672
                $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...
1673
                $facturestatic->statut = $obj->status;  // For backward compatibility
1674
                $facturestatic->status = $obj->status;
1675
1676
                $response->nbtodo++;
1677
                $response->total += $obj->total_ht;
1678
1679
                if ($facturestatic->hasDelay()) {
1680
                    $response->nbtodolate++;
1681
                    $response->url_late = DOL_URL_ROOT . '/fourn/facture/list.php?search_option=late&mainmenu=billing&leftmenu=suppliers_bills';
1682
                }
1683
            }
1684
1685
            $this->db->free($resql);
1686
            return $response;
1687
        } else {
1688
            dol_print_error($this->db);
1689
            $this->error = $this->db->error();
1690
            return -1;
1691
        }
1692
    }
1693
1694
    // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1695
1696
    /**
1697
     * Is the payment of the supplier invoice having a delay?
1698
     *
1699
     * @return bool
1700
     */
1701
    public function hasDelay()
1702
    {
1703
        global $conf;
1704
1705
        $now = dol_now();
1706
1707
        if (!$this->date_echeance) {
1708
            return false;
1709
        }
1710
1711
        $status = isset($this->status) ? $this->status : $this->statut;
1712
1713
        return ($status == self::STATUS_VALIDATED) && ($this->date_echeance < ($now - $conf->facture->fournisseur->warning_delay));
1714
    }
1715
1716
    /**
1717
     *  Initialise an instance with random values.
1718
     *  Used to build previews or test instances.
1719
     *  id must be 0 if object instance is a specimen.
1720
     *
1721
     * @param string $option ''=Create a specimen invoice with lines, 'nolines'=No lines
1722
     *
1723
     * @return int
1724
     */
1725
    public function initAsSpecimen($option = '')
1726
    {
1727
        global $langs, $conf;
1728
1729
        $now = dol_now();
1730
1731
        // Load array of products prodids
1732
        $num_prods = 0;
1733
        $prodids = [];
1734
1735
        $sql = "SELECT rowid";
1736
        $sql .= " FROM " . MAIN_DB_PREFIX . "product";
1737
        $sql .= " WHERE entity IN (" . getEntity('product') . ")";
1738
        $sql .= $this->db->plimit(100);
1739
1740
        $resql = $this->db->query($sql);
1741
        if ($resql) {
1742
            $num_prods = $this->db->num_rows($resql);
1743
            $i = 0;
1744
            while ($i < $num_prods) {
1745
                $i++;
1746
                $row = $this->db->fetch_row($resql);
1747
                $prodids[$i] = $row[0];
1748
            }
1749
        }
1750
1751
        // Initialise parameters
1752
        $this->id = 0;
1753
        $this->ref = 'SPECIMEN';
1754
        $this->ref_supplier = 'SUPPLIER_REF_SPECIMEN';
1755
        $this->specimen = 1;
1756
        $this->socid = 1;
1757
        $this->date = $now;
1758
        $this->date_lim_reglement = $this->date + 3600 * 24 * 30;
1759
        $this->cond_reglement_code = 'RECEP';
1760
        $this->mode_reglement_code = 'CHQ';
1761
1762
        $this->note_public = 'This is a comment (public)';
1763
        $this->note_private = 'This is a comment (private)';
1764
1765
        $this->multicurrency_tx = 1;
1766
        $this->multicurrency_code = $conf->currency;
1767
1768
        $xnbp = 0;
1769
        if (empty($option) || $option != 'nolines') {
1770
            // Lines
1771
            $nbp = 5;
1772
            while ($xnbp < $nbp) {
1773
                $line = new SupplierInvoiceLine($this->db);
1774
                $line->desc = $langs->trans("Description") . " " . $xnbp;
1775
                $line->qty = 1;
1776
                $line->subprice = 100;
1777
                $line->pu_ht = 100; // the canelle template use pu_ht and not subprice
0 ignored issues
show
Deprecated Code introduced by
The property DoliModules\Supplier\Mod...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

1777
                /** @scrutinizer ignore-deprecated */ $line->pu_ht = 100; // 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...
1778
                $line->price = 100;
0 ignored issues
show
Bug introduced by
The property price does not exist on DoliModules\Supplier\Model\SupplierInvoiceLine. Did you mean price_level?
Loading history...
1779
                $line->tva_tx = 19.6;
1780
                $line->localtax1_tx = 0;
1781
                $line->localtax2_tx = 0;
1782
                if ($xnbp == 2) {
1783
                    $line->total_ht = 50;
1784
                    $line->total_ttc = 59.8;
1785
                    $line->total_tva = 9.8;
1786
                    $line->remise_percent = 50;
1787
                } else {
1788
                    $line->total_ht = 100;
1789
                    $line->total_ttc = 119.6;
1790
                    $line->total_tva = 19.6;
1791
                    $line->remise_percent = 0;
1792
                }
1793
1794
                if ($num_prods > 0) {
1795
                    $prodid = mt_rand(1, $num_prods);
1796
                    $line->fk_product = $prodids[$prodid];
1797
                }
1798
                $line->product_type = 0;
1799
1800
                $this->lines[$xnbp] = $line;
1801
1802
                $this->total_ht += $line->total_ht;
1803
                $this->total_tva += $line->total_tva;
1804
                $this->total_ttc += $line->total_ttc;
1805
1806
                $xnbp++;
1807
            }
1808
        }
1809
1810
        $this->total_ht = $xnbp * 100;
1811
        $this->total_tva = $xnbp * 19.6;
1812
        $this->total_ttc = $xnbp * 119.6;
1813
1814
        return 1;
1815
    }
1816
1817
    /**
1818
     *      Load indicators for dashboard (this->nbtodo and this->nbtodolate)
1819
     *
1820
     * @return         int     Return integer <0 if KO, >0 if OK
1821
     */
1822
    public function loadStateBoard()
1823
    {
1824
        global $conf, $user;
1825
1826
        $this->nb = [];
1827
1828
        $clause = "WHERE";
1829
1830
        $sql = "SELECT count(f.rowid) as nb";
1831
        $sql .= " FROM " . MAIN_DB_PREFIX . "facture_fourn as f";
1832
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON f.fk_soc = s.rowid";
1833
        if (!$user->hasRight("societe", "client", "voir") && !$user->socid) {
1834
            $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON s.rowid = sc.fk_soc";
1835
            $sql .= " WHERE sc.fk_user = " . ((int) $user->id);
1836
            $clause = "AND";
1837
        }
1838
        $sql .= " " . $clause . " f.entity = " . $conf->entity;
1839
1840
        $resql = $this->db->query($sql);
1841
        if ($resql) {
1842
            while ($obj = $this->db->fetch_object($resql)) {
1843
                $this->nb["supplier_invoices"] = $obj->nb;
1844
            }
1845
            $this->db->free($resql);
1846
            return 1;
1847
        } else {
1848
            dol_print_error($this->db);
1849
            $this->error = $this->db->error();
1850
            return -1;
1851
        }
1852
    }
1853
1854
    /**
1855
     *  Load an object from its id and create a new one in database
1856
     *
1857
     * @param User $user         User that clone
1858
     * @param int  $fromid       Id of object to clone
1859
     * @param int  $invertdetail Reverse sign of amounts for lines
1860
     *
1861
     * @return     int                     New id of clone
1862
     */
1863
    public function createFromClone(User $user, $fromid, $invertdetail = 0)
1864
    {
1865
        global $conf, $langs;
1866
1867
        $error = 0;
1868
1869
        $object = new FactureFournisseur($this->db);
1870
1871
        $this->db->begin();
1872
1873
        // Load source object
1874
        $object->fetch($fromid);
1875
        $object->id = 0;
1876
        $object->statut = self::STATUS_DRAFT;   // For backward compatibility
1877
        $object->status = self::STATUS_DRAFT;
1878
1879
        $object->fetch_thirdparty(); // We need it to recalculate VAT localtaxes according to main sale taxes and vendor
1880
1881
        // Clear fields
1882
        $object->ref_supplier = (empty($this->ref_supplier) ? $langs->trans("CopyOf") . ' ' . $object->ref_supplier : $this->ref_supplier);
1883
        $object->author = $user->id;
0 ignored issues
show
Deprecated Code introduced by
The property DoliModules\Supplier\Mod...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

1883
        /** @scrutinizer ignore-deprecated */ $object->author = $user->id;

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

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

Loading history...
1884
        $object->user_validation_id = 0;
1885
        $object->fk_facture_source = 0;
1886
        $object->date_creation = '';
1887
        $object->date_validation = '';
1888
        $object->date = (empty($this->date) ? dol_now() : $this->date);
1889
        $object->ref_client = '';
1890
        $object->close_code = '';
1891
        $object->close_note = '';
1892
        if (getDolGlobalInt('MAIN_DONT_KEEP_NOTE_ON_CLONING') == 1) {
1893
            $object->note_private = '';
1894
            $object->note_public = '';
1895
        }
1896
1897
        $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...
1898
1899
        // Loop on each line of new invoice
1900
        foreach ($object->lines as $i => $line) {
1901
            if (isset($object->lines[$i]->info_bits) && ($object->lines[$i]->info_bits & 0x02) == 0x02) {   // We do not clone line of discounts
1902
                unset($object->lines[$i]);
1903
            }
1904
        }
1905
1906
        // Create clone
1907
        $object->context['createfromclone'] = 'createfromclone';
1908
        $result = $object->create($user);
1909
1910
        // Other options
1911
        if ($result < 0) {
1912
            $this->error = $object->error;
1913
            $this->errors = $object->errors;
1914
            $error++;
1915
        }
1916
1917
        if (!$error) {
1918
        }
1919
1920
        unset($object->context['createfromclone']);
1921
1922
        // End
1923
        if (!$error) {
1924
            $this->db->commit();
1925
            return $object->id;
1926
        } else {
1927
            $this->db->rollback();
1928
            return -1;
1929
        }
1930
    }
1931
1932
    /**
1933
     *    Create supplier invoice into database
1934
     *
1935
     * @param User $user user object that creates
1936
     *
1937
     * @return     int                   Id invoice created if OK, < 0 if KO
1938
     */
1939
    public function create($user)
1940
    {
1941
        global $langs, $conf, $hookmanager;
1942
1943
        $error = 0;
1944
        $now = dol_now();
1945
1946
        // Clean parameters
1947
        if (isset($this->ref_supplier)) {
1948
            $this->ref_supplier = trim($this->ref_supplier);
1949
        }
1950
        if (empty($this->type)) {
1951
            $this->type = self::TYPE_STANDARD;
1952
        }
1953
        if (empty($this->date)) {
1954
            $this->date = $now;
1955
        }
1956
1957
        // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
1958
        if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
1959
            [$this->fk_multicurrency, $this->multicurrency_tx] = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date);
0 ignored issues
show
Bug introduced by
The type DoliModules\Supplier\Model\MultiCurrency was not found. Did you mean MultiCurrency? If so, make sure to prefix the type with \.
Loading history...
1960
        } else {
1961
            $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
1962
        }
1963
        if (empty($this->fk_multicurrency)) {
1964
            $this->multicurrency_code = $conf->currency;
1965
            $this->fk_multicurrency = 0;
1966
            $this->multicurrency_tx = 1;
1967
        }
1968
1969
        $this->db->begin();
1970
1971
        // Create invoice from a template recurring invoice
1972
        if ($this->fac_rec > 0) {
1973
            $this->fk_fac_rec_source = $this->fac_rec;
1974
1975
            $_facrec = new FactureFournisseurRec($this->db);
1976
            $result = $_facrec->fetch($this->fac_rec);
1977
            $result = $_facrec->fetchObjectLinked(null, '', null, '', 'OR', 1, 'sourcetype', 0); // This load $_facrec->linkedObjectsIds
1978
1979
            // Define some dates
1980
            if (!empty($_facrec->frequency)) {
1981
                $originaldatewhen = $_facrec->date_when;
1982
                $nextdatewhen = dol_time_plus_duree($originaldatewhen, $_facrec->frequency, $_facrec->unit_frequency);
1983
                $previousdaynextdatewhen = dol_time_plus_duree($nextdatewhen, -1, 'd');
1984
                $this->socid = $_facrec->socid;
1985
            }
1986
1987
            $this->entity = $_facrec->entity; // Invoice created in same entity than template
1988
1989
            // Fields coming from GUI
1990
            // @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
1991
            // set by posted page with $object->xxx = ... and this section should be removed.
1992
            $this->fk_project = GETPOSTINT('projectid') > 0 ? (GETPOSTINT('projectid')) : $_facrec->fk_project;
1993
            $this->note_public = GETPOST('note_public', 'restricthtml') ? GETPOST('note_public', 'restricthtml') : $_facrec->note_public;
1994
            $this->note_private = GETPOST('note_private', 'restricthtml') ? GETPOST('note_private', 'restricthtml') : $_facrec->note_private;
1995
            $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...
1996
            $this->cond_reglement_id = GETPOSTINT('cond_reglement_id') > 0 ? (GETPOSTINT('cond_reglement_id')) : $_facrec->cond_reglement_id;
1997
            $this->mode_reglement_id = GETPOSTINT('mode_reglement_id') > 0 ? (GETPOSTINT('mode_reglement_id')) : $_facrec->mode_reglement_id;
1998
            $this->fk_account = GETPOST('fk_account') > 0 ? ((int) GETPOST('fk_account')) : $_facrec->fk_account;
1999
2000
            // Set here to have this defined for substitution into notes, should be recalculated after adding lines to get same result
2001
            $this->total_ht = $_facrec->total_ht;
2002
            $this->total_ttc = $_facrec->total_ttc;
2003
2004
            // Fields always coming from template
2005
            $this->fk_incoterms = $_facrec->fk_incoterms;
2006
            $this->location_incoterms = $_facrec->location_incoterms;
2007
2008
            // Clean parameters
2009
            if (!$this->type) {
2010
                $this->type = self::TYPE_STANDARD;
2011
            }
2012
            if (!empty(GETPOST('ref_supplier'))) {
2013
                $this->ref_supplier = trim($this->ref_supplier);
2014
            } else {
2015
                $this->ref_supplier = trim($this->ref_supplier . '_' . ($_facrec->nb_gen_done + 1));
2016
            }
2017
            $this->note_public = trim($this->note_public);
2018
            $this->note_private = trim($this->note_private);
2019
            $this->note_private = dol_concatdesc($this->note_private, $langs->trans("GeneratedFromRecurringInvoice", $_facrec->title));
2020
2021
            $this->array_options = $_facrec->array_options;
2022
2023
            if (!$this->mode_reglement_id) {
2024
                $this->mode_reglement_id = 0;
2025
            }
2026
            $this->status = self::STATUS_DRAFT;
2027
            $this->statut = self::STATUS_DRAFT; // deprecated
2028
2029
            $this->linked_objects = $_facrec->linkedObjectsIds;
2030
            // We do not add link to template invoice or next invoice will be linked to all generated invoices
2031
            //$this->linked_objects['facturerec'][0] = $this->fac_rec;
2032
2033
            $forceduedate = $this->calculate_date_lim_reglement();
2034
2035
            // For recurring invoices, update date and number of last generation of recurring template invoice, before inserting new invoice
2036
            if ($_facrec->frequency > 0) {
2037
                dol_syslog("This is a recurring invoice so we set date_last_gen and next date_when");
2038
                if (empty($_facrec->date_when)) {
2039
                    $_facrec->date_when = $now;
2040
                }
2041
                $next_date = $_facrec->getNextDate(); // Calculate next date
2042
                $result = $_facrec->setValueFrom('date_last_gen', $now, '', null, 'date', '', $user, '');
2043
                //$_facrec->setValueFrom('nb_gen_done', $_facrec->nb_gen_done + 1);     // Not required, +1 already included into setNextDate when second param is 1.
2044
                $result = $_facrec->setNextDate($next_date, 1);
2045
            }
2046
2047
            // Define lang of customer
2048
            $outputlangs = $langs;
2049
            $newlang = '';
2050
2051
            if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang) && isset($this->thirdparty->default_lang)) {
2052
                $newlang = $this->thirdparty->default_lang; // for proposal, order, invoice, ...
2053
            }
2054
            if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang) && isset($this->default_lang)) {
2055
                $newlang = $this->default_lang; // for thirdparty
2056
            }
2057
            if (!empty($newlang)) {
2058
                $outputlangs = new Translate("", $conf);
0 ignored issues
show
Bug introduced by
The type DoliModules\Supplier\Model\Translate was not found. Did you mean Translate? If so, make sure to prefix the type with \.
Loading history...
2059
                $outputlangs->setDefaultLang($newlang);
2060
            }
2061
2062
            // Array of possible substitutions (See also file mailing-send.php that should manage same substitutions)
2063
            $substitutionarray = getCommonSubstitutionArray($outputlangs, 0, null, $this);
2064
            $substitutionarray['__INVOICE_PREVIOUS_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%m');
2065
            $substitutionarray['__INVOICE_MONTH__'] = dol_print_date($this->date, '%m');
2066
            $substitutionarray['__INVOICE_NEXT_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%m');
2067
            $substitutionarray['__INVOICE_PREVIOUS_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%B');
2068
            $substitutionarray['__INVOICE_MONTH_TEXT__'] = dol_print_date($this->date, '%B');
2069
            $substitutionarray['__INVOICE_NEXT_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%B');
2070
            $substitutionarray['__INVOICE_PREVIOUS_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'y'), '%Y');
2071
            $substitutionarray['__INVOICE_YEAR__'] = dol_print_date($this->date, '%Y');
2072
            $substitutionarray['__INVOICE_NEXT_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'y'), '%Y');
2073
            // Only for template invoice
2074
            $substitutionarray['__INVOICE_DATE_NEXT_INVOICE_BEFORE_GEN__'] = dol_print_date($originaldatewhen, 'dayhour');
2075
            $substitutionarray['__INVOICE_DATE_NEXT_INVOICE_AFTER_GEN__'] = dol_print_date($nextdatewhen, 'dayhour');
2076
            $substitutionarray['__INVOICE_PREVIOUS_DATE_NEXT_INVOICE_AFTER_GEN__'] = dol_print_date($previousdaynextdatewhen, 'dayhour');
2077
            $substitutionarray['__INVOICE_COUNTER_CURRENT__'] = $_facrec->nb_gen_done;
2078
            $substitutionarray['__INVOICE_COUNTER_MAX__'] = $_facrec->nb_gen_max;
2079
2080
            complete_substitutions_array($substitutionarray, $outputlangs);
2081
2082
            $this->note_public = make_substitutions($this->note_public, $substitutionarray);
2083
            $this->note_private = make_substitutions($this->note_private, $substitutionarray);
2084
        }
2085
2086
        // Define due date if not already defined
2087
        if (!empty($forceduedate)) {
2088
            $this->date_echeance = $forceduedate;
2089
        }
2090
2091
        $sql = "INSERT INTO " . MAIN_DB_PREFIX . "facture_fourn (";
2092
        $sql .= "ref";
2093
        $sql .= ", ref_supplier";
2094
        $sql .= ", ref_ext";
2095
        $sql .= ", entity";
2096
        $sql .= ", type";
2097
        $sql .= ", subtype";
2098
        $sql .= ", libelle";
2099
        $sql .= ", fk_soc";
2100
        $sql .= ", datec";
2101
        $sql .= ", datef";
2102
        $sql .= ", vat_reverse_charge";
2103
        $sql .= ", fk_projet";
2104
        $sql .= ", fk_cond_reglement";
2105
        $sql .= ", fk_mode_reglement";
2106
        $sql .= ", fk_account";
2107
        $sql .= ", note_private";
2108
        $sql .= ", note_public";
2109
        $sql .= ", fk_user_author";
2110
        $sql .= ", date_lim_reglement";
2111
        $sql .= ", fk_incoterms, location_incoterms";
2112
        $sql .= ", fk_multicurrency";
2113
        $sql .= ", multicurrency_code";
2114
        $sql .= ", multicurrency_tx";
2115
        $sql .= ", fk_facture_source";
2116
        $sql .= ", fk_fac_rec_source";
2117
        $sql .= ")";
2118
        $sql .= " VALUES (";
2119
        $sql .= "'(PROV)'";
2120
        $sql .= ", '" . $this->db->escape($this->ref_supplier) . "'";
2121
        $sql .= ", '" . $this->db->escape($this->ref_ext) . "'";
2122
        $sql .= ", " . ((int) $conf->entity);
2123
        $sql .= ", '" . $this->db->escape($this->type) . "'";
2124
        $sql .= ", " . ($this->subtype ? "'" . $this->db->escape($this->subtype) . "'" : "null");
2125
        $sql .= ", '" . $this->db->escape(isset($this->label) ? $this->label : (isset($this->libelle) ? $this->libelle : '')) . "'";
2126
        $sql .= ", " . ((int) $this->socid);
2127
        $sql .= ", '" . $this->db->idate($now) . "'";
2128
        $sql .= ", '" . $this->db->idate($this->date) . "'";
2129
        $sql .= ", " . ($this->vat_reverse_charge != '' ? ((int) $this->db->escape($this->vat_reverse_charge)) : 0);
2130
        $sql .= ", " . ($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
2131
        $sql .= ", " . ($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : "null");
2132
        $sql .= ", " . ($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : "null");
2133
        $sql .= ", " . ($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
2134
        $sql .= ", '" . $this->db->escape($this->note_private) . "'";
2135
        $sql .= ", '" . $this->db->escape($this->note_public) . "'";
2136
        $sql .= ", " . ((int) $user->id) . ",";
2137
        $sql .= $this->date_echeance != '' ? "'" . $this->db->idate($this->date_echeance) . "'" : "null";
2138
        $sql .= ", " . (int) $this->fk_incoterms;
2139
        $sql .= ", '" . $this->db->escape($this->location_incoterms) . "'";
2140
        $sql .= ", " . (int) $this->fk_multicurrency;
2141
        $sql .= ", '" . $this->db->escape($this->multicurrency_code) . "'";
2142
        $sql .= ", " . (float) $this->multicurrency_tx;
2143
        $sql .= ", " . ($this->fk_facture_source ? ((int) $this->fk_facture_source) : "null");
2144
        $sql .= ", " . (isset($this->fk_fac_rec_source) ? $this->fk_fac_rec_source : "NULL");
2145
        $sql .= ")";
2146
2147
        dol_syslog(get_class($this) . "::create", LOG_DEBUG);
2148
        $resql = $this->db->query($sql);
2149
        if ($resql) {
2150
            $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX . 'facture_fourn');
2151
2152
            // Update ref with new one
2153
            $this->ref = '(PROV' . $this->id . ')';
2154
            $sql = 'UPDATE ' . MAIN_DB_PREFIX . "facture_fourn SET ref='" . $this->db->escape($this->ref) . "' WHERE rowid=" . ((int) $this->id);
2155
2156
            dol_syslog(get_class($this) . "::create", LOG_DEBUG);
2157
            $resql = $this->db->query($sql);
2158
            if (!$resql) {
2159
                $error++;
2160
            }
2161
2162
            if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) {  // To use new linkedObjectsIds instead of old linked_objects
2163
                $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
2164
            }
2165
2166
            // Add object linked
2167
            if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
2168
                foreach ($this->linked_objects as $origin => $tmp_origin_id) {
2169
                    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, ...))
2170
                        foreach ($tmp_origin_id as $origin_id) {
2171
                            $ret = $this->add_object_linked($origin, $origin_id);
2172
                            if (!$ret) {
2173
                                dol_print_error($this->db);
2174
                                $error++;
2175
                            }
2176
                        }
2177
                    } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
2178
                        $origin_id = $tmp_origin_id;
2179
                        $ret = $this->add_object_linked($origin, $origin_id);
2180
                        if (!$ret) {
2181
                            dol_print_error($this->db);
2182
                            $error++;
2183
                        }
2184
                    }
2185
                }
2186
            }
2187
2188
            if (!$error && empty($this->fac_rec) && count($this->lines) && is_object($this->lines[0])) {    // If this->lines is array of InvoiceLines (preferred mode)
2189
                dol_syslog("There is " . count($this->lines) . " lines that are invoice lines objects");
2190
                foreach ($this->lines as $i => $val) {
2191
                    $sql = 'INSERT INTO ' . MAIN_DB_PREFIX . 'facture_fourn_det (fk_facture_fourn, special_code, fk_remise_except)';
2192
                    $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') . ')';
2193
2194
                    $resql_insert = $this->db->query($sql);
2195
                    if ($resql_insert) {
2196
                        $idligne = $this->db->last_insert_id(MAIN_DB_PREFIX . 'facture_fourn_det');
2197
2198
                        $res = $this->updateline(
2199
                            $idligne,
2200
                            $this->lines[$i]->desc ? $this->lines[$i]->desc : $this->lines[$i]->description,
0 ignored issues
show
Deprecated Code introduced by
The property DoliModules\Supplier\Mod...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

2200
                            $this->lines[$i]->desc ? $this->lines[$i]->desc : /** @scrutinizer ignore-deprecated */ $this->lines[$i]->description,

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

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

Loading history...
2201
                            $this->lines[$i]->subprice,
2202
                            $this->lines[$i]->tva_tx . ($this->lines[$i]->vat_src_code ? ' (' . $this->lines[$i]->vat_src_code . ')' : ''),
2203
                            $this->lines[$i]->localtax1_tx,
2204
                            $this->lines[$i]->localtax2_tx,
2205
                            $this->lines[$i]->qty,
2206
                            $this->lines[$i]->fk_product,
2207
                            'HT',
2208
                            (!empty($this->lines[$i]->info_bits) ? $this->lines[$i]->info_bits : ''),
2209
                            $this->lines[$i]->product_type,
2210
                            $this->lines[$i]->remise_percent,
2211
                            false,
2212
                            $this->lines[$i]->date_start,
2213
                            $this->lines[$i]->date_end,
2214
                            $this->lines[$i]->array_options,
2215
                            $this->lines[$i]->fk_unit,
2216
                            $this->lines[$i]->multicurrency_subprice,
2217
                            $this->lines[$i]->ref_supplier
2218
                        );
2219
                    } else {
2220
                        $this->error = $this->db->lasterror();
2221
                        $this->db->rollback();
2222
                        return -5;
2223
                    }
2224
                }
2225
            } elseif (!$error && empty($this->fac_rec)) {   // If this->lines is an array of invoice line arrays
2226
                dol_syslog("There is " . count($this->lines) . " lines that are array lines");
2227
                foreach ($this->lines as $i => $val) {
2228
                    $line = $this->lines[$i];
2229
2230
                    // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
2231
                    //if (! is_object($line)) $line=json_decode(json_encode($line), false);  // convert recursively array into object.
2232
                    if (!is_object($line)) {
2233
                        $line = (object) $line;
2234
                    }
2235
2236
                    $sql = 'INSERT INTO ' . MAIN_DB_PREFIX . 'facture_fourn_det (fk_facture_fourn, special_code, fk_remise_except)';
2237
                    $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') . ')';
2238
2239
                    $resql_insert = $this->db->query($sql);
2240
                    if ($resql_insert) {
2241
                        $idligne = $this->db->last_insert_id(MAIN_DB_PREFIX . 'facture_fourn_det');
2242
2243
                        $this->updateline(
2244
                            $idligne,
2245
                            $line->desc ? $line->desc : $line->description,
2246
                            $line->pu_ht,
2247
                            $line->tva_tx,
2248
                            $line->localtax1_tx,
2249
                            $line->localtax2_tx,
2250
                            $line->qty,
2251
                            $line->fk_product,
2252
                            'HT',
2253
                            (!empty($line->info_bits) ? $line->info_bits : ''),
2254
                            $line->product_type,
2255
                            $line->remise_percent,
2256
                            0,
2257
                            $line->date_start,
2258
                            $line->date_end,
2259
                            $line->array_options,
2260
                            $line->fk_unit,
2261
                            $line->multicurrency_subprice,
2262
                            $line->ref_supplier
2263
                        );
2264
                    } else {
2265
                        $this->error = $this->db->lasterror();
2266
                        $this->db->rollback();
2267
                        return -5;
2268
                    }
2269
                }
2270
            }
2271
2272
            /*
2273
             * Insert lines of template invoices
2274
             */
2275
            if (!$error && $this->fac_rec > 0) {
2276
                foreach ($_facrec->lines as $i => $val) {
2277
                    if ($_facrec->lines[$i]->fk_product) {
2278
                        $prod = new Product($this->db);
2279
                        $res = $prod->fetch($_facrec->lines[$i]->fk_product);
2280
                    }
2281
2282
                    // For line from template invoice, we use data from template invoice
2283
                    /*
2284
                    $tva_tx = get_default_tva($mysoc,$soc,$prod->id);
2285
                    $tva_npr = get_default_npr($mysoc,$soc,$prod->id);
2286
                    if (empty($tva_tx)) $tva_npr=0;
2287
                    $localtax1_tx=get_localtax($tva_tx,1,$soc,$mysoc,$tva_npr);
2288
                    $localtax2_tx=get_localtax($tva_tx,2,$soc,$mysoc,$tva_npr);
2289
                    */
2290
                    $tva_tx = $_facrec->lines[$i]->tva_tx . ($_facrec->lines[$i]->vat_src_code ? '(' . $_facrec->lines[$i]->vat_src_code . ')' : '');
2291
                    $tva_npr = $_facrec->lines[$i]->info_bits;
2292
                    if (empty($tva_tx)) {
2293
                        $tva_npr = 0;
2294
                    }
2295
                    $localtax1_tx = $_facrec->lines[$i]->localtax1_tx;
2296
                    $localtax2_tx = $_facrec->lines[$i]->localtax2_tx;
2297
2298
                    $fk_product_fournisseur_price = empty($_facrec->lines[$i]->fk_product_fournisseur_price) ? null : $_facrec->lines[$i]->fk_product_fournisseur_price;
2299
                    $buyprice = empty($_facrec->lines[$i]->buyprice) ? 0 : $_facrec->lines[$i]->buyprice;
2300
2301
                    // If buyprice not defined from template invoice, we try to guess the best value
2302
                    if (!$buyprice && $_facrec->lines[$i]->fk_product > 0) {
2303
                        $producttmp = new ProductFournisseur($this->db);
2304
                        $producttmp->fetch($_facrec->lines[$i]->fk_product);
2305
2306
                        // If margin module defined on costprice, we try the costprice
2307
                        // If not defined or if module margin defined and pmp and stock module enabled, we try pmp price
2308
                        // else we get the best supplier price
2309
                        if (getDolGlobalString('MARGIN_TYPE') == 'costprice' && !empty($producttmp->cost_price)) {
2310
                            $buyprice = $producttmp->cost_price;
2311
                        } elseif (isModEnabled('stock') && (getDolGlobalString('MARGIN_TYPE') == 'costprice' || getDolGlobalString('MARGIN_TYPE') == 'pmp') && !empty($producttmp->pmp)) {
2312
                            $buyprice = $producttmp->pmp;
2313
                        } else {
2314
                            if ($producttmp->find_min_price_product_fournisseur($_facrec->lines[$i]->fk_product) > 0) {
2315
                                if ($producttmp->product_fourn_price_id > 0) {
2316
                                    $buyprice = price2num($producttmp->fourn_unitprice * (1 - $producttmp->fourn_remise_percent / 100) + $producttmp->fourn_remise, 'MU');
2317
                                }
2318
                            }
2319
                        }
2320
                    }
2321
2322
                    $result_insert = $this->addline(
2323
                        $_facrec->lines[$i]->desc ? $_facrec->lines[$i]->desc : $_facrec->lines[$i]->description,
2324
                        $_facrec->lines[$i]->pu_ht,
2325
                        $tva_tx,
2326
                        $localtax1_tx,
2327
                        $localtax2_tx,
2328
                        $_facrec->lines[$i]->qty,
2329
                        $_facrec->lines[$i]->fk_product,
2330
                        $_facrec->lines[$i]->remise_percent,
2331
                        ($_facrec->lines[$i]->date_start == 1 && $this->date) ? $this->date : '',
2332
                        ($_facrec->lines[$i]->date_end == 1 && $previousdaynextdatewhen) ? $previousdaynextdatewhen : '',
2333
                        0,
2334
                        $_facrec->lines[$i]->info_bits,
2335
                        'HT',
2336
                        0,
2337
                        $_facrec->lines[$i]->rang,
2338
                        false,
2339
                        $_facrec->lines[$i]->array_options,
2340
                        $_facrec->lines[$i]->fk_unit,
2341
                        0,
2342
                        0,
2343
                        $_facrec->lines[$i]->ref_supplier,
2344
                        $_facrec->lines[$i]->special_code,
2345
                        0,
2346
                        0
2347
                    );
2348
                    if ($result_insert < 0) {
2349
                        $error++;
2350
                        $this->error = $this->db->error();
2351
                        break;
2352
                    }
2353
                }
2354
            }
2355
2356
2357
            // Update total price
2358
            $result = $this->update_price(1);
2359
            if ($result > 0) {
2360
                // Actions on extra fields
2361
                if (!$error) {
2362
                    $result = $this->insertExtraFields(); // This also set $this->error or $this->errors if errors are found
2363
                    if ($result < 0) {
2364
                        $error++;
2365
                    }
2366
                }
2367
2368
                if (!$error) {
2369
                    // Call trigger
2370
                    $result = $this->call_trigger('BILL_SUPPLIER_CREATE', $user);
2371
                    if ($result < 0) {
2372
                        $error++;
2373
                    }
2374
                    // End call triggers
2375
                }
2376
2377
                if (!$error) {
2378
                    $this->db->commit();
2379
                    return $this->id;
2380
                } else {
2381
                    $this->db->rollback();
2382
                    return -4;
2383
                }
2384
            } else {
2385
                $this->error = $langs->trans('FailedToUpdatePrice');
2386
                $this->db->rollback();
2387
                return -3;
2388
            }
2389
        } else {
2390
            if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
2391
                $this->error = $langs->trans('ErrorRefAlreadyExists');
2392
                $this->db->rollback();
2393
                return -1;
2394
            } else {
2395
                $this->error = $this->db->lasterror();
2396
                $this->db->rollback();
2397
                return -2;
2398
            }
2399
        }
2400
    }
2401
2402
    /**
2403
     * Update a line detail into database
2404
     *
2405
     * @param int        $id              Id of line invoice
2406
     * @param string     $desc            Description of line
2407
     * @param double     $pu              Prix unitaire (HT ou TTC selon price_base_type)
2408
     * @param double     $vatrate         VAT Rate (Can be '8.5', '8.5 (ABC)')
2409
     * @param double     $txlocaltax1     LocalTax1 Rate
2410
     * @param double     $txlocaltax2     LocalTax2 Rate
2411
     * @param double     $qty             Quantity
2412
     * @param int        $idproduct       Id produit
2413
     * @param string     $price_base_type HT or TTC
2414
     * @param int        $info_bits       Miscellaneous information of line
2415
     * @param int        $type            Type of line (0=product, 1=service)
2416
     * @param double     $remise_percent  Percentage discount of the line
2417
     * @param int        $notrigger       Disable triggers
2418
     * @param int|string $date_start      Date start of service
2419
     * @param int|string $date_end        Date end of service
2420
     * @param array      $array_options   extrafields array
2421
     * @param int|null   $fk_unit         Code of the unit to use. Null to use the default one
2422
     * @param double     $pu_devise       Amount in currency
2423
     * @param string     $ref_supplier    Supplier ref
2424
     * @param integer    $rang            line rank
2425
     *
2426
     * @return      int                             Return integer <0 if KO, >0 if OK
2427
     */
2428
    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)
2429
    {
2430
        global $mysoc, $langs;
2431
2432
        dol_syslog(get_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);
2433
        include_once BASE_PATH . '/../Dolibarr/Lib/Price.php';
2434
2435
        $pu = price2num($pu);
2436
        $qty = price2num($qty);
2437
        $remise_percent = (float) price2num($remise_percent);
2438
        $pu_devise = price2num($pu_devise);
2439
2440
        // Check parameters
2441
        //if (! is_numeric($pu) || ! is_numeric($qty)) return -1;
2442
        if ($type < 0) {
2443
            return -1;
2444
        }
2445
2446
        if ($date_start && $date_end && $date_start > $date_end) {
2447
            $langs->load("errors");
2448
            $this->error = $langs->trans('ErrorStartDateGreaterEnd');
2449
            return -1;
2450
        }
2451
2452
        // Clean parameters
2453
        if (empty($vatrate)) {
2454
            $vatrate = 0;
2455
        }
2456
        if (empty($txlocaltax1)) {
2457
            $txlocaltax1 = 0;
2458
        }
2459
        if (empty($txlocaltax2)) {
2460
            $txlocaltax2 = 0;
2461
        }
2462
2463
        $txlocaltax1 = (float) price2num($txlocaltax1);
2464
        $txlocaltax2 = (float) price2num($txlocaltax2);
2465
2466
        // Calcul du total TTC et de la TVA pour la ligne a partir de
2467
        // qty, pu, remise_percent et txtva
2468
        // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2469
        // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2470
2471
        $localtaxes_type = getLocalTaxesFromRate($vatrate, 0, $mysoc, $this->thirdparty);
2472
2473
        $reg = [];
2474
2475
        // Clean vat code
2476
        $vat_src_code = '';
2477
        if (preg_match('/\((.*)\)/', $vatrate, $reg)) {
2478
            $vat_src_code = $reg[1];
2479
            $vatrate = preg_replace('/\s*\(.*\)/', '', $vatrate); // Remove code into vatrate.
2480
        }
2481
2482
        $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);
2483
        $total_ht = $tabprice[0];
2484
        $total_tva = $tabprice[1];
2485
        $total_ttc = $tabprice[2];
2486
        $pu_ht = $tabprice[3];
2487
        $pu_tva = $tabprice[4];
2488
        $pu_ttc = $tabprice[5];
2489
        $total_localtax1 = $tabprice[9];
2490
        $total_localtax2 = $tabprice[10];
2491
2492
        // MultiCurrency
2493
        $multicurrency_total_ht = $tabprice[16];
2494
        $multicurrency_total_tva = $tabprice[17];
2495
        $multicurrency_total_ttc = $tabprice[18];
2496
        $pu_ht_devise = $tabprice[19];
2497
2498
        if (empty($info_bits)) {
2499
            $info_bits = 0;
2500
        }
2501
2502
        //Fetch current line from the database and then clone the object and set it in $oldline property
2503
        $line = new SupplierInvoiceLine($this->db);
2504
        $line->fetch($id);
2505
        $line->fetch_optionals();
2506
2507
        $staticline = clone $line;
2508
2509
        if ($idproduct) {
2510
            $product = new Product($this->db);
2511
            $result = $product->fetch($idproduct);
2512
            $product_type = $product->type;
2513
        } else {
2514
            $idproduct = $staticline->fk_product;
2515
            $product_type = $type;
2516
        }
2517
2518
        $line->oldline = $staticline;
2519
        $line->context = $this->context;
2520
2521
        $line->description = $desc;
0 ignored issues
show
Deprecated Code introduced by
The property DoliModules\Supplier\Mod...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

2521
        /** @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...
2522
        $line->desc = $desc;
2523
2524
        $line->qty = ($this->type == self::TYPE_CREDIT_NOTE ? abs($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...NOTE ? abs($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...
2525
        $line->subprice = ($this->type == self::TYPE_CREDIT_NOTE ? -abs($pu_ht) : $pu_ht); // For credit note, unit price always negative, always positive otherwise
2526
        $line->pu_ht = ($this->type == self::TYPE_CREDIT_NOTE ? -abs($pu_ht) : $pu_ht); // For credit note, unit price always negative, always positive otherwise
0 ignored issues
show
Deprecated Code introduced by
The property DoliModules\Supplier\Mod...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

2526
        /** @scrutinizer ignore-deprecated */ $line->pu_ht = ($this->type == self::TYPE_CREDIT_NOTE ? -abs($pu_ht) : $pu_ht); // For credit note, unit price always negative, always positive otherwise

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...
2527
        $line->pu_ttc = ($this->type == self::TYPE_CREDIT_NOTE ? -abs($pu_ttc) : $pu_ttc); // For credit note, unit price always negative, always positive otherwise
2528
2529
        $line->remise_percent = $remise_percent;
2530
        $line->ref_supplier = $ref_supplier;
2531
2532
        $line->date_start = $date_start;
2533
        $line->date_end = $date_end;
2534
2535
        $line->vat_src_code = $vat_src_code;
2536
        $line->tva_tx = $vatrate;
2537
        $line->localtax1_tx = $txlocaltax1;
2538
        $line->localtax2_tx = $txlocaltax2;
2539
        $line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
0 ignored issues
show
Documentation Bug introduced by
It seems like empty($localtaxes_type[0...' : $localtaxes_type[0] can also be of type string. However, the property $localtax1_type 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...
2540
        $line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
0 ignored issues
show
Documentation Bug introduced by
It seems like empty($localtaxes_type[2...' : $localtaxes_type[2] can also be of type string. However, the property $localtax2_type 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...
2541
2542
        $line->total_ht = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($total_ht) : $total_ht);
2543
        $line->total_tva = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($total_tva) : $total_tva);
2544
        $line->total_localtax1 = $total_localtax1;
2545
        $line->total_localtax2 = $total_localtax2;
2546
        $line->total_ttc = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs($total_ttc) : $total_ttc);
2547
2548
        $line->fk_product = $idproduct;
2549
        $line->product_type = $product_type;
2550
        $line->info_bits = $info_bits;
2551
        $line->fk_unit = $fk_unit;
2552
        $line->rang = $rang;
2553
2554
        if (is_array($array_options) && count($array_options) > 0) {
2555
            // We replace values in this->line->array_options only for entries defined into $array_options
2556
            foreach ($array_options as $key => $value) {
2557
                $line->array_options[$key] = $array_options[$key];
2558
            }
2559
        }
2560
2561
        // Multicurrency
2562
        $line->multicurrency_subprice = $pu_ht_devise;
2563
        $line->multicurrency_total_ht = $multicurrency_total_ht;
2564
        $line->multicurrency_total_tva = $multicurrency_total_tva;
2565
        $line->multicurrency_total_ttc = $multicurrency_total_ttc;
2566
2567
        $res = $line->update($notrigger);
2568
2569
        if ($res < 1) {
2570
            $this->errors[] = $line->error;
2571
        } else {
2572
            // Update total price into invoice record
2573
            $res = $this->update_price('1', 'auto', 0, $this->thirdparty);
2574
        }
2575
2576
        return $res;
2577
    }
2578
2579
    /**
2580
     *  Update database
2581
     *
2582
     * @param User $user      User that modify
2583
     * @param int  $notrigger 0=launch triggers after, 1=disable triggers
2584
     *
2585
     * @return int                      Return integer <0 if KO, >0 if OK
2586
     */
2587
    public function update($user = null, $notrigger = 0)
2588
    {
2589
        global $langs;
2590
        $error = 0;
2591
2592
        // Clean parameters
2593
        if (empty($this->type)) {
2594
            $this->type = self::TYPE_STANDARD;
2595
        }
2596
        if (isset($this->ref)) {
2597
            $this->ref = trim($this->ref);
2598
        }
2599
        if (isset($this->ref_supplier)) {
2600
            $this->ref_supplier = trim($this->ref_supplier);
2601
        }
2602
        if (isset($this->ref_ext)) {
2603
            $this->ref_ext = trim($this->ref_ext);
2604
        }
2605
        if (isset($this->entity)) {
2606
            $this->entity = (int) $this->entity;
2607
        }
2608
        if (isset($this->type)) {
2609
            $this->type = (int) $this->type;
2610
        }
2611
        if (isset($this->subtype)) {
2612
            $this->subtype = (int) $this->subtype;
2613
        }
2614
        if (isset($this->socid)) {
2615
            $this->socid = (int) $this->socid;
2616
        }
2617
        if (isset($this->label)) {
2618
            $this->label = trim($this->label);
2619
        }
2620
        if (isset($this->paye)) {
2621
            $this->paye = (int) $this->paye;
2622
        }
2623
        if (isset($this->close_code)) {
2624
            $this->close_code = trim($this->close_code);
2625
        }
2626
        if (isset($this->close_note)) {
2627
            $this->close_note = trim($this->close_note);
2628
        }
2629
        if (isset($this->localtax1)) {
2630
            $this->localtax1 = trim($this->localtax1);
2631
        }
2632
        if (isset($this->localtax2)) {
2633
            $this->localtax2 = trim($this->localtax2);
2634
        }
2635
        if (empty($this->total_ht)) {
2636
            $this->total_ht = 0;
2637
        }
2638
        if (empty($this->total_tva)) {
2639
            $this->total_tva = 0;
2640
        }
2641
        //  if (isset($this->total_localtax1)) $this->total_localtax1=trim($this->total_localtax1);
2642
        //  if (isset($this->total_localtax2)) $this->total_localtax2=trim($this->total_localtax2);
2643
        if (isset($this->total_ttc)) {
2644
            $this->total_ttc = (float) $this->total_ttc;
2645
        }
2646
        if (isset($this->statut)) {
2647
            $this->statut = (int) $this->statut;
2648
        }
2649
        if (isset($this->status)) {
2650
            $this->status = (int) $this->status;
2651
        }
2652
        if (isset($this->author)) {
2653
            $this->author = trim($this->author);
2654
        }
2655
        if (isset($this->fk_user_valid)) {
2656
            $this->fk_user_valid = trim($this->fk_user_valid);
2657
        }
2658
        if (isset($this->fk_facture_source)) {
2659
            $this->fk_facture_source = (int) $this->fk_facture_source;
2660
        }
2661
        if (isset($this->fk_project)) {
2662
            if (empty($this->fk_project)) {
2663
                $this->fk_project = 0;
2664
            } else {
2665
                $this->fk_project = (int) $this->fk_project;
2666
            }
2667
        }
2668
        if (isset($this->cond_reglement_id)) {
2669
            $this->cond_reglement_id = (int) $this->cond_reglement_id;
2670
        }
2671
        if (isset($this->note_private)) {
2672
            $this->note = trim($this->note_private);
2673
        }
2674
        if (isset($this->note_public)) {
2675
            $this->note_public = trim($this->note_public);
2676
        }
2677
        if (isset($this->model_pdf)) {
2678
            $this->model_pdf = trim($this->model_pdf);
2679
        }
2680
        if (isset($this->import_key)) {
2681
            $this->import_key = trim($this->import_key);
2682
        }
2683
2684
2685
        // Check parameters
2686
        // Put here code to add control on parameters values
2687
2688
        // Update request
2689
        $sql = "UPDATE " . MAIN_DB_PREFIX . "facture_fourn SET";
2690
        $sql .= " ref=" . (isset($this->ref) ? "'" . $this->db->escape($this->ref) . "'" : "null") . ",";
2691
        $sql .= " ref_supplier=" . (isset($this->ref_supplier) ? "'" . $this->db->escape($this->ref_supplier) . "'" : "null") . ",";
2692
        $sql .= " ref_ext=" . (isset($this->ref_ext) ? "'" . $this->db->escape($this->ref_ext) . "'" : "null") . ",";
2693
        $sql .= " entity=" . (isset($this->entity) ? ((int) $this->entity) : "null") . ",";
2694
        $sql .= " type=" . (isset($this->type) ? ((int) $this->type) : "null") . ",";
2695
        $sql .= " subtype=" . (isset($this->subtype) ? $this->db->escape($this->subtype) : "null") . ",";
2696
        $sql .= " fk_soc=" . (isset($this->socid) ? ((int) $this->socid) : "null") . ",";
2697
        $sql .= " datec=" . (dol_strlen($this->datec) != 0 ? "'" . $this->db->idate($this->datec) . "'" : 'null') . ",";
2698
        $sql .= " datef=" . (dol_strlen($this->date) != 0 ? "'" . $this->db->idate($this->date) . "'" : 'null') . ",";
2699
        if (dol_strlen($this->tms) != 0) {
2700
            $sql .= " tms=" . (dol_strlen($this->tms) != 0 ? "'" . $this->db->idate($this->tms) . "'" : 'null') . ",";
2701
        }
2702
        $sql .= " libelle=" . (isset($this->label) ? "'" . $this->db->escape($this->label) . "'" : "null") . ",";
2703
        $sql .= " paye=" . (isset($this->paye) ? ((int) $this->paye) : "0") . ",";
2704
        $sql .= " close_code=" . (isset($this->close_code) ? "'" . $this->db->escape($this->close_code) . "'" : "null") . ",";
2705
        $sql .= " close_note=" . (isset($this->close_note) ? "'" . $this->db->escape($this->close_note) . "'" : "null") . ",";
2706
        $sql .= " localtax1=" . (isset($this->localtax1) ? ((float) $this->localtax1) : "null") . ",";
2707
        $sql .= " localtax2=" . (isset($this->localtax2) ? ((float) $this->localtax2) : "null") . ",";
2708
        $sql .= " total_ht=" . (isset($this->total_ht) ? ((float) $this->total_ht) : "null") . ",";
2709
        $sql .= " total_tva=" . (isset($this->total_tva) ? ((float) $this->total_tva) : "null") . ",";
2710
        $sql .= " total_ttc=" . (isset($this->total_ttc) ? ((float) $this->total_ttc) : "null") . ",";
2711
        $sql .= " fk_statut=" . (isset($this->status) ? ((int) $this->status) : (isset($this->statut) ? ((int) $this->statut) : "null")) . ",";
2712
        $sql .= " fk_user_author=" . (isset($this->author) ? ((int) $this->author) : "null") . ",";
2713
        $sql .= " fk_user_valid=" . (isset($this->fk_user_valid) ? ((int) $this->fk_user_valid) : "null") . ",";
2714
        $sql .= " fk_facture_source=" . ($this->fk_facture_source ? ((int) $this->fk_facture_source) : "null") . ",";
2715
        $sql .= " vat_reverse_charge = " . ($this->vat_reverse_charge != '' ? ((int) $this->db->escape($this->vat_reverse_charge)) : 0) . ",";
2716
        $sql .= " fk_projet=" . (!empty($this->fk_project) ? ((int) $this->fk_project) : "null") . ",";
2717
        $sql .= " fk_cond_reglement=" . (isset($this->cond_reglement_id) ? ((int) $this->cond_reglement_id) : "null") . ",";
2718
        $sql .= " date_lim_reglement=" . (dol_strlen($this->date_echeance) != 0 ? "'" . $this->db->idate($this->date_echeance) . "'" : 'null') . ",";
2719
        $sql .= " note_private=" . (isset($this->note_private) ? "'" . $this->db->escape($this->note_private) . "'" : "null") . ",";
2720
        $sql .= " note_public=" . (isset($this->note_public) ? "'" . $this->db->escape($this->note_public) . "'" : "null") . ",";
2721
        $sql .= " model_pdf=" . (isset($this->model_pdf) ? "'" . $this->db->escape($this->model_pdf) . "'" : "null") . ",";
2722
        $sql .= " import_key=" . (isset($this->import_key) ? "'" . $this->db->escape($this->import_key) . "'" : "null");
2723
        $sql .= " WHERE rowid=" . ((int) $this->id);
2724
2725
        $this->db->begin();
2726
2727
        dol_syslog(get_class($this) . "::update", LOG_DEBUG);
2728
        $resql = $this->db->query($sql);
2729
2730
        if (!$resql) {
2731
            $error++;
2732
2733
            if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
2734
                $this->errors[] = $langs->trans('ErrorRefAlreadyExists');
2735
            } else {
2736
                $this->errors[] = "Error " . $this->db->lasterror();
2737
            }
2738
        }
2739
2740
        if (!$error) {
2741
            $result = $this->insertExtraFields();
2742
            if ($result < 0) {
2743
                $error++;
2744
            }
2745
        }
2746
2747
        if (!$error) {
2748
            if (!$notrigger) {
2749
                // Call trigger
2750
                $result = $this->call_trigger('BILL_SUPPLIER_MODIFY', $user);
2751
                if ($result < 0) {
2752
                    $error++;
2753
                }
2754
                // End call triggers
2755
            }
2756
        }
2757
2758
        // Commit or rollback
2759
        if ($error) {
2760
            foreach ($this->errors as $errmsg) {
2761
                dol_syslog(get_class($this) . "::update " . $errmsg, LOG_ERR);
2762
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2763
            }
2764
            $this->db->rollback();
2765
            return -1 * $error;
2766
        } else {
2767
            $this->db->commit();
2768
            return 1;
2769
        }
2770
    }
2771
2772
    /**
2773
     *  Adds an invoice line (associated with no predefined product/service)
2774
     *  The parameters are already supposed to be correct and with final values when calling
2775
     *  this method. Also, for the VAT rate, it must already have been defined by the caller by
2776
     *  by the get_default_tva method(vendor_company, buying company, idprod) and the desc must
2777
     *  already have the right value (the caller has to manage the multilanguage).
2778
     *
2779
     * @param string   $desc                Description of the line
2780
     * @param double   $pu                  Unit price (HT or TTC according to price_base_type, > 0 even for credit
2781
     *                                      note)
2782
     * @param double   $txtva               Force Vat rate to use, -1 for auto.
2783
     * @param double   $txlocaltax1         LocalTax1 Rate
2784
     * @param double   $txlocaltax2         LocalTax2 Rate
2785
     * @param double   $qty                 Quantity
2786
     * @param int      $fk_product          Product/Service ID predefined
2787
     * @param double   $remise_percent      Percentage discount of the line
2788
     * @param int      $date_start          Service start date
2789
     * @param int      $date_end            Service expiry date
2790
     * @param int      $fk_code_ventilation Accounting breakdown code
2791
     * @param int      $info_bits           Line type bits
2792
     * @param string   $price_base_type     HT or TTC
2793
     * @param int      $type                Type of line (0=product, 1=service)
2794
     * @param int      $rang                Position of line
2795
     * @param int      $notrigger           Disable triggers
2796
     * @param array    $array_options       extrafields array
2797
     * @param int|null $fk_unit             Code of the unit to use. Null to use the default one
2798
     * @param int      $origin_id           id origin document
2799
     * @param double   $pu_devise           Amount in currency
2800
     * @param string   $ref_supplier        Supplier ref
2801
     * @param string   $special_code        Special code
2802
     * @param int      $fk_parent_line      Parent line id
2803
     * @param int      $fk_remise_except    Id discount used
2804
     *
2805
     * @return     int                                 >0 if OK, <0 if KO
2806
     */
2807
    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 = '', $fk_parent_line = 0, $fk_remise_except = 0)
2808
    {
2809
        global $langs, $mysoc, $conf;
2810
2811
        dol_syslog(get_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);
2812
        include_once BASE_PATH . '/../Dolibarr/Lib/Price.php';
2813
2814
        if ($this->statut == self::STATUS_DRAFT) {
2815
            // Clean parameters
2816
            if (empty($remise_percent)) {
2817
                $remise_percent = 0;
2818
            }
2819
            if (empty($qty)) {
2820
                $qty = 0;
2821
            }
2822
            if (empty($info_bits)) {
2823
                $info_bits = 0;
2824
            }
2825
            if (empty($rang)) {
2826
                $rang = 0;
2827
            }
2828
            if (empty($fk_code_ventilation)) {
2829
                $fk_code_ventilation = 0;
2830
            }
2831
            if (empty($txtva)) {
2832
                $txtva = 0;
2833
            }
2834
            if (empty($txlocaltax1)) {
2835
                $txlocaltax1 = 0;
2836
            }
2837
            if (empty($txlocaltax2)) {
2838
                $txlocaltax2 = 0;
2839
            }
2840
2841
            $remise_percent = price2num($remise_percent);
2842
            $qty = price2num($qty);
2843
            $pu = price2num($pu);
2844
            if (!preg_match('/\((.*)\)/', $txtva)) {
2845
                $txtva = price2num($txtva); // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
2846
            }
2847
            $txlocaltax1 = price2num($txlocaltax1);
2848
            $txlocaltax2 = price2num($txlocaltax2);
2849
2850
            if ($date_start && $date_end && $date_start > $date_end) {
2851
                $langs->load("errors");
2852
                $this->error = $langs->trans('ErrorStartDateGreaterEnd');
2853
                return -1;
2854
            }
2855
2856
            $this->db->begin();
2857
2858
            if ($fk_product > 0) {
2859
                if (getDolGlobalString('SUPPLIER_INVOICE_WITH_PREDEFINED_PRICES_ONLY')) {
2860
                    // Check quantity is enough
2861
                    dol_syslog(get_class($this) . "::addline we check supplier prices fk_product=" . $fk_product . " qty=" . $qty . " ref_supplier=" . $ref_supplier);
2862
                    $prod = new ProductFournisseur($this->db);
2863
                    if ($prod->fetch($fk_product) > 0) {
2864
                        $product_type = $prod->type;
2865
                        $label = $prod->label;
2866
                        $fk_prod_fourn_price = 0;
2867
2868
                        // We use 'none' instead of $ref_supplier, because $ref_supplier may not exists anymore. So we will take the first supplier price ok.
2869
                        // If we want a dedicated supplier price, we must provide $fk_prod_fourn_price.
2870
                        $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
2871
                        if ($result > 0) {
2872
                            if (empty($pu)) {
2873
                                $pu = $prod->fourn_pu; // Unit price supplier price set by get_buyprice
2874
                            }
2875
                            $ref_supplier = $prod->ref_supplier; // Ref supplier price set by get_buyprice
2876
                            // is remise percent not keyed but present for the product we add it
2877
                            if ($remise_percent == 0 && $prod->remise_percent != 0) {
2878
                                $remise_percent = $prod->remise_percent;
2879
                            }
2880
                        }
2881
                        if ($result == 0) {                   // If result == 0, we failed to found the supplier reference price
2882
                            $langs->load("errors");
2883
                            $this->error = "Ref " . $prod->ref . " " . $langs->trans("ErrorQtyTooLowForThisSupplier");
2884
                            $this->db->rollback();
2885
                            dol_syslog(get_class($this) . "::addline we did not found supplier price, so we can't guess unit price");
2886
                            //$pu    = $prod->fourn_pu;     // We do not overwrite unit price
2887
                            //$ref   = $prod->ref_fourn;    // We do not overwrite ref supplier price
2888
                            return -1;
2889
                        }
2890
                        if ($result == -1) {
2891
                            $langs->load("errors");
2892
                            $this->error = "Ref " . $prod->ref . " " . $langs->trans("ErrorQtyTooLowForThisSupplier");
2893
                            $this->db->rollback();
2894
                            dol_syslog(get_class($this) . "::addline result=" . $result . " - " . $this->error, LOG_DEBUG);
2895
                            return -1;
2896
                        }
2897
                        if ($result < -1) {
2898
                            $this->error = $prod->error;
2899
                            $this->db->rollback();
2900
                            dol_syslog(get_class($this) . "::addline result=" . $result . " - " . $this->error, LOG_ERR);
2901
                            return -1;
2902
                        }
2903
                    } else {
2904
                        $this->error = $prod->error;
2905
                        $this->db->rollback();
2906
                        return -1;
2907
                    }
2908
                }
2909
            } else {
2910
                $product_type = $type;
2911
            }
2912
2913
            if (isModEnabled("multicurrency") && $pu_devise > 0) {
2914
                $pu = 0;
2915
            }
2916
2917
            $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
2918
2919
            // Clean vat code
2920
            $reg = [];
2921
            $vat_src_code = '';
2922
            if (preg_match('/\((.*)\)/', $txtva, $reg)) {
2923
                $vat_src_code = $reg[1];
2924
                $txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
2925
            }
2926
2927
            // Calcul du total TTC et de la TVA pour la ligne a partir de
2928
            // qty, pu, remise_percent et txtva
2929
            // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2930
            // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2931
2932
            $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);
2933
            $total_ht = $tabprice[0];
2934
            $total_tva = $tabprice[1];
2935
            $total_ttc = $tabprice[2];
2936
            $total_localtax1 = $tabprice[9];
2937
            $total_localtax2 = $tabprice[10];
2938
            $pu_ht = $tabprice[3];
2939
2940
            // MultiCurrency
2941
            $multicurrency_total_ht = $tabprice[16];
2942
            $multicurrency_total_tva = $tabprice[17];
2943
            $multicurrency_total_ttc = $tabprice[18];
2944
            $pu_ht_devise = $tabprice[19];
2945
2946
            // Check parameters
2947
            if ($type < 0) {
2948
                return -1;
2949
            }
2950
2951
            if ($rang < 0) {
2952
                $rangmax = $this->line_max();
2953
                $rang = $rangmax + 1;
2954
            }
2955
2956
            // Insert line
2957
            $supplierinvoiceline = new SupplierInvoiceLine($this->db);
2958
2959
            $supplierinvoiceline->context = $this->context;
2960
2961
            $supplierinvoiceline->fk_facture_fourn = $this->id;
2962
            //$supplierinvoiceline->label=$label;   // deprecated
2963
            $supplierinvoiceline->desc = $desc;
2964
            $supplierinvoiceline->ref_supplier = $ref_supplier;
2965
2966
            $supplierinvoiceline->qty = ($this->type == self::TYPE_CREDIT_NOTE ? abs($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...NOTE ? abs($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...
2967
            $supplierinvoiceline->subprice = ($this->type == self::TYPE_CREDIT_NOTE ? -abs($pu_ht) : $pu_ht); // For credit note, unit price always negative, always positive otherwise
2968
2969
            $supplierinvoiceline->vat_src_code = $vat_src_code;
2970
            $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...
2971
            $supplierinvoiceline->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
2972
            $supplierinvoiceline->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
2973
            $supplierinvoiceline->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
0 ignored issues
show
Documentation Bug introduced by
It seems like empty($localtaxes_type[0...' : $localtaxes_type[0] can also be of type string. However, the property $localtax1_type 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...
2974
            $supplierinvoiceline->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
0 ignored issues
show
Documentation Bug introduced by
It seems like empty($localtaxes_type[2...' : $localtaxes_type[2] can also be of type string. However, the property $localtax2_type 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...
2975
2976
            $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
2977
            $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
2978
            $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
2979
            $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
2980
            $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
2981
2982
            $supplierinvoiceline->fk_product = $fk_product;
2983
            $supplierinvoiceline->product_type = $type;
2984
            $supplierinvoiceline->remise_percent = $remise_percent;
2985
            $supplierinvoiceline->date_start = $date_start;
2986
            $supplierinvoiceline->date_end = $date_end;
2987
            $supplierinvoiceline->fk_code_ventilation = $fk_code_ventilation;
2988
            $supplierinvoiceline->rang = $rang;
2989
            $supplierinvoiceline->info_bits = $info_bits;
2990
            $supplierinvoiceline->fk_remise_except = $fk_remise_except;
2991
2992
2993
            $supplierinvoiceline->special_code = (string) $special_code;
2994
            $supplierinvoiceline->fk_parent_line = $fk_parent_line;
2995
            $supplierinvoiceline->origin = $this->origin;
0 ignored issues
show
Deprecated Code introduced by
The property DoliCore\Base\GenericDocument::$origin has been deprecated: Use now $origin_type and $origin_id; ( Ignorable by Annotation )

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

2995
            $supplierinvoiceline->origin = /** @scrutinizer ignore-deprecated */ $this->origin;

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

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

Loading history...
2996
            $supplierinvoiceline->origin_id = $origin_id;
2997
            $supplierinvoiceline->fk_unit = $fk_unit;
2998
2999
            // Multicurrency
3000
            $supplierinvoiceline->fk_multicurrency = $this->fk_multicurrency;
3001
            $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...
3002
            $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
3003
3004
            $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
3005
            $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
3006
            $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
3007
3008
            if (is_array($array_options) && count($array_options) > 0) {
3009
                $supplierinvoiceline->array_options = $array_options;
3010
            }
3011
3012
            $result = $supplierinvoiceline->insert($notrigger);
3013
            if ($result > 0) {
3014
                // Reorder if child line
3015
                if (!empty($fk_parent_line)) {
3016
                    $this->line_order(true, 'DESC');
3017
                } elseif ($rang > 0 && $rang <= count($this->lines)) { // Update all rank of all other lines
3018
                    $linecount = count($this->lines);
3019
                    for ($ii = $rang; $ii <= $linecount; $ii++) {
3020
                        $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
3021
                    }
3022
                }
3023
3024
                // Mise a jour information denormalisees au niveau de la facture meme
3025
                $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.
3026
                if ($result > 0) {
3027
                    $this->db->commit();
3028
                    return $supplierinvoiceline->id;
3029
                } else {
3030
                    $this->error = $this->db->error();
3031
                    $this->db->rollback();
3032
                    return -1;
3033
                }
3034
            } else {
3035
                $this->error = $supplierinvoiceline->error;
3036
                $this->errors = $supplierinvoiceline->errors;
3037
                $this->db->rollback();
3038
                return -2;
3039
            }
3040
        } else {
3041
            return 0;
3042
        }
3043
    }
3044
3045
    /**
3046
     *  Create a document onto disk according to template model.
3047
     *
3048
     * @param string     $modele      Force template to use ('' to not force)
3049
     * @param Translate  $outputlangs Object lang a utiliser pour traduction
3050
     * @param int        $hidedetails Hide details of lines
3051
     * @param int        $hidedesc    Hide description
3052
     * @param int        $hideref     Hide ref
3053
     * @param null|array $moreparams  Array to provide more information
3054
     *
3055
     * @return     int                         Return integer <0 if KO, 0 if nothing done, >0 if OK
3056
     */
3057
    public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3058
    {
3059
        global $langs;
3060
3061
        $langs->load("suppliers");
3062
        $outputlangs->load("products");
3063
3064
        // Set the model on the model name to use
3065
        if (empty($modele)) {
3066
            if (getDolGlobalString('INVOICE_SUPPLIER_ADDON_PDF')) {
3067
                $modele = getDolGlobalString('INVOICE_SUPPLIER_ADDON_PDF');
3068
            } else {
3069
                $modele = ''; // No default value. For supplier invoice, we allow to disable all PDF generation
3070
            }
3071
        }
3072
3073
        if (empty($modele)) {
3074
            return 0;
3075
        } else {
3076
            $modelpath = "core/modules/supplier_invoice/doc/";
3077
3078
            return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3079
        }
3080
    }
3081
3082
    /**
3083
     * Returns the rights used for this class
3084
     * @return stdClass
0 ignored issues
show
Bug introduced by
The type DoliModules\Supplier\Model\stdClass was not found. Did you mean stdClass? If so, make sure to prefix the type with \.
Loading history...
3085
     */
3086
    public function getRights()
3087
    {
3088
        global $user;
3089
3090
        return $user->hasRight("fournisseur", "facture");
3091
    }
3092
3093
    /**
3094
     * Is credit note used
3095
     *
3096
     * @return bool
3097
     */
3098
    public function isCreditNoteUsed()
3099
    {
3100
        $isUsed = false;
3101
3102
        $sql = "SELECT fk_invoice_supplier FROM " . MAIN_DB_PREFIX . "societe_remise_except WHERE fk_invoice_supplier_source = " . ((int) $this->id);
3103
        $resql = $this->db->query($sql);
3104
        if (!empty($resql)) {
3105
            $obj = $this->db->fetch_object($resql);
3106
            if (!empty($obj->fk_invoice_supplier)) {
3107
                $isUsed = true;
3108
            }
3109
        }
3110
3111
        return $isUsed;
3112
    }
3113
3114
    /**
3115
     *  Return clicable link of object (with eventually picto)
3116
     *
3117
     * @param string $option    Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
3118
     * @param array  $arraydata Array of data
3119
     *
3120
     * @return     string                              HTML Code for Kanban thumb.
3121
     */
3122
    public function getKanbanView($option = '', $arraydata = null)
3123
    {
3124
        global $langs;
3125
3126
        $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
3127
3128
        $return = '<div class="box-flex-item box-flex-grow-zero">';
3129
        $return .= '<div class="info-box info-box-sm">';
3130
        $return .= '<span class="info-box-icon bg-infobox-action">';
3131
        $return .= img_picto('', $this->picto);
3132
        $return .= '</span>';
3133
        $return .= '<div class="info-box-content">';
3134
        $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . (method_exists($this, 'getNomUrl') ? $this->getNomUrl(1) : $this->ref) . '</span>';
3135
        if ($selected >= 0) {
3136
            $return .= '<input id="cb' . $this->id . '" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="' . $this->id . '"' . ($selected ? ' checked="checked"' : '') . '>';
3137
        }
3138
        if (!empty($arraydata['thirdparty'])) {
3139
            $return .= '<br><span class="info-box-label">' . $arraydata['thirdparty'] . '</span>';
3140
        }
3141
        if (property_exists($this, 'date')) {
3142
            $return .= '<br><span class="info-box-label">' . dol_print_date($this->date, 'day') . '</span>';
3143
        }
3144
        if (property_exists($this, 'total_ht')) {
3145
            $return .= ' &nbsp; <span class="info-box-label amount" title="' . dol_escape_htmltag($langs->trans("AmountHT")) . '">' . price($this->total_ht);
3146
            $return .= ' ' . $langs->trans("HT");
3147
            $return .= '</span>';
3148
        }
3149
        if (method_exists($this, 'getLibStatut')) {
3150
            $alreadypaid = (empty($arraydata['alreadypaid']) ? 0 : $arraydata['alreadypaid']);
3151
            $return .= '<br><div class="info-box-status">' . $this->getLibStatut(3, $alreadypaid) . '</div>';
3152
        }
3153
        $return .= '</div>';
3154
        $return .= '</div>';
3155
        $return .= '</div>';
3156
        return $return;
3157
    }
3158
3159
    /**
3160
     *  Return clicable name (with picto eventually)
3161
     *
3162
     * @param int    $withpicto             0=No picto, 1=Include picto into link, 2=Only picto
3163
     * @param string $option                Where point the link
3164
     * @param int    $max                   Max length of shown ref
3165
     * @param int    $short                 1=Return just URL
3166
     * @param string $moretitle             Add more text to title tooltip
3167
     * @param int    $notooltip             1=Disable tooltip
3168
     * @param int    $save_lastsearch_value -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save
3169
     *                                      lastsearch_values whenclicking
3170
     * @param int    $addlinktonotes        Add link to show notes
3171
     *
3172
     * @return     string                              String with URL
3173
     */
3174
    public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $moretitle = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = 0)
3175
    {
3176
        global $langs, $conf, $user, $hookmanager;
3177
3178
        $result = '';
3179
3180
        if ($option == 'withdraw') {
3181
            $url = DOL_URL_ROOT . '/compta/facture/prelevement.php?facid=' . $this->id . '&type=bank-transfer';
3182
        } elseif ($option == 'document') {
3183
            $url = DOL_URL_ROOT . '/fourn/facture/document.php?facid=' . $this->id;
3184
        } else {
3185
            $url = DOL_URL_ROOT . '/fourn/facture/card.php?facid=' . $this->id;
3186
        }
3187
3188
        if ($short) {
3189
            return $url;
3190
        }
3191
3192
        if ($option !== 'nolink') {
3193
            // Add param to save lastsearch_values or not
3194
            $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
3195
            if ($save_lastsearch_value == -1 && isset($_SERVER['PHP_SELF']) && preg_match('/list\.php/', $_SERVER['PHP_SELF'])) {
3196
                $add_save_lastsearch_values = 1;
3197
            }
3198
            if ($add_save_lastsearch_values) {
3199
                $url .= '&save_lastsearch_values=1';
3200
            }
3201
        }
3202
3203
        $picto = $this->picto;
3204
        if ($this->type == self::TYPE_REPLACEMENT) {
3205
            $picto .= 'r'; // Replacement invoice
3206
        }
3207
        if ($this->type == self::TYPE_CREDIT_NOTE) {
3208
            $picto .= 'a'; // Credit note
3209
        }
3210
        if ($this->type == self::TYPE_DEPOSIT) {
3211
            $picto .= 'd'; // Deposit invoice
3212
        }
3213
        $params = [
3214
            'id' => $this->id,
3215
            'objecttype' => $this->element,
3216
            'option' => $option,
3217
            'moretitle' => $moretitle,
3218
        ];
3219
        $classfortooltip = 'classfortooltip';
3220
        $dataparams = '';
3221
        if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
3222
            $classfortooltip = 'classforajaxtooltip';
3223
            $dataparams = ' data-params="' . dol_escape_htmltag(json_encode($params)) . '"';
3224
            $label = '';
3225
        } else {
3226
            $label = implode($this->getTooltipContentArray($params));
3227
        }
3228
3229
        $ref = $this->ref;
3230
        if (empty($ref)) {
3231
            $ref = $this->id;
3232
        }
3233
3234
        $linkclose = '';
3235
        if (empty($notooltip)) {
3236
            if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3237
                $label = $langs->trans("ShowSupplierInvoice");
3238
                $linkclose .= ' alt="' . dol_escape_htmltag($label, 1) . '"';
3239
            }
3240
            $linkclose .= ($label ? ' title="' . dol_escape_htmltag($label, 1) . '"' : ' title="tocomplete"');
3241
            $linkclose .= $dataparams . ' class="' . $classfortooltip . '"';
3242
        }
3243
3244
        $linkstart = '<a href="' . $url . '"';
3245
        $linkstart .= $linkclose . '>';
3246
        $linkend = '</a>';
3247
3248
        $result .= $linkstart;
3249
        if ($withpicto) {
3250
            $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="' . (($withpicto != 2) ? 'paddingright ' : '') . '"'), 0, 0, $notooltip ? 0 : 1);
3251
        }
3252
        if ($withpicto != 2) {
3253
            $result .= ($max ? dol_trunc($ref, $max) : $ref);
3254
        }
3255
        $result .= $linkend;
3256
3257
        if ($addlinktonotes) {
3258
            $txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
3259
            if ($txttoshow) {
3260
                $notetoshow = $langs->trans("ViewPrivateNote") . ':<br>' . dol_string_nohtmltag($txttoshow, 1);
3261
                $result .= ' <span class="note inline-block">';
3262
                $result .= '<a href="' . DOL_URL_ROOT . '/fourn/facture/note.php?id=' . $this->id . '" class="classfortooltip" title="' . dol_escape_htmltag($notetoshow) . '">';
3263
                $result .= img_picto('', 'note');
3264
                $result .= '</a>';
3265
                $result .= '</span>';
3266
            }
3267
        }
3268
        global $action;
3269
        $hookmanager->initHooks([$this->element . 'dao']);
3270
        $parameters = ['id' => $this->id, 'getnomurl' => &$result];
3271
        $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
3272
        if ($reshook > 0) {
3273
            $result = $hookmanager->resPrint;
3274
        } else {
3275
            $result .= $hookmanager->resPrint;
3276
        }
3277
        return $result;
3278
    }
3279
3280
    /**
3281
     * getTooltipContentArray
3282
     *
3283
     * @param array $params ex option, infologin
3284
     *
3285
     * @return array
3286
     * @since v18
3287
     */
3288
    public function getTooltipContentArray($params)
3289
    {
3290
        global $conf, $langs, $mysoc;
3291
3292
        $langs->load('bills');
3293
3294
        $datas = [];
3295
        $moretitle = $params['moretitle'] ?? '';
3296
        $picto = $this->picto;
3297
        if ($this->type == self::TYPE_REPLACEMENT) {
3298
            $picto .= 'r'; // Replacement invoice
3299
        }
3300
        if ($this->type == self::TYPE_CREDIT_NOTE) {
3301
            $picto .= 'a'; // Credit note
3302
        }
3303
        if ($this->type == self::TYPE_DEPOSIT) {
3304
            $picto .= 'd'; // Deposit invoice
3305
        }
3306
3307
        $datas['picto'] = img_picto('', $this->picto) . ' <u class="paddingrightonly">' . $langs->trans("SupplierInvoice") . '</u>';
3308
        if ($this->type == self::TYPE_REPLACEMENT) {
3309
            $datas['picto'] .= '<u class="paddingrightonly">' . $langs->transnoentitiesnoconv("InvoiceReplace") . '</u>';
3310
        } elseif ($this->type == self::TYPE_CREDIT_NOTE) {
3311
            $datas['picto'] .= '<u class="paddingrightonly">' . $langs->transnoentitiesnoconv("CreditNote") . '</u>';
3312
        } elseif ($this->type == self::TYPE_DEPOSIT) {
3313
            $datas['picto'] .= '<u class="paddingrightonly">' . $langs->transnoentitiesnoconv("Deposit") . '</u>';
3314
        }
3315
        if (isset($this->status)) {
3316
            $alreadypaid = -1;
3317
            if (isset($this->alreadypaid)) {
3318
                $alreadypaid = $this->alreadypaid;
3319
            }
3320
3321
            $datas['picto'] .= ' ' . $this->getLibStatut(5, $alreadypaid);
3322
        }
3323
        if ($moretitle) {
3324
            $datas['picto'] .= ' - ' . $moretitle;
3325
        }
3326
        if (!empty($this->ref)) {
3327
            $datas['ref'] = '<br><b>' . $langs->trans('Ref') . ':</b> ' . $this->ref;
3328
        }
3329
        if (!empty($this->ref_supplier)) {
3330
            $datas['refsupplier'] = '<br><b>' . $langs->trans('RefSupplier') . ':</b> ' . $this->ref_supplier;
3331
        }
3332
        if (!empty($this->label)) {
3333
            $datas['label'] = '<br><b>' . $langs->trans('Label') . ':</b> ' . $this->label;
3334
        }
3335
        if (!empty($this->date)) {
3336
            $datas['date'] = '<br><b>' . $langs->trans('Date') . ':</b> ' . dol_print_date($this->date, 'day');
3337
        }
3338
        if (!empty($this->date_echeance)) {
3339
            $datas['date_echeance'] = '<br><b>' . $langs->trans('DateDue') . ':</b> ' . dol_print_date($this->date_echeance, 'day');
3340
        }
3341
        if (!empty($this->total_ht)) {
3342
            $datas['amountht'] = '<br><b>' . $langs->trans('AmountHT') . ':</b> ' . price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
3343
        }
3344
        if (!empty($this->total_tva)) {
3345
            $datas['totaltva'] = '<br><b>' . $langs->trans('AmountVAT') . ':</b> ' . price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
3346
        }
3347
        if (!empty($this->total_localtax1) && $this->total_localtax1 != 0) {
3348
            // We keep test != 0 because $this->total_localtax1 can be '0.00000000'
3349
            $datas['amountlt1'] = '<br><b>' . $langs->transcountry('AmountLT1', $mysoc->country_code) . ':</b> ' . price($this->total_localtax1, 0, $langs, 0, -1, -1, $conf->currency);
3350
        }
3351
        if (!empty($this->total_localtax2) && $this->total_localtax2 != 0) {
3352
            $datas['amountlt2'] = '<br><b>' . $langs->transcountry('AmountLT2', $mysoc->country_code) . ':</b> ' . price($this->total_localtax2, 0, $langs, 0, -1, -1, $conf->currency);
3353
        }
3354
        if (!empty($this->revenuestamp)) {
3355
            $datas['amountrevenustamp'] = '<br><b>' . $langs->trans('RevenueStamp') . ':</b> ' . price($this->revenuestamp, 0, $langs, 0, -1, -1, $conf->currency);
3356
        }
3357
        if (!empty($this->total_ttc)) {
3358
            $datas['totalttc'] = '<br><b>' . $langs->trans('AmountTTC') . ':</b> ' . price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
3359
        }
3360
        return $datas;
3361
    }
3362
3363
    /**
3364
     *  Change the option VAT reverse charge
3365
     *
3366
     * @param int $vatreversecharge 0 = Off, 1 = On
3367
     *
3368
     * @return     int                         1 if OK, 0 if KO
3369
     */
3370
    public function setVATReverseCharge($vatreversecharge)
3371
    {
3372
        if (!$this->table_element) {
3373
            dol_syslog(get_class($this) . "::setVATReverseCharge was called on object with property table_element not defined", LOG_ERR);
3374
            return -1;
3375
        }
3376
3377
        dol_syslog(get_class($this) . '::setVATReverseCharge(' . $vatreversecharge . ')');
3378
3379
        $sql = "UPDATE " . MAIN_DB_PREFIX . $this->table_element;
3380
        $sql .= " SET vat_reverse_charge = " . ((int) $vatreversecharge);
3381
        $sql .= " WHERE rowid=" . ((int) $this->id);
3382
3383
        if ($this->db->query($sql)) {
3384
            $this->vat_reverse_charge = ($vatreversecharge == 0) ? 0 : 1;
3385
            return 1;
3386
        } else {
3387
            dol_syslog(get_class($this) . '::setVATReverseCharge Error ', LOG_DEBUG);
3388
            $this->error = $this->db->error();
3389
            return 0;
3390
        }
3391
    }
3392
3393
    /**
3394
     *  Send reminders by emails for supplier invoices validated that are due.
3395
     *  CAN BE A CRON TASK
3396
     *
3397
     * @param int        $nbdays         Delay before due date (or after if delay is negative)
3398
     * @param string     $paymentmode    '' or 'all' by default (no filter), or 'LIQ', 'CHQ', CB', ...
3399
     * @param int|string $template       Name (or id) of email template (Must be a template of type
3400
     *                                   'invoice_supplier_send')
3401
     * @param string     $datetouse      'duedate' (default) or 'invoicedate'
3402
     * @param string     $forcerecipient Force email of recipient (for example to send the email to an accountant
3403
     *                                   supervisor instead of the customer)
3404
     *
3405
     * @return int                             0 if OK, <>0 if KO (this function is used also by cron so only 0 is OK)
3406
     */
3407
    public function sendEmailsRemindersOnSupplierInvoiceDueDate($nbdays = 0, $paymentmode = 'all', $template = '', $datetouse = 'duedate', $forcerecipient = '')
3408
    {
3409
        global $conf, $langs, $user;
3410
3411
        $this->output = '';
3412
        $this->error = '';
3413
        $nbMailSend = 0;
3414
3415
        $error = 0;
3416
        $errorsMsg = [];
3417
3418
        $langs->load('bills');
3419
3420
        if (!isModEnabled(empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD) ? 'fournisseur' : 'supplier_invoice')) {   // Should not happen. If module disabled, cron job should not be visible.
3421
            $this->output .= $langs->trans('ModuleNotEnabled', $langs->transnoentitiesnoconv('Suppliers'));
3422
            return 0;
3423
        }
3424
        if (!in_array($datetouse, ['duedate', 'invoicedate'])) {
3425
            $this->output .= 'Bad value for parameter datetouse. Must be "duedate" or "invoicedate"';
3426
            return 0;
3427
        }
3428
3429
        require_once BASE_PATH . '/../Dolibarr/Lib/Date.php';
3430
        require_once DOL_DOCUMENT_ROOT . '/core/class/CMailFile.class.php';
3431
        $formmail = new FormMail($this->db);
3432
3433
        $now = dol_now();
3434
        $tmpidate = dol_get_first_hour(dol_time_plus_duree($now, $nbdays, 'd'), 'gmt');
3435
3436
        $tmpinvoice = new FactureFournisseur($this->db);
3437
3438
        dol_syslog(__METHOD__ . " start", LOG_INFO);
3439
3440
        // Select all action comm reminder
3441
        $sql = "SELECT rowid as id FROM " . MAIN_DB_PREFIX . "facture_fourn as f";
3442
        if (!empty($paymentmode) && $paymentmode != 'all') {
3443
            $sql .= ", " . MAIN_DB_PREFIX . "c_paiement as cp";
3444
        }
3445
        $sql .= " WHERE f.paye = 0";    // Only unpaid
3446
        $sql .= " AND f.fk_statut = " . self::STATUS_VALIDATED;   // Only validated status
3447
        if ($datetouse == 'invoicedate') {
3448
            $sql .= " AND f.datef = '" . $this->db->idate($tmpidate, 'gmt') . "'";
3449
        } else {
3450
            $sql .= " AND f.date_lim_reglement = '" . $this->db->idate($tmpidate, 'gmt') . "'";
3451
        }
3452
        $sql .= " AND f.entity IN (" . getEntity('supplier_invoice', 0) . ")";  // One batch process only one company (no sharing)
3453
        if (!empty($paymentmode) && $paymentmode != 'all') {
3454
            $sql .= " AND f.fk_mode_reglement = cp.id AND cp.code = '" . $this->db->escape($paymentmode) . "'";
3455
        }
3456
        // TODO Add a filter to check there is no payment started yet
3457
        if ($datetouse == 'invoicedate') {
3458
            $sql .= $this->db->order("datef", "ASC");
3459
        } else {
3460
            $sql .= $this->db->order("date_lim_reglement", "ASC");
3461
        }
3462
3463
        $resql = $this->db->query($sql);
3464
3465
        $stmpidate = dol_print_date($tmpidate, 'day', 'gmt');
3466
        if ($datetouse == 'invoicedate') {
3467
            $this->output .= $langs->transnoentitiesnoconv("SearchValidatedSupplierInvoicesWithDate", $stmpidate);
3468
        } else {
3469
            $this->output .= $langs->transnoentitiesnoconv("SearchUnpaidSupplierInvoicesWithDueDate", $stmpidate);
3470
        }
3471
        if (!empty($paymentmode) && $paymentmode != 'all') {
3472
            $this->output .= ' (' . $langs->transnoentitiesnoconv("PaymentMode") . ' ' . $paymentmode . ')';
3473
        }
3474
        $this->output .= '<br>';
3475
3476
        if ($resql) {
3477
            while ($obj = $this->db->fetch_object($resql)) {
3478
                if (!$error) {
3479
                    // Load event
3480
                    $res = $tmpinvoice->fetch($obj->id);
3481
                    if ($res > 0) {
3482
                        $tmpinvoice->fetch_thirdparty();
3483
3484
                        $outputlangs = new Translate('', $conf);
3485
                        if ($tmpinvoice->thirdparty->default_lang) {
3486
                            $outputlangs->setDefaultLang($tmpinvoice->thirdparty->default_lang);
3487
                            $outputlangs->loadLangs(["main", "suppliers"]);
3488
                        } else {
3489
                            $outputlangs = $langs;
3490
                        }
3491
3492
                        // Select email template according to language of recipient
3493
                        $templateId = 0;
3494
                        $templateLabel = '';
3495
                        if (empty($template) || $template == 'EmailTemplateCode') {
3496
                            $templateLabel = '(SendingReminderEmailOnUnpaidSupplierInvoice)';
3497
                        } else {
3498
                            if (is_numeric($template)) {
3499
                                $templateId = $template;
3500
                            } else {
3501
                                $templateLabel = $template;
3502
                            }
3503
                        }
3504
3505
                        $arraymessage = $formmail->getEMailTemplate($this->db, 'invoice_supplier_send', $user, $outputlangs, $templateId, 1, $templateLabel);
3506
                        if (is_numeric($arraymessage) && $arraymessage <= 0) {
3507
                            $langs->load("errors");
3508
                            $this->output .= $langs->trans('ErrorFailedToFindEmailTemplate', $template);
3509
                            return 0;
3510
                        }
3511
3512
                        // PREPARE EMAIL
3513
                        $errormesg = '';
3514
3515
                        // Make substitution in email content
3516
                        $substitutionarray = getCommonSubstitutionArray($outputlangs, 0, '', $tmpinvoice);
3517
3518
                        complete_substitutions_array($substitutionarray, $outputlangs, $tmpinvoice);
3519
3520
                        // Topic
3521
                        $sendTopic = make_substitutions(empty($arraymessage->topic) ? $outputlangs->transnoentitiesnoconv('InformationMessage') : $arraymessage->topic, $substitutionarray, $outputlangs, 1);
3522
3523
                        // Content
3524
                        $content = $outputlangs->transnoentitiesnoconv($arraymessage->content);
3525
3526
                        $sendContent = make_substitutions($content, $substitutionarray, $outputlangs, 1);
3527
3528
                        // Recipient
3529
                        $to = [];
3530
                        if ($forcerecipient) {  // If a recipient was forced
3531
                            $to = [$forcerecipient];
3532
                        } else {
3533
                            $res = $tmpinvoice->fetch_thirdparty();
3534
                            $recipient = $tmpinvoice->thirdparty;
3535
                            if ($res > 0) {
3536
                                $tmparraycontact = $tmpinvoice->liste_contact(-1, 'internal', 0, 'SALESREPFOLL');
3537
                                if (is_array($tmparraycontact) && count($tmparraycontact) > 0) {
3538
                                    foreach ($tmparraycontact as $data_email) {
3539
                                        if (!empty($data_email['email'])) {
3540
                                            $to[] = $data_email['email'];
3541
                                        }
3542
                                    }
3543
                                }
3544
                                if (empty($to) && !empty($recipient->email)) {
3545
                                    $to[] = $recipient->email;
3546
                                }
3547
                                if (empty($to)) {
3548
                                    $errormesg = "Failed to send remind to thirdparty id=" . $tmpinvoice->socid . ". No email defined for supplier invoice or customer.";
3549
                                    $error++;
3550
                                }
3551
                            } else {
3552
                                $errormesg = "Failed to load recipient with thirdparty id=" . $tmpinvoice->socid;
3553
                                $error++;
3554
                            }
3555
                        }
3556
3557
                        // Sender
3558
                        $from = getDolGlobalString('MAIN_MAIL_EMAIL_FROM');
3559
                        if (!empty($arraymessage->email_from)) {    // If a sender is defined into template, we use it in priority
3560
                            $from = $arraymessage->email_from;
3561
                        }
3562
                        if (empty($from)) {
3563
                            $errormesg = "Failed to get sender into global setup MAIN_MAIL_EMAIL_FROM";
3564
                            $error++;
3565
                        }
3566
3567
                        if (!$error && !empty($to)) {
3568
                            $this->db->begin();
3569
3570
                            $to = implode(',', $to);
3571
                            if (!empty($arraymessage->email_to)) {  // If a recipient is defined into template, we add it
3572
                                $to = $to . ',' . $arraymessage->email_to;
3573
                            }
3574
3575
                            // Errors Recipient
3576
                            $errors_to = $conf->global->MAIN_MAIL_ERRORS_TO;
3577
3578
                            $trackid = 'inv' . $tmpinvoice->id;
3579
                            $sendcontext = 'standard';
3580
3581
                            $email_tocc = '';
3582
                            if (!empty($arraymessage->email_tocc)) {    // If a CC is defined into template, we use it
3583
                                $email_tocc = $arraymessage->email_tocc;
3584
                            }
3585
3586
                            $email_tobcc = '';
3587
                            if (!empty($arraymessage->email_tobcc)) {   // If a BCC is defined into template, we use it
3588
                                $email_tobcc = $arraymessage->email_tobcc;
3589
                            }
3590
3591
                            // Mail Creation
3592
                            $cMailFile = new CMailFile($sendTopic, $to, $from, $sendContent, [], [], [], $email_tocc, $email_tobcc, 0, 1, $errors_to, '', $trackid, '', $sendcontext, '');
0 ignored issues
show
Bug introduced by
The type DoliModules\Supplier\Model\CMailFile was not found. Did you mean CMailFile? If so, make sure to prefix the type with \.
Loading history...
3593
3594
                            // Sending Mail
3595
                            if ($cMailFile->sendfile()) {
3596
                                $nbMailSend++;
3597
3598
                                // Add a line into event table
3599
3600
3601
                                // Insert record of emails sent
3602
                                $actioncomm = new ActionComm($this->db);
3603
3604
                                $actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
3605
                                $actioncomm->socid = $tmpinvoice->thirdparty->id; // To link to a company
3606
                                $actioncomm->contact_id = 0;
3607
3608
                                $actioncomm->code = 'AC_EMAIL';
3609
                                $actioncomm->label = 'sendEmailsRemindersOnInvoiceDueDateOK (nbdays=' . $nbdays . ' paymentmode=' . $paymentmode . ' template=' . $template . ' datetouse=' . $datetouse . ' forcerecipient=' . $forcerecipient . ')';
3610
                                $actioncomm->note_private = $sendContent;
3611
                                $actioncomm->fk_project = $tmpinvoice->fk_project;
3612
                                $actioncomm->datep = dol_now();
3613
                                $actioncomm->datef = $actioncomm->datep;
3614
                                $actioncomm->percentage = -1; // Not applicable
3615
                                $actioncomm->authorid = $user->id; // User saving action
3616
                                $actioncomm->userownerid = $user->id; // Owner of action
3617
                                // Fields when action is an email (content should be added into note)
3618
                                $actioncomm->email_msgid = $cMailFile->msgid;
3619
                                $actioncomm->email_subject = $sendTopic;
3620
                                $actioncomm->email_from = $from;
3621
                                $actioncomm->email_sender = '';
3622
                                $actioncomm->email_to = $to;
3623
                                //$actioncomm->email_tocc = $sendtocc;
3624
                                //$actioncomm->email_tobcc = $sendtobcc;
3625
                                //$actioncomm->email_subject = $subject;
3626
                                $actioncomm->errors_to = $errors_to;
3627
3628
                                $actioncomm->elementtype = 'invoice_supplier';
3629
                                $actioncomm->fk_element = $tmpinvoice->id;
3630
3631
                                //$actioncomm->extraparams = $extraparams;
3632
3633
                                $actioncomm->create($user);
3634
                            } else {
3635
                                $errormesg = $cMailFile->error . ' : ' . $to;
3636
                                $error++;
3637
3638
                                // Add a line into event table
3639
3640
3641
                                // Insert record of emails sent
3642
                                $actioncomm = new ActionComm($this->db);
3643
3644
                                $actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
3645
                                $actioncomm->socid = $tmpinvoice->thirdparty->id; // To link to a company
3646
                                $actioncomm->contact_id = 0;
3647
3648
                                $actioncomm->code = 'AC_EMAIL';
3649
                                $actioncomm->label = 'sendEmailsRemindersOnInvoiceDueDateKO';
3650
                                $actioncomm->note_private = $errormesg;
3651
                                $actioncomm->fk_project = $tmpinvoice->fk_project;
3652
                                $actioncomm->datep = dol_now();
3653
                                $actioncomm->datef = $actioncomm->datep;
3654
                                $actioncomm->percentage = -1; // Not applicable
3655
                                $actioncomm->authorid = $user->id; // User saving action
3656
                                $actioncomm->userownerid = $user->id; // Owner of action
3657
                                // Fields when action is an email (content should be added into note)
3658
                                $actioncomm->email_msgid = $cMailFile->msgid;
3659
                                $actioncomm->email_from = $from;
3660
                                $actioncomm->email_sender = '';
3661
                                $actioncomm->email_to = $to;
3662
                                //$actioncomm->email_tocc = $sendtocc;
3663
                                //$actioncomm->email_tobcc = $sendtobcc;
3664
                                //$actioncomm->email_subject = $subject;
3665
                                $actioncomm->errors_to = $errors_to;
3666
3667
                                //$actioncomm->extraparams = $extraparams;
3668
3669
                                $actioncomm->create($user);
3670
                            }
3671
3672
                            $this->db->commit();    // We always commit
3673
                        }
3674
3675
                        if ($errormesg) {
3676
                            $errorsMsg[] = $errormesg;
3677
                        }
3678
                    } else {
3679
                        $errorsMsg[] = 'Failed to fetch record invoice with ID = ' . $obj->id;
3680
                        $error++;
3681
                    }
3682
                }
3683
            }
3684
        } else {
3685
            $error++;
3686
        }
3687
3688
        if (!$error) {
3689
            $this->output .= 'Nb of emails sent : ' . $nbMailSend;
3690
3691
            dol_syslog(__METHOD__ . " end - " . $this->output, LOG_INFO);
3692
3693
            return 0;
3694
        } else {
3695
            $this->error = 'Nb of emails sent : ' . $nbMailSend . ', ' . (empty($errorsMsg) ? $error : implode(', ', $errorsMsg));
3696
3697
            dol_syslog(__METHOD__ . " end - " . $this->error, LOG_INFO);
3698
3699
            return $error;
3700
        }
3701
    }
3702
}
3703