dolibarr 23.0.3
fournisseur.facture.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2002-2004 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2004-2012 Laurent Destailleur <eldy@users.sourceforge.net>
4 * Copyright (C) 2004 Christophe Combelles <ccomb@free.fr>
5 * Copyright (C) 2005 Marc Barilley <marc@ocebo.com>
6 * Copyright (C) 2005-2012 Regis Houssin <regis.houssin@inodbox.com>
7 * Copyright (C) 2010-2023 Juanjo Menent <jmenent@simnandez.es>
8 * Copyright (C) 2013-2019 Philippe Grand <philippe.grand@atoo-net.com>
9 * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
10 * Copyright (C) 2014-2016 Marcos García <marcosgdf@gmail.com>
11 * Copyright (C) 2015 Bahfir Abbes <bafbes@gmail.com>
12 * Copyright (C) 2015-2022 Ferran Marcet <fmarcet@2byte.es>
13 * Copyright (C) 2016-2026 Alexandre Spangaro <alexandre@inovea-conseil.com>
14 * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
15 * Copyright (C) 2018-2025 Frédéric France <frederic.france@free.fr>
16 * Copyright (C) 2022 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
17 * Copyright (C) 2023 Nick Fragoulis
18 * Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
19 *
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 3 of the License, or
23 * (at your option) any later version.
24 *
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
29 *
30 * You should have received a copy of the GNU General Public License
31 * along with this program. If not, see <https://www.gnu.org/licenses/>.
32 */
33
40require_once DOL_DOCUMENT_ROOT.'/core/class/commoninvoice.class.php';
41require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php';
42require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.ligne.class.php';
43require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
44
45if (isModEnabled('accounting')) {
46 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formaccounting.class.php';
47 require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php';
48}
49
54{
58 public $element = 'invoice_supplier';
59
63 public $table_element = 'facture_fourn';
64
68 public $table_element_line = 'facture_fourn_det';
69
73 public $class_element_line = 'SupplierInvoiceLine';
77 public $fk_element = 'fk_facture_fourn';
78
82 public $picto = 'supplier_invoice';
83
88 public $restrictiononfksoc = 1;
89
93 protected $table_ref_field = 'ref';
94
98 public $rowid;
99
103 public $ref;
104
108 public $ref_supplier;
109
114 public $libelle;
118 public $label;
119
120 //Check constants for types
121 public $type = self::TYPE_STANDARD;
122
129 public $statut;
130
136 public $status;
137
144 public $fk_statut;
145
151 public $paye;
156 public $paid;
157
162 public $author;
163
168 public $fk_user_valid;
169
175 public $datec;
176
182 public $date_echeance;
183
188 public $amount = 0;
193 public $remise = 0;
194
199 public $tva;
200
201 // Warning: Do not set default value into property definition. it must stay null.
202 // For example to avoid to have substitution done when object is generic and not yet defined.
204 public $localtax1;
206 public $localtax2;
208 public $total_ht;
210 public $total_tva;
212 public $total_localtax1;
214 public $total_localtax2;
216 public $total_ttc;
217
223 public $note;
225 public $note_private;
227 public $note_public;
229 public $propalid;
230
234 public $fk_account; // default bank account
235
239 public $transport_mode_id;
240
244 public $vat_reverse_charge;
245
249 public $extraparams = array();
250
255 public $lines = array();
256
261 public $fournisseur;
262
264
267 public $fk_facture_source;
268
270 public $fac_rec;
272 public $fk_fac_rec_source;
273
274 public $fields = array(
275 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 10),
276 'ref' => array('type' => 'varchar(255)', 'label' => 'Ref', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'showoncombobox' => 1, 'position' => 15),
277 'ref_supplier' => array('type' => 'varchar(255)', 'label' => 'RefSupplier', 'enabled' => 1, 'visible' => -1, 'position' => 20),
278 'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 25, 'index' => 1),
279 'ref_ext' => array('type' => 'varchar(255)', 'label' => 'RefExt', 'enabled' => 1, 'visible' => 0, 'position' => 30),
280 'type' => array('type' => 'smallint(6)', 'label' => 'Type', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 35),
281 'subtype' => array('type' => 'smallint(6)', 'label' => 'InvoiceSubtype', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 36),
282 'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 'isModEnabled("societe")', 'visible' => -1, 'notnull' => 1, 'position' => 40),
283 'datec' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -1, 'position' => 45),
284 'datef' => array('type' => 'date', 'label' => 'Date', 'enabled' => 1, 'visible' => -1, 'position' => 50),
285 'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 55),
286 'libelle' => array('type' => 'varchar(255)', 'label' => 'Label', 'enabled' => 1, 'visible' => -1, 'position' => 60),
287 'paye' => array('type' => 'smallint(6)', 'label' => 'Paye', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 65),
288 'amount' => array('type' => 'double(24,8)', 'label' => 'Amount', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 70),
289 'remise' => array('type' => 'double(24,8)', 'label' => 'Discount', 'enabled' => 1, 'visible' => -1, 'position' => 75),
290 'close_code' => array('type' => 'varchar(16)', 'label' => 'CloseCode', 'enabled' => 1, 'visible' => -1, 'position' => 80),
291 'close_note' => array('type' => 'varchar(128)', 'label' => 'CloseNote', 'enabled' => 1, 'visible' => -1, 'position' => 85),
292 'tva' => array('type' => 'double(24,8)', 'label' => 'Tva', 'enabled' => 1, 'visible' => -1, 'position' => 90),
293 'localtax1' => array('type' => 'double(24,8)', 'label' => 'Localtax1', 'enabled' => 1, 'visible' => -1, 'position' => 95),
294 'localtax2' => array('type' => 'double(24,8)', 'label' => 'Localtax2', 'enabled' => 1, 'visible' => -1, 'position' => 100),
295 'total_ht' => array('type' => 'double(24,8)', 'label' => 'TotalHT', 'enabled' => 1, 'visible' => -1, 'position' => 105),
296 'total_tva' => array('type' => 'double(24,8)', 'label' => 'TotalVAT', 'enabled' => 1, 'visible' => -1, 'position' => 110),
297 'total_ttc' => array('type' => 'double(24,8)', 'label' => 'TotalTTC', 'enabled' => 1, 'visible' => -1, 'position' => 115),
298 'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => -1, 'position' => 125),
299 'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'position' => 130),
300 'fk_user_valid' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserValidation', 'enabled' => 1, 'visible' => -1, 'position' => 135),
301 'fk_facture_source' => array('type' => 'integer', 'label' => 'Fk facture source', 'enabled' => 1, 'visible' => -1, 'position' => 140),
302 'fk_projet' => array('type' => 'integer:Project:projet/class/project.class.php:1:fk_statut=1', 'label' => 'Project', 'enabled' => "isModEnabled('project')", 'visible' => -1, 'position' => 145),
303 'fk_account' => array('type' => 'integer', 'label' => 'Account', 'enabled' => 'isModEnabled("bank")', 'visible' => -1, 'position' => 150),
304 'fk_cond_reglement' => array('type' => 'integer', 'label' => 'PaymentTerm', 'enabled' => 1, 'visible' => -1, 'position' => 155),
305 'fk_mode_reglement' => array('type' => 'integer', 'label' => 'PaymentMode', 'enabled' => 1, 'visible' => -1, 'position' => 160),
306 'date_lim_reglement' => array('type' => 'date', 'label' => 'DateLimReglement', 'enabled' => 1, 'visible' => -1, 'position' => 165),
307 'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 170),
308 'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 175),
309 'model_pdf' => array('type' => 'varchar(255)', 'label' => 'ModelPdf', 'enabled' => 1, 'visible' => 0, 'position' => 180),
310 'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => -1, 'position' => 190),
311 'fk_incoterms' => array('type' => 'integer', 'label' => 'IncotermCode', 'enabled' => 1, 'visible' => -1, 'position' => 195),
312 'location_incoterms' => array('type' => 'varchar(255)', 'label' => 'IncotermLocation', 'enabled' => 1, 'visible' => -1, 'position' => 200),
313 'fk_multicurrency' => array('type' => 'integer', 'label' => 'MulticurrencyId', 'enabled' => 1, 'visible' => -1, 'position' => 205),
314 'multicurrency_code' => array('type' => 'varchar(255)', 'label' => 'MulticurrencyCode', 'enabled' => 1, 'visible' => -1, 'position' => 210),
315 'multicurrency_tx' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyRate', 'enabled' => 1, 'visible' => -1, 'position' => 215),
316 'multicurrency_total_ht' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyTotalHT', 'enabled' => 1, 'visible' => -1, 'position' => 220),
317 'multicurrency_total_tva' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyTotalVAT', 'enabled' => 1, 'visible' => -1, 'position' => 225),
318 'multicurrency_total_ttc' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyTotalTTC', 'enabled' => 1, 'visible' => -1, 'position' => 230),
319 'date_pointoftax' => array('type' => 'date', 'label' => 'Date pointoftax', 'enabled' => 1, 'visible' => -1, 'position' => 235),
320 'date_valid' => array('type' => 'date', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -1, 'position' => 240),
321 'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'Last main doc', 'enabled' => 1, 'visible' => -1, 'position' => 245),
322 'fk_statut' => array('type' => 'smallint(6)', 'label' => 'Status', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 500),
323 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'position' => 900),
324 );
325
326
330 const TYPE_STANDARD = 0;
331
336
341
345 const TYPE_DEPOSIT = 3;
346
350 const STATUS_DRAFT = 0;
351
356
364 const STATUS_CLOSED = 2;
365
374
375 const CLOSECODE_DISCOUNTVAT = 'discount_vat';
376 const CLOSECODE_BADCREDIT = 'badsupplier';
377 const CLOSECODE_ABANDONED = 'abandon';
378 const CLOSECODE_REPLACED = 'replaced';
379
380
386 public function __construct($db)
387 {
388 $this->db = $db;
389
390 $this->ismultientitymanaged = 1;
391 }
392
399 public function create($user)
400 {
401 global $langs, $conf, $hookmanager;
402
403 $error = 0;
404 $now = dol_now();
405
406 // Clean parameters
407 if (isset($this->ref_supplier)) {
408 $this->ref_supplier = trim($this->ref_supplier);
409 }
410 if (empty($this->type)) {
411 $this->type = self::TYPE_STANDARD;
412 }
413 if (empty($this->date)) {
414 $this->date = $now;
415 }
416
417 // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
418 if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
419 list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date);
420 } else {
421 $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
422 }
423 if (empty($this->fk_multicurrency)) {
424 $this->multicurrency_code = $conf->currency;
425 $this->fk_multicurrency = 0;
426 $this->multicurrency_tx = 1;
427 }
428 $this->entity = setEntity($this);
429
430 $this->db->begin();
431
432 // Defaults
433 $originaldatewhen = 0;
434 $nextdatewhen = 0;
435 $previousdaynextdatewhen = 0;
436 $_facrec = null;
437
438 // Create invoice from a template recurring invoice
439 if ($this->fac_rec > 0) {
440 $this->fk_fac_rec_source = $this->fac_rec;
441
442 require_once DOL_DOCUMENT_ROOT . '/fourn/class/fournisseur.facture-rec.class.php';
443 $_facrec = new FactureFournisseurRec($this->db);
444 $result = $_facrec->fetch($this->fac_rec);
445 $result = $_facrec->fetchObjectLinked(null, '', null, '', 'OR', 1, 'sourcetype', 0); // This load $_facrec->linkedObjectsIds
446
447 // Define some dates
448 if (!empty($_facrec->frequency)) {
449 $originaldatewhen = $_facrec->date_when;
450 $nextdatewhen = dol_time_plus_duree($originaldatewhen, $_facrec->frequency, $_facrec->unit_frequency);
451 $previousdaynextdatewhen = dol_time_plus_duree($nextdatewhen, -1, 'd');
452 $this->socid = $_facrec->socid;
453 }
454
455 $this->entity = $_facrec->entity; // Invoice created in same entity than template
456
457 // Fields coming from GUI
458 // @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
459 // set by posted page with $object->xxx = ... and this section should be removed.
460 $this->fk_project = GETPOSTINT('projectid') > 0 ? (GETPOSTINT('projectid')) : $_facrec->fk_project;
461 $this->note_public = GETPOST('note_public', 'restricthtml') ? GETPOST('note_public', 'restricthtml') : $_facrec->note_public;
462 $this->note_private = GETPOST('note_private', 'restricthtml') ? GETPOST('note_private', 'restricthtml') : $_facrec->note_private;
463 $this->model_pdf = GETPOST('model', 'alpha') ? GETPOST('model', 'alpha') : $_facrec->model_pdf;
464 $this->cond_reglement_id = GETPOSTINT('cond_reglement_id') > 0 ? (GETPOSTINT('cond_reglement_id')) : $_facrec->cond_reglement_id;
465 $this->mode_reglement_id = GETPOSTINT('mode_reglement_id') > 0 ? (GETPOSTINT('mode_reglement_id')) : $_facrec->mode_reglement_id;
466 $this->fk_account = GETPOST('fk_account') > 0 ? ((int) GETPOST('fk_account')) : $_facrec->fk_account;
467
468 // Set here to have this defined for substitution into notes, should be recalculated after adding lines to get same result
469 $this->total_ht = $_facrec->total_ht;
470 $this->total_ttc = $_facrec->total_ttc;
471
472 // Fields always coming from template
473 $this->fk_incoterms = $_facrec->fk_incoterms;
474 $this->location_incoterms = $_facrec->location_incoterms;
475
476 // Clean parameters
477 if (! $this->type) {
478 $this->type = self::TYPE_STANDARD;
479 }
480 $this->note_public = trim($this->note_public);
481 $this->note_private = trim($this->note_private);
482 $this->note_private = dol_concatdesc($this->note_private, $langs->trans("GeneratedFromRecurringInvoice", $_facrec->title));
483
484 $this->array_options = $_facrec->array_options;
485
486 if (! $this->mode_reglement_id) {
487 $this->mode_reglement_id = 0;
488 }
489 $this->status = self::STATUS_DRAFT;
490 $this->statut = self::STATUS_DRAFT; // deprecated
491
492 $this->linked_objects = $_facrec->linkedObjectsIds;
493 // We do not add link to template invoice or next invoice will be linked to all generated invoices
494 //$this->linked_objects['facturerec'][0] = $this->fac_rec;
495
496 $forceduedate = $this->calculate_date_lim_reglement();
497
498 // For recurring invoices, update date and number of last generation of recurring template invoice, before inserting new invoice
499 if ($_facrec->frequency > 0) {
500 $this->ref_supplier = trim($this->ref_supplier . '_' . ($_facrec->nb_gen_done + 1));
501 dol_syslog("This is a recurring invoice so we set date_last_gen and next date_when");
502 if (empty($_facrec->date_when)) {
503 $_facrec->date_when = $now;
504 }
505 $next_date = $_facrec->getNextDate(); // Calculate next date
506 $result = $_facrec->setValueFrom('date_last_gen', $now, '', 0, 'date', '', $user, '');
507 //$_facrec->setValueFrom('nb_gen_done', $_facrec->nb_gen_done + 1); // Not required, +1 already included into setNextDate when second param is 1.
508 $result = $_facrec->setNextDate($next_date, 1);
509 }
510
511 // Define lang of customer
512 $outputlangs = $langs;
513 $newlang = '';
514
515 if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang) && isset($this->thirdparty->default_lang)) {
516 $newlang = $this->thirdparty->default_lang; // for proposal, order, invoice, ...
517 }
518 if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang) && property_exists($this, 'default_lang') && isset($this->default_lang)) { // @phan-suppress-current-line PhanUndeclaredProperty
519 $newlang = $this->default_lang; // for thirdparty @phan-suppress-current-line PhanUndeclaredProperty
520 }
521 if (!empty($newlang)) {
522 $outputlangs = new Translate("", $conf);
523 $outputlangs->setDefaultLang($newlang);
524 } // Array of possible substitutions (See also file mailing-send.php that should manage same substitutions)
525 $substitutionarray = getCommonSubstitutionArray($outputlangs, 0, null, $this);
526 $substitutionarray['__INVOICE_PREVIOUS_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%m');
527 $substitutionarray['__INVOICE_MONTH__'] = dol_print_date($this->date, '%m');
528 $substitutionarray['__INVOICE_NEXT_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%m');
529 $substitutionarray['__INVOICE_PREVIOUS_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%B');
530 $substitutionarray['__INVOICE_MONTH_TEXT__'] = dol_print_date($this->date, '%B');
531 $substitutionarray['__INVOICE_NEXT_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%B');
532 $substitutionarray['__INVOICE_PREVIOUS_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'y'), '%Y');
533 $substitutionarray['__INVOICE_YEAR__'] = dol_print_date($this->date, '%Y');
534 $substitutionarray['__INVOICE_NEXT_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'y'), '%Y'); // Only for template invoice
535 $substitutionarray['__INVOICE_DATE_NEXT_INVOICE_BEFORE_GEN__'] = $originaldatewhen ? dol_print_date($originaldatewhen, 'dayhour') : '';
536 $substitutionarray['__INVOICE_DATE_NEXT_INVOICE_AFTER_GEN__'] = $nextdatewhen ? dol_print_date($nextdatewhen, 'dayhour') : '';
537 $substitutionarray['__INVOICE_PREVIOUS_DATE_NEXT_INVOICE_AFTER_GEN__'] = $previousdaynextdatewhen ? dol_print_date($previousdaynextdatewhen, 'dayhour') : '';
538 $substitutionarray['__INVOICE_COUNTER_CURRENT__'] = $_facrec->nb_gen_done;
539 $substitutionarray['__INVOICE_COUNTER_MAX__'] = $_facrec->nb_gen_max;
540
541 complete_substitutions_array($substitutionarray, $outputlangs);
542
543 $this->note_public = make_substitutions($this->note_public, $substitutionarray);
544 $this->note_private = make_substitutions($this->note_private, $substitutionarray);
545 }
546
547 // Define due date if not already defined
548 if (!empty($forceduedate)) {
549 $this->date_echeance = $forceduedate;
550 }
551
552 $sql = "INSERT INTO ".MAIN_DB_PREFIX."facture_fourn (";
553 $sql .= "ref";
554 $sql .= ", ref_supplier";
555 $sql .= ", ref_ext";
556 $sql .= ", entity";
557 $sql .= ", type";
558 $sql .= ", subtype";
559 $sql .= ", libelle";
560 $sql .= ", fk_soc";
561 $sql .= ", datec";
562 $sql .= ", datef";
563 $sql .= ", vat_reverse_charge";
564 $sql .= ", fk_projet";
565 $sql .= ", fk_cond_reglement";
566 $sql .= ", fk_mode_reglement";
567 $sql .= ", fk_account";
568 $sql .= ", note_private";
569 $sql .= ", note_public";
570 $sql .= ", fk_user_author";
571 $sql .= ", date_lim_reglement";
572 $sql .= ", fk_incoterms, location_incoterms";
573 $sql .= ", fk_multicurrency";
574 $sql .= ", multicurrency_code";
575 $sql .= ", multicurrency_tx";
576 $sql .= ", fk_facture_source";
577 $sql .= ", fk_fac_rec_source";
578 $sql .= ")";
579 $sql .= " VALUES (";
580 $sql .= "'(PROV)'";
581 $sql .= ", '".$this->db->escape($this->ref_supplier)."'";
582 $sql .= ", '".$this->db->escape($this->ref_ext)."'";
583 $sql .= ", ".((int) $this->entity);
584 $sql .= ", '".$this->db->escape((string) $this->type)."'";
585 $sql .= ", ".(isset($this->subtype) ? (int) $this->subtype : "null");
586 $sql .= ", '".$this->db->escape(isset($this->label) ? $this->label : (isset($this->libelle) ? $this->libelle : ''))."'";
587 $sql .= ", ".((int) $this->socid);
588 $sql .= ", '".$this->db->idate($now)."'";
589 $sql .= ", '".$this->db->idate($this->date)."'";
590 $sql .= ", ".($this->vat_reverse_charge != '' ? ((int) $this->vat_reverse_charge) : 0);
591 $sql .= ", ".($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
592 $sql .= ", ".($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : "null");
593 $sql .= ", ".($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : "null");
594 $sql .= ", ".($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
595 $sql .= ", '".$this->db->escape($this->note_private)."'";
596 $sql .= ", '".$this->db->escape($this->note_public)."'";
597 $sql .= ", ".((int) $user->id).",";
598 $sql .= $this->date_echeance != '' ? "'".$this->db->idate($this->date_echeance)."'" : "null";
599 $sql .= ", ".(int) $this->fk_incoterms;
600 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
601 $sql .= ", ".(int) $this->fk_multicurrency;
602 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
603 $sql .= ", ".(float) $this->multicurrency_tx;
604 $sql .= ", ".($this->fk_facture_source ? ((int) $this->fk_facture_source) : "null");
605 $sql .= ", ".(isset($this->fk_fac_rec_source) ? $this->fk_fac_rec_source : "NULL");
606 $sql .= ")";
607
608 dol_syslog(get_class($this)."::create", LOG_DEBUG);
609 $resql = $this->db->query($sql);
610 if ($resql) {
611 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'facture_fourn');
612
613 // Update ref with new one
614 $this->ref = '(PROV'.$this->id.')';
615 $sql = 'UPDATE '.MAIN_DB_PREFIX."facture_fourn SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".((int) $this->id);
616
617 dol_syslog(get_class($this)."::create", LOG_DEBUG);
618 $resql = $this->db->query($sql);
619 if (!$resql) {
620 $error++;
621 }
622
623 if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
624 $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
625 }
626
627 // Add object linked
628 if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
629 foreach ($this->linked_objects as $origin => $tmp_origin_id) {
630 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, ...))
631 foreach ($tmp_origin_id as $origin_id) {
632 $ret = $this->add_object_linked($origin, $origin_id);
633 if (!$ret) {
634 dol_print_error($this->db);
635 $error++;
636 }
637 }
638 } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
639 $origin_id = $tmp_origin_id;
640 $ret = $this->add_object_linked($origin, $origin_id);
641 if (!$ret) {
642 dol_print_error($this->db);
643 $error++;
644 }
645 }
646 }
647 }
648
649 if (!$error && empty($this->fac_rec) && count($this->lines) && is_object($this->lines[0])) { // If this->lines is array of InvoiceLines (preferred mode)
650 dol_syslog("There is ".count($this->lines)." lines that are invoice lines objects");
651 foreach ($this->lines as $i => $val) {
652 $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'facture_fourn_det (fk_facture_fourn, special_code, fk_remise_except)';
653 $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').')';
654
655 $resql_insert = $this->db->query($sql);
656 if ($resql_insert) {
657 $idligne = $this->db->last_insert_id(MAIN_DB_PREFIX.'facture_fourn_det');
658
659 $res = $this->updateline(
660 $idligne,
661 $this->lines[$i]->desc ? $this->lines[$i]->desc : $this->lines[$i]->description,
662 $this->lines[$i]->subprice,
663 $this->lines[$i]->tva_tx.($this->lines[$i]->vat_src_code ? ' ('.$this->lines[$i]->vat_src_code.')' : ''),
664 $this->lines[$i]->localtax1_tx,
665 $this->lines[$i]->localtax2_tx,
666 $this->lines[$i]->qty,
667 $this->lines[$i]->fk_product,
668 'HT',
669 (!empty($this->lines[$i]->info_bits) ? $this->lines[$i]->info_bits : ''),
670 $this->lines[$i]->product_type,
671 $this->lines[$i]->remise_percent,
672 0,
673 $this->lines[$i]->date_start,
674 $this->lines[$i]->date_end,
675 $this->lines[$i]->array_options,
676 $this->lines[$i]->fk_unit,
677 $this->lines[$i]->multicurrency_subprice,
678 $this->lines[$i]->ref_supplier
679 );
680 } else {
681 $this->error = $this->db->lasterror();
682 $this->db->rollback();
683 return -5;
684 }
685 }
686 } elseif (!$error && empty($this->fac_rec)) { // If this->lines is an array of invoice line arrays
687 dol_syslog("There is ".count($this->lines)." lines that are array lines");
688 foreach ($this->lines as $i => $val) {
689 $line = $this->lines[$i];
690
691 // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
692 //if (! is_object($line)) $line=json_decode(json_encode($line), false); // convert recursively array into object.
693 if (!is_object($line)) {
694 $line = (object) $line;
695 }
696
697 $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'facture_fourn_det (fk_facture_fourn, special_code, fk_remise_except)';
698 $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').')';
699
700 $resql_insert = $this->db->query($sql);
701 if ($resql_insert) {
702 $idligne = $this->db->last_insert_id(MAIN_DB_PREFIX.'facture_fourn_det');
703
704 $this->updateline(
705 $idligne,
706 $line->desc ? $line->desc : $line->description,
707 $line->subprice,
708 $line->tva_tx,
709 $line->localtax1_tx,
710 $line->localtax2_tx,
711 $line->qty,
712 $line->fk_product,
713 'HT',
714 (!empty($line->info_bits) ? $line->info_bits : ''),
715 $line->product_type,
716 $line->remise_percent,
717 0,
718 $line->date_start,
719 $line->date_end,
720 $line->array_options,
721 $line->fk_unit,
722 $line->multicurrency_subprice,
723 $line->ref_supplier
724 );
725 } else {
726 $this->error = $this->db->lasterror();
727 $this->db->rollback();
728 return -5;
729 }
730 }
731 }
732
733 /*
734 * Insert lines of template invoices
735 */
736 if (! $error && $this->fac_rec > 0 && $_facrec instanceof FactureFournisseurRec) {
737 foreach ($_facrec->lines as $i => $val) {
738 $product_type = $_facrec->lines[$i]->product_type;
739 if ($_facrec->lines[$i]->fk_product) {
740 $prod = new Product($this->db);
741 $res = $prod->fetch($_facrec->lines[$i]->fk_product);
742 }
743
744 // For line from template invoice, we use data from template invoice
745 /*
746 $tva_tx = get_default_tva($mysoc,$soc,$prod->id);
747 $tva_npr = get_default_npr($mysoc,$soc,$prod->id);
748 if (empty($tva_tx)) $tva_npr=0;
749 $localtax1_tx=get_localtax($tva_tx,1,$soc,$mysoc,$tva_npr);
750 $localtax2_tx=get_localtax($tva_tx,2,$soc,$mysoc,$tva_npr);
751 */
752 $tva_tx = $_facrec->lines[$i]->tva_tx . ($_facrec->lines[$i]->vat_src_code ? '(' . $_facrec->lines[$i]->vat_src_code . ')' : '');
753 $tva_npr = $_facrec->lines[$i]->info_bits;
754 if (empty($tva_tx)) {
755 $tva_npr = 0;
756 }
757 $localtax1_tx = $_facrec->lines[$i]->localtax1_tx;
758 $localtax2_tx = $_facrec->lines[$i]->localtax2_tx;
759
760 // $fk_product_fournisseur_price not used and does not exist on line
761 // $fk_product_fournisseur_price = empty($_facrec->lines[$i]->fk_product_fournisseur_price) ? null : $_facrec->lines[$i]->fk_product_fournisseur_price;
762 $buyprice = empty($_facrec->lines[$i]->buyprice) ? 0 : $_facrec->lines[$i]->buyprice;
763
764 // If buyprice not defined from template invoice, we try to guess the best value
765 if (! $buyprice && $_facrec->lines[$i]->fk_product > 0) {
766 require_once DOL_DOCUMENT_ROOT . '/fourn/class/fournisseur.product.class.php';
767 $producttmp = new ProductFournisseur($this->db);
768 $producttmp->fetch($_facrec->lines[$i]->fk_product);
769
770 // If margin module defined on costprice, we try the costprice
771 // If not defined or if module margin defined and pmp and stock module enabled, we try pmp price
772 // else we get the best supplier price
773 if (getDolGlobalString('MARGIN_TYPE') == 'costprice' && !empty($producttmp->cost_price)) {
774 $buyprice = $producttmp->cost_price;
775 } elseif (isModEnabled('stock') && (getDolGlobalString('MARGIN_TYPE') == 'costprice' || getDolGlobalString('MARGIN_TYPE') == 'pmp') && !empty($producttmp->pmp)) {
776 $buyprice = $producttmp->pmp;
777 } else {
778 if ($producttmp->find_min_price_product_fournisseur($_facrec->lines[$i]->fk_product) > 0) {
779 if ($producttmp->product_fourn_price_id > 0) {
780 $buyprice = price2num($producttmp->fourn_unitprice * (1 - $producttmp->fourn_remise_percent / 100) + $producttmp->fourn_remise, 'MU');
781 }
782 }
783 }
784 }
785
786 $result_insert = $this->addline(
787 $_facrec->lines[$i]->desc ? $_facrec->lines[$i]->desc : $_facrec->lines[$i]->description,
788 $_facrec->lines[$i]->pu_ht,
789 $tva_tx,
790 $localtax1_tx,
791 $localtax2_tx,
792 $_facrec->lines[$i]->qty,
793 $_facrec->lines[$i]->fk_product,
794 $_facrec->lines[$i]->remise_percent,
795 ($_facrec->lines[$i]->date_start == 1 && $this->date) ? $this->date : '',
796 ($_facrec->lines[$i]->date_end == 1 && $previousdaynextdatewhen) ? $previousdaynextdatewhen : '',
797 0,
798 $_facrec->lines[$i]->info_bits,
799 'HT',
800 $product_type,
801 $_facrec->lines[$i]->rang,
802 0,
803 $_facrec->lines[$i]->array_options,
804 $_facrec->lines[$i]->fk_unit,
805 0,
806 0,
807 $_facrec->lines[$i]->ref_supplier,
808 $_facrec->lines[$i]->special_code,
809 0,
810 0
811 );
812 if ($result_insert < 0) {
813 $error++;
814 $this->error = $this->db->error();
815 break;
816 }
817 }
818 }
819
820
821 // Update total price
822 $result = $this->update_price(1);
823 if ($result > 0) {
824 // Actions on extra fields
825 if (!$error) {
826 $result = $this->insertExtraFields(); // This also set $this->error or $this->errors if errors are found
827 if ($result < 0) {
828 $error++;
829 }
830 }
831
832 if (!$error) {
833 // Call trigger
834 $result = $this->call_trigger('BILL_SUPPLIER_CREATE', $user);
835 if ($result < 0) {
836 $error++;
837 }
838 // End call triggers
839 }
840
841 if (!$error) {
842 $this->db->commit();
843 return $this->id;
844 } else {
845 $this->db->rollback();
846 return -4;
847 }
848 } else {
849 $this->error = $langs->trans('FailedToUpdatePrice');
850 $this->db->rollback();
851 return -3;
852 }
853 } else {
854 if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
855 $this->error = $langs->trans('ErrorRefAlreadyExists');
856 $this->db->rollback();
857 return -1;
858 } else {
859 $this->error = $this->db->lasterror();
860 $this->db->rollback();
861 return -2;
862 }
863 }
864 }
865
874 public function fetch($id = 0, $ref = '', $ref_ext = '')
875 {
876 if (empty($id) && empty($ref) && empty($ref_ext)) {
877 return -1;
878 }
879
880 $sql = "SELECT";
881 $sql .= " t.rowid,";
882 $sql .= " t.ref,";
883 $sql .= " t.ref_supplier,";
884 $sql .= " t.ref_ext,";
885 $sql .= " t.entity,";
886 $sql .= " t.type,";
887 $sql .= " t.subtype,";
888 $sql .= " t.fk_soc,";
889 $sql .= " t.datec,";
890 $sql .= " t.datef,";
891 $sql .= " t.tms as datem,";
892 $sql .= " t.libelle as label,";
893 $sql .= " t.paye as paid,";
894 $sql .= " t.close_code,";
895 $sql .= " t.close_note,";
896 $sql .= " t.tva,";
897 $sql .= " t.localtax1,";
898 $sql .= " t.localtax2,";
899 $sql .= " t.total_ht,";
900 $sql .= " t.total_tva,";
901 $sql .= " t.total_ttc,";
902 $sql .= " t.fk_statut as status,";
903 $sql .= " t.fk_user_author,";
904 $sql .= " t.fk_user_valid,";
905 $sql .= " t.fk_facture_source,";
906 $sql .= " t.vat_reverse_charge,";
907 $sql .= " t.fk_fac_rec_source,";
908 $sql .= " t.fk_projet as fk_project,";
909 $sql .= " t.fk_cond_reglement,";
910 $sql .= " t.fk_account,";
911 $sql .= " t.fk_mode_reglement,";
912 $sql .= " t.date_lim_reglement,";
913 $sql .= " t.note_private,";
914 $sql .= " t.note_public,";
915 $sql .= " t.model_pdf,";
916 $sql .= " t.last_main_doc,";
917 $sql .= " t.import_key,";
918 $sql .= " t.extraparams,";
919 $sql .= " cr.code as cond_reglement_code, cr.libelle as cond_reglement_label, cr.libelle_facture as cond_reglement_doc,";
920 $sql .= " p.code as mode_reglement_code, p.libelle as mode_reglement_label,";
921 $sql .= ' s.nom as socnom, s.rowid as socid,';
922 $sql .= ' t.fk_incoterms, t.location_incoterms,';
923 $sql .= " i.libelle as label_incoterms,";
924 $sql .= ' t.fk_transport_mode,';
925 $sql .= ' t.fk_multicurrency, t.multicurrency_code, t.multicurrency_tx, t.multicurrency_total_ht, t.multicurrency_total_tva, t.multicurrency_total_ttc';
926 $sql .= ' FROM '.MAIN_DB_PREFIX.'facture_fourn as t';
927 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON (t.fk_soc = s.rowid)";
928 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_payment_term as cr ON t.fk_cond_reglement = cr.rowid";
929 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_paiement as p ON t.fk_mode_reglement = p.id";
930 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON t.fk_incoterms = i.rowid';
931 if ($id) {
932 $sql .= " WHERE t.rowid = ".((int) $id);
933 } else {
934 $sql .= ' WHERE t.entity IN ('.getEntity('supplier_invoice').')'; // Don't use entity if you use rowid
935 if ($ref) {
936 $sql .= " AND t.ref = '".$this->db->escape($ref)."'";
937 }
938 if ($ref_ext) {
939 $sql .= " AND t.ref_ext = '".$this->db->escape($ref_ext)."'";
940 }
941 }
942
943 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
944 $resql = $this->db->query($sql);
945 if ($resql) {
946 if ($this->db->num_rows($resql)) {
947 $obj = $this->db->fetch_object($resql);
948
949 $this->id = $obj->rowid;
950 $this->ref = $obj->ref ? $obj->ref : $obj->rowid; // We take rowid if ref is empty for backward compatibility
951
952 $this->ref_supplier = $obj->ref_supplier;
953 $this->ref_ext = $obj->ref_ext;
954 $this->entity = $obj->entity;
955 $this->type = empty($obj->type) ? self::TYPE_STANDARD : $obj->type;
956 $this->subtype = $obj->subtype;
957 $this->socid = $obj->fk_soc;
958 $this->date = $this->db->jdate($obj->datef);
959 $this->date_creation = $this->db->jdate($obj->datec);
960 $this->datec = $this->db->jdate($obj->datec);
961 $this->date_modification = $this->db->jdate($obj->datem);
962 $this->tms = $this->db->jdate($obj->datem);
963 $this->libelle = $obj->label; // deprecated
964 $this->label = $obj->label;
965 $this->paye = $obj->paid;
966 $this->paid = $obj->paid;
967 $this->close_code = $obj->close_code;
968 $this->close_note = $obj->close_note;
969 $this->total_localtax1 = $obj->localtax1;
970 $this->total_localtax2 = $obj->localtax2;
971 $this->total_ht = $obj->total_ht;
972 $this->total_tva = $obj->total_tva;
973 $this->total_ttc = $obj->total_ttc;
974 $this->status = $obj->status;
975 $this->statut = $obj->status; // For backward compatibility
976 $this->fk_statut = $obj->status; // For backward compatibility
977 $this->user_creation_id = $obj->fk_user_author;
978 $this->author = $obj->fk_user_author; // deprecated
979 $this->user_validation_id = $obj->fk_user_valid;
980 $this->fk_user_valid = $obj->fk_user_valid;
981 $this->fk_facture_source = $obj->fk_facture_source;
982 $this->vat_reverse_charge = empty($obj->vat_reverse_charge) ? 0 : 1;
983 $this->fk_fac_rec_source = $obj->fk_fac_rec_source;
984 $this->fk_project = $obj->fk_project;
985 $this->cond_reglement_id = $obj->fk_cond_reglement;
986 $this->cond_reglement_code = $obj->cond_reglement_code;
987 $this->cond_reglement = $obj->cond_reglement_label; // deprecated
988 $this->cond_reglement_label = $obj->cond_reglement_label;
989 $this->cond_reglement_doc = $obj->cond_reglement_doc;
990 $this->fk_account = $obj->fk_account;
991 $this->mode_reglement_id = $obj->fk_mode_reglement;
992 $this->mode_reglement_code = $obj->mode_reglement_code;
993 $this->mode_reglement = $obj->mode_reglement_label;
994 $this->date_echeance = $this->db->jdate($obj->date_lim_reglement);
995 $this->note = $obj->note_private; // deprecated
996 $this->note_private = $obj->note_private;
997 $this->note_public = $obj->note_public;
998 $this->model_pdf = $obj->model_pdf;
999 $this->last_main_doc = $obj->last_main_doc;
1000 $this->import_key = $obj->import_key;
1001
1002 //Incoterms
1003 $this->fk_incoterms = $obj->fk_incoterms;
1004 $this->location_incoterms = $obj->location_incoterms;
1005 $this->label_incoterms = $obj->label_incoterms;
1006 $this->transport_mode_id = $obj->fk_transport_mode;
1007
1008 // Multicurrency
1009 $this->fk_multicurrency = $obj->fk_multicurrency;
1010 $this->multicurrency_code = $obj->multicurrency_code;
1011 $this->multicurrency_tx = $obj->multicurrency_tx;
1012 $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
1013 $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
1014 $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1015
1016 $this->extraparams = isset($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
1017
1018 $this->socid = $obj->socid;
1019
1020 $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
1021
1022 // Retrieve all extrafield
1023 // fetch optionals attributes and labels
1024 $this->fetch_optionals();
1025
1026 $result = $this->fetch_lines();
1027 if ($result < 0) {
1028 $this->error = $this->db->lasterror();
1029 return -3;
1030 }
1031 } else {
1032 $this->error = 'Bill with id '.$id.' not found';
1033 dol_syslog(get_class($this).'::fetch '.$this->error);
1034 return 0;
1035 }
1036
1037 $this->db->free($resql);
1038 return 1;
1039 } else {
1040 $this->error = "Error ".$this->db->lasterror();
1041 return -1;
1042 }
1043 }
1044
1045
1046 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1052 public function fetch_lines()
1053 {
1054 // phpcs:enable
1055 $this->lines = array();
1056
1057 $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';
1058 $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';
1059 $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, f.extraparams';
1060 $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';
1061 $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';
1062 $sql .= ' FROM '.MAIN_DB_PREFIX.'facture_fourn_det as f';
1063 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON f.fk_product = p.rowid';
1064 $sql .= ' WHERE fk_facture_fourn='.((int) $this->id);
1065 $sql .= ' ORDER BY f.rang, f.rowid';
1066
1067 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1068
1069 $resql_rows = $this->db->query($sql);
1070 if ($resql_rows) {
1071 $num_rows = $this->db->num_rows($resql_rows);
1072 if ($num_rows) {
1073 $i = 0;
1074 while ($i < $num_rows) {
1075 $obj = $this->db->fetch_object($resql_rows);
1076
1077 $line = new SupplierInvoiceLine($this->db);
1078
1079 $line->id = $obj->rowid;
1080 $line->rowid = $obj->rowid;
1081
1082 $line->description = $obj->line_desc;
1083 $line->desc = $obj->line_desc;
1084 $line->date_start = $this->db->jdate($obj->date_start);
1085 $line->date_end = $this->db->jdate($obj->date_end);
1086
1087 $line->product_ref = $obj->product_ref;
1088 $line->ref = $obj->product_ref;
1089 $line->ref_supplier = $obj->ref_supplier;
1090 $line->libelle = $obj->label;
1091 $line->label = $obj->label;
1092 $line->product_barcode = $obj->product_barcode;
1093 $line->product_desc = $obj->product_desc;
1094 $line->subprice = $obj->pu_ht;
1095 $line->pu_ht = $obj->pu_ht; // deprecated
1096 $line->subprice_ttc = $obj->pu_ttc;
1097 $line->pu_ttc = $obj->pu_ttc; // deprcated
1098 $line->vat_src_code = $obj->vat_src_code;
1099 $line->tva_tx = $obj->tva_tx;
1100 $line->localtax1_tx = $obj->localtax1_tx;
1101 $line->localtax2_tx = $obj->localtax2_tx;
1102 $line->localtax1_type = $obj->localtax1_type;
1103 $line->localtax2_type = $obj->localtax2_type;
1104 $line->qty = $obj->qty;
1105 $line->remise_percent = $obj->remise_percent;
1106 $line->fk_remise_except = $obj->fk_remise_except;
1107 //$line->tva = $obj->total_tva; // deprecated
1108 $line->total_ht = $obj->total_ht;
1109 $line->total_ttc = $obj->total_ttc;
1110 $line->total_tva = $obj->total_tva;
1111 $line->total_localtax1 = $obj->total_localtax1;
1112 $line->total_localtax2 = $obj->total_localtax2;
1113 $line->fk_facture_fourn = $obj->fk_facture_fourn;
1114 $line->fk_product = $obj->fk_product;
1115 $line->product_type = $obj->product_type;
1116 $line->product_label = $obj->label;
1117 $line->info_bits = $obj->info_bits;
1118 $line->fk_parent_line = $obj->fk_parent_line;
1119 $line->special_code = $obj->special_code;
1120 $line->rang = $obj->rang;
1121 $line->fk_unit = $obj->fk_unit;
1122
1123 $line->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
1124
1125 // Accountancy
1126 $line->fk_code_ventilation = $obj->fk_code_ventilation;
1127
1128 // Multicurrency
1129 $line->fk_multicurrency = $obj->fk_multicurrency;
1130 $line->multicurrency_code = $obj->multicurrency_code;
1131 $line->multicurrency_subprice = $obj->multicurrency_subprice;
1132 $line->multicurrency_total_ht = $obj->multicurrency_total_ht;
1133 $line->multicurrency_total_tva = $obj->multicurrency_total_tva;
1134 $line->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1135
1136 // Extra fields
1137 $line->fetch_optionals();
1138
1139 $this->lines[$i] = $line;
1140
1141 $i++;
1142 }
1143 }
1144 $this->db->free($resql_rows);
1145 return 1;
1146 } else {
1147 $this->error = $this->db->error();
1148 dol_syslog(get_class($this)."::fetch_lines - No lines:{$this->error} Error:{$this->error}", LOG_DEBUG);
1149 return -3;
1150 }
1151 }
1152
1153
1161 public function update($user = null, $notrigger = 0)
1162 {
1163 global $langs;
1164 $error = 0;
1165
1166 // Clean parameters
1167 if (empty($this->type)) {
1168 $this->type = self::TYPE_STANDARD;
1169 }
1170 if (isset($this->ref)) {
1171 $this->ref = trim($this->ref);
1172 }
1173 if (isset($this->ref_supplier)) {
1174 $this->ref_supplier = trim($this->ref_supplier);
1175 }
1176 if (isset($this->ref_ext)) {
1177 $this->ref_ext = trim($this->ref_ext);
1178 }
1179 if (isset($this->entity)) {
1180 $this->entity = (int) $this->entity;
1181 }
1182 if (isset($this->type)) {
1183 $this->type = (int) $this->type;
1184 }
1185 if (isset($this->subtype)) {
1186 $this->subtype = (int) $this->subtype;
1187 }
1188 if (isset($this->socid)) {
1189 $this->socid = (int) $this->socid;
1190 }
1191 if (isset($this->label)) {
1192 $this->label = trim($this->label);
1193 }
1194 if (isset($this->paid)) {
1195 $this->paid = (int) (bool) $this->paid;
1196 $this->paye = $this->paid; // deprecated
1197 } elseif (isset($this->paye)) {
1198 $this->paid = (int) (bool) $this->paye;
1199 $this->paye = $this->paid; // deprecared
1200 }
1201 if (isset($this->close_code)) {
1202 $this->close_code = trim($this->close_code);
1203 }
1204 if (isset($this->close_note)) {
1205 $this->close_note = trim($this->close_note);
1206 }
1207 if (empty($this->total_ht)) {
1208 $this->total_ht = 0;
1209 }
1210 if (empty($this->total_tva)) {
1211 $this->total_tva = 0;
1212 }
1213 if (empty($this->total_localtax1)) {
1214 $this->total_localtax1 = 0;
1215 }
1216 if (empty($this->total_localtax2)) {
1217 $this->total_localtax2 = 0;
1218 }
1219 if (isset($this->total_ttc)) {
1220 $this->total_ttc = (float) $this->total_ttc;
1221 }
1222 if (isset($this->status)) {
1223 $this->status = (int) $this->status;
1224 $this->statut = $this->status;
1225 } elseif (isset($this->statut)) {
1226 $this->status = (int) $this->statut;
1227 $this->statut = $this->status;
1228 }
1229 if (isset($this->author)) { // TODO: user_creation_id?
1230 $this->author = (int) $this->author;
1231 }
1232 if (isset($this->fk_user_valid)) {
1233 $this->fk_user_valid = (int) $this->fk_user_valid;
1234 }
1235 if (isset($this->fk_facture_source)) {
1236 $this->fk_facture_source = (int) $this->fk_facture_source;
1237 }
1238 if (isset($this->fk_project)) {
1239 if (empty($this->fk_project)) {
1240 $this->fk_project = 0;
1241 } else {
1242 $this->fk_project = (int) $this->fk_project;
1243 }
1244 }
1245 if (isset($this->mode_reglement_id)) {
1246 $this->mode_reglement_id = (int) $this->mode_reglement_id;
1247 }
1248 if (isset($this->cond_reglement_id)) {
1249 $this->cond_reglement_id = (int) $this->cond_reglement_id;
1250 }
1251 if (isset($this->note_private)) {
1252 $this->note_private = trim($this->note_private);
1253 $this->note = $this->note_private;
1254 }
1255 if (isset($this->note_public)) {
1256 $this->note_public = trim($this->note_public);
1257 }
1258 if (isset($this->model_pdf)) {
1259 $this->model_pdf = trim($this->model_pdf);
1260 }
1261 if (isset($this->import_key)) {
1262 $this->import_key = trim($this->import_key);
1263 }
1264
1265
1266 // Check parameters
1267 // Put here code to add control on parameters values
1268
1269 // Update request
1270 $sql = "UPDATE ".MAIN_DB_PREFIX."facture_fourn SET";
1271 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1272 $sql .= " ref_supplier=".(isset($this->ref_supplier) ? "'".$this->db->escape($this->ref_supplier)."'" : "null").",";
1273 $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1274 $sql .= " entity=".(isset($this->entity) ? ((int) $this->entity) : "null").",";
1275 $sql .= " type=".(isset($this->type) ? ((int) $this->type) : "null").",";
1276 $sql .= " subtype=".(isset($this->subtype) ? (int) $this->subtype : "null").",";
1277 $sql .= " fk_soc=".(isset($this->socid) ? ((int) $this->socid) : "null").",";
1278 $sql .= " datec=".(dol_strlen((string) $this->datec) != 0 ? "'".$this->db->idate($this->datec)."'" : 'null').",";
1279 $sql .= " datef=".(dol_strlen((string) $this->date) != 0 ? "'".$this->db->idate($this->date)."'" : 'null').",";
1280 if (dol_strlen((string) $this->date_modification) != 0) {
1281 $sql .= " tms=".(dol_strlen((string) $this->date_modification) != 0 ? "'".$this->db->idate($this->date_modification)."'" : 'null').",";
1282 } elseif (dol_strlen((string) $this->tms) != 0) { // For backward compatibility
1283 $sql .= " tms=".(dol_strlen((string) $this->tms) != 0 ? "'".$this->db->idate($this->tms)."'" : 'null').",";
1284 }
1285 $sql .= " libelle=".(isset($this->label) ? "'".$this->db->escape($this->label)."'" : "null").",";
1286 $sql .= " paye=".(isset($this->paid) ? ((int) $this->paid) : "0").",";
1287 $sql .= " close_code=".(isset($this->close_code) ? "'".$this->db->escape($this->close_code)."'" : "null").",";
1288 $sql .= " close_note=".(isset($this->close_note) ? "'".$this->db->escape($this->close_note)."'" : "null").",";
1289 $sql .= " localtax1=".(isset($this->total_localtax1) ? ((float) $this->total_localtax1) : "null").",";
1290 $sql .= " localtax2=".(isset($this->total_localtax2) ? ((float) $this->total_localtax2) : "null").",";
1291 $sql .= " total_ht=".(isset($this->total_ht) ? ((float) $this->total_ht) : "null").",";
1292 $sql .= " total_tva=".(isset($this->total_tva) ? ((float) $this->total_tva) : "null").",";
1293 $sql .= " total_ttc=".(isset($this->total_ttc) ? ((float) $this->total_ttc) : "null").",";
1294 $sql .= " fk_statut=".(isset($this->status) ? ((int) $this->status) : (isset($this->statut) ? ((int) $this->statut) : "null")).",";
1295 $sql .= " fk_user_author=".(isset($this->author) ? ((int) $this->author) : "null").",";
1296 $sql .= " fk_user_valid=".(isset($this->fk_user_valid) ? ((int) $this->fk_user_valid) : "null").",";
1297 $sql .= " fk_facture_source=".($this->fk_facture_source ? ((int) $this->fk_facture_source) : "null").",";
1298 $sql .= " vat_reverse_charge = ".($this->vat_reverse_charge != '' ? ((int) $this->vat_reverse_charge) : 0).",";
1299 $sql .= " fk_projet=".(!empty($this->fk_project) ? ((int) $this->fk_project) : "null").",";
1300 $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? ((int) $this->mode_reglement_id) : "null").",";
1301 $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? ((int) $this->cond_reglement_id) : "null").",";
1302 $sql .= " date_lim_reglement=".(dol_strlen((string) $this->date_echeance) != 0 ? "'".$this->db->idate($this->date_echeance)."'" : 'null').",";
1303 $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1304 $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1305 $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1306 $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null");
1307 $sql .= " WHERE rowid=".((int) $this->id);
1308
1309 $this->db->begin();
1310
1311 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1312 $resql = $this->db->query($sql);
1313
1314 if (!$resql) {
1315 $error++;
1316
1317 if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1318 $this->errors[] = $langs->trans('ErrorRefAlreadyExists');
1319 } else {
1320 $this->errors[] = "Error ".$this->db->lasterror();
1321 }
1322 }
1323
1324 if (!$error) {
1325 $result = $this->insertExtraFields();
1326 if ($result < 0) {
1327 $error++;
1328 }
1329 }
1330
1331 if (!$error) {
1332 if (!$notrigger) {
1333 // Call trigger
1334 $result = $this->call_trigger('BILL_SUPPLIER_MODIFY', $user);
1335 if ($result < 0) {
1336 $error++;
1337 }
1338 // End call triggers
1339 }
1340 }
1341
1342 // Commit or rollback
1343 if ($error) {
1344 foreach ($this->errors as $errmsg) {
1345 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1346 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1347 }
1348 $this->db->rollback();
1349 return -1 * $error;
1350 } else {
1351 $this->db->commit();
1352 return 1;
1353 }
1354 }
1355
1356 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1363 public function insert_discount($idremise)
1364 {
1365 // phpcs:enable
1366 global $conf, $langs;
1367
1368 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1369 include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
1370
1371 $this->db->begin();
1372
1373 $remise = new DiscountAbsolute($this->db);
1374 $result = $remise->fetch($idremise);
1375
1376 if ($result > 0) {
1377 if ($remise->fk_invoice_supplier) { // Protection against multiple submission
1378 $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
1379 $this->db->rollback();
1380 return -5;
1381 }
1382
1383 $facligne = new SupplierInvoiceLine($this->db);
1384 $facligne->fk_facture_fourn = $this->id;
1385 $facligne->fk_remise_except = $remise->id;
1386 $facligne->desc = $remise->description; // Description ligne
1387 $facligne->vat_src_code = $remise->vat_src_code;
1388 $facligne->tva_tx = $remise->tva_tx;
1389 $facligne->subprice = -(float) $remise->amount_ht;
1390 $facligne->fk_product = 0; // Id produit predefini
1391 $facligne->product_type = 0;
1392 $facligne->qty = 1;
1393 $facligne->remise_percent = 0;
1394 $facligne->rang = -1;
1395 $facligne->info_bits = 2;
1396
1397 if (getDolGlobalString('MAIN_ADD_LINE_AT_POSITION')) {
1398 $facligne->rang = 1;
1399 $linecount = count($this->lines);
1400 for ($ii = 1; $ii <= $linecount; $ii++) {
1401 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
1402 }
1403 }
1404
1405 // Get buy/cost price of invoice that is source of discount
1406 if ($remise->fk_invoice_supplier_source > 0) {
1407 $srcinvoice = new FactureFournisseur($this->db);
1408 $srcinvoice->fetch($remise->fk_invoice_supplier_source);
1409 $totalcostpriceofinvoice = 0;
1410 include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmargin.class.php'; // TODO Move this into commonobject
1411 $formmargin = new FormMargin($this->db);
1412 $arraytmp = $formmargin->getMarginInfosArray($srcinvoice, false);
1413 $facligne->pa_ht = $arraytmp['pa_total'];
1414 }
1415
1416 $facligne->total_ht = -(float) $remise->amount_ht;
1417 $facligne->total_tva = -(float) $remise->amount_tva;
1418 $facligne->total_ttc = -(float) $remise->amount_ttc;
1419
1420 $facligne->multicurrency_subprice = -$remise->multicurrency_subprice;
1421 $facligne->multicurrency_total_ht = -$remise->multicurrency_total_ht;
1422 $facligne->multicurrency_total_tva = -$remise->multicurrency_total_tva;
1423 $facligne->multicurrency_total_ttc = -$remise->multicurrency_total_ttc;
1424
1425 $lineid = $facligne->insert();
1426 if ($lineid > 0) {
1427 $result = $this->update_price(1);
1428 if ($result > 0) {
1429 // Create link between discount and invoice line
1430 $result = $remise->link_to_invoice($lineid, 0);
1431 if ($result < 0) {
1432 $this->error = $remise->error;
1433 $this->db->rollback();
1434 return -4;
1435 }
1436
1437 $this->db->commit();
1438 return 1;
1439 } else {
1440 $this->error = $facligne->error;
1441 $this->db->rollback();
1442 return -1;
1443 }
1444 } else {
1445 $this->error = $facligne->error;
1446 $this->db->rollback();
1447 return -2;
1448 }
1449 } else {
1450 $this->db->rollback();
1451 return -3;
1452 }
1453 }
1454
1455
1463 public function delete(User $user, $notrigger = 0)
1464 {
1465 global $conf;
1466
1467 $rowid = $this->id;
1468
1469 dol_syslog("FactureFournisseur::delete rowid=".$rowid, LOG_DEBUG);
1470
1471 // TODO Test if there is at least on payment. If yes, refuse to delete.
1472
1473 $error = 0;
1474 $this->db->begin();
1475
1476 if (!$notrigger) {
1477 // Call trigger
1478 $result = $this->call_trigger('BILL_SUPPLIER_DELETE', $user);
1479 if ($result < 0) {
1480 $this->db->rollback();
1481 return -1;
1482 }
1483 // Fin appel triggers
1484 }
1485
1486 // If invoice was converted into a discount not yet consumed, we remove discount
1487 $sql = 'DELETE FROM '.MAIN_DB_PREFIX.'societe_remise_except';
1488 $sql .= ' WHERE fk_invoice_supplier_source = '.((int) $rowid);
1489 $sql .= ' AND fk_invoice_supplier_line IS NULL';
1490 $resql = $this->db->query($sql);
1491
1492 // If invoice has consumned discounts
1493 $this->fetch_lines();
1494 $list_rowid_det = array();
1495 foreach ($this->lines as $key => $invoiceline) {
1496 $list_rowid_det[] = $invoiceline->id;
1497 }
1498
1499 // Consumned discounts are freed
1500 if (count($list_rowid_det)) {
1501 $sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
1502 $sql .= ' SET fk_invoice_supplier = NULL, fk_invoice_supplier_line = NULL';
1503 $sql .= ' WHERE fk_invoice_supplier_line IN ('.$this->db->sanitize(implode(',', $list_rowid_det)).')';
1504
1505 dol_syslog(get_class($this)."::delete", LOG_DEBUG);
1506 if (!$this->db->query($sql)) {
1507 $error++;
1508 }
1509 }
1510
1511 if (!$error) {
1512 $main = MAIN_DB_PREFIX.'facture_fourn_det';
1513 $ef = $main."_extrafields";
1514 $sqlef = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM ".$main." WHERE fk_facture_fourn = ".((int) $rowid).")";
1515 $resqlef = $this->db->query($sqlef);
1516 $sql = 'DELETE FROM '.MAIN_DB_PREFIX.'facture_fourn_det WHERE fk_facture_fourn = '.((int) $rowid);
1517 dol_syslog(get_class($this)."::delete", LOG_DEBUG);
1518 $resql = $this->db->query($sql);
1519 if ($resqlef && $resql) {
1520 $sql = 'DELETE FROM '.MAIN_DB_PREFIX.'facture_fourn WHERE rowid = '.((int) $rowid);
1521 dol_syslog(get_class($this)."::delete", LOG_DEBUG);
1522 $resql2 = $this->db->query($sql);
1523 if (!$resql2) {
1524 $error++;
1525 }
1526 } else {
1527 $error++;
1528 }
1529 }
1530
1531 if (!$error) {
1532 // Delete linked object
1533 $res = $this->deleteObjectLinked();
1534 if ($res < 0) {
1535 $error++;
1536 }
1537 }
1538
1539 if (!$error) {
1540 // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
1541 $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1542 $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1543
1544 // We remove directory
1545 if ($conf->fournisseur->facture->dir_output) {
1546 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1547
1548 $ref = dol_sanitizeFileName($this->ref);
1549 $dir = $conf->fournisseur->facture->dir_output.'/'.get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier').$ref;
1550 $file = $dir."/".$ref.".pdf";
1551 if (file_exists($file)) {
1552 if (!dol_delete_file($file, 0, 0, 0, $this)) { // For triggers
1553 $this->error = 'ErrorFailToDeleteFile';
1554 $error++;
1555 }
1556 }
1557 if (file_exists($dir)) {
1558 $res = @dol_delete_dir_recursive($dir);
1559
1560 if (!$res) {
1561 $this->error = 'ErrorFailToDeleteDir';
1562 $error++;
1563 }
1564 }
1565 }
1566 }
1567
1568 // Remove linked categories.
1569 if (!$error) {
1570 $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_invoice";
1571 $sql .= " WHERE fk_invoice = ".((int) $this->id);
1572
1573 $result = $this->db->query($sql);
1574 if (!$result) {
1575 $error++;
1576 $this->errors[] = $this->db->lasterror();
1577 }
1578 }
1579
1580 // Remove extrafields
1581 if (!$error) {
1582 $result = $this->deleteExtraFields();
1583 if ($result < 0) {
1584 $error++;
1585 dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
1586 }
1587 }
1588
1589 if (!$error) {
1590 dol_syslog(get_class($this)."::delete $this->id by $user->id", LOG_DEBUG);
1591 $this->db->commit();
1592 return 1;
1593 } else {
1594 $this->error = $this->db->lasterror();
1595 $this->db->rollback();
1596 return -$error;
1597 }
1598 }
1599
1600
1601 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1612 public function set_paid($user, $close_code = '', $close_note = '')
1613 {
1614 // phpcs:enable
1615 dol_syslog(get_class($this)."::set_paid is deprecated, use setPaid instead", LOG_NOTICE);
1616 return $this->setPaid($user, $close_code, $close_note);
1617 }
1618
1627 public function setPaid($user, $close_code = '', $close_note = '')
1628 {
1629 $error = 0;
1630
1631 if ($this->paid != 1) {
1632 $this->db->begin();
1633
1634 $now = dol_now();
1635
1636 dol_syslog("FactureFournisseur::setPaid", LOG_DEBUG);
1637
1638 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture_fourn SET';
1639 $sql .= ' fk_statut = '.self::STATUS_CLOSED;
1640 if (!$close_code) {
1641 $sql .= ', paye=1';
1642 }
1643 if ($close_code) {
1644 $sql .= ", close_code='".$this->db->escape($close_code)."'";
1645 }
1646 if ($close_note) {
1647 $sql .= ", close_note='".$this->db->escape($close_note)."'";
1648 }
1649 $sql .= ', fk_user_closing = '.((int) $user->id);
1650 $sql .= ", date_closing = '".$this->db->idate($now)."'";
1651 $sql .= ' WHERE rowid = '.((int) $this->id);
1652
1653 $resql = $this->db->query($sql);
1654 if ($resql) {
1655 // Call trigger
1656 $result = $this->call_trigger('BILL_SUPPLIER_PAYED', $user);
1657 if ($result < 0) {
1658 $error++;
1659 }
1660 // End call triggers
1661 } else {
1662 $error++;
1663 $this->error = $this->db->error();
1664 dol_print_error($this->db);
1665 }
1666
1667 if (!$error) {
1668 $this->db->commit();
1669 return 1;
1670 } else {
1671 $this->db->rollback();
1672 return -1;
1673 }
1674 } else {
1675 return 0;
1676 }
1677 }
1678
1679 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1690 public function set_unpaid($user)
1691 {
1692 // phpcs:enable
1693 dol_syslog(get_class($this)."::set_unpaid is deprecated, use setUnpaid instead", LOG_NOTICE);
1694 return $this->setUnpaid($user);
1695 }
1696
1705 public function setUnpaid($user)
1706 {
1707 $error = 0;
1708
1709 $this->db->begin();
1710
1711 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture_fourn';
1712 $sql .= ' SET paye=0, fk_statut='.self::STATUS_VALIDATED.', close_code=null, close_note=null,';
1713 $sql .= ' date_closing=null,';
1714 $sql .= ' fk_user_closing=null';
1715 $sql .= ' WHERE rowid = '.((int) $this->id);
1716
1717 dol_syslog(get_class($this)."::set_unpaid", LOG_DEBUG);
1718 $resql = $this->db->query($sql);
1719 if ($resql) {
1720 // Call trigger
1721 $result = $this->call_trigger('BILL_SUPPLIER_UNPAYED', $user);
1722 if ($result < 0) {
1723 $error++;
1724 }
1725 // End call triggers
1726 } else {
1727 $error++;
1728 $this->error = $this->db->error();
1729 dol_print_error($this->db);
1730 }
1731
1732 if (!$error) {
1733 $this->db->commit();
1734 return 1;
1735 } else {
1736 $this->db->rollback();
1737 return -1;
1738 }
1739 }
1740
1751 public function setCanceled($user, $close_code = '', $close_note = '')
1752 {
1753 dol_syslog(get_class($this)."::setCanceled rowid=".((int) $this->id), LOG_DEBUG);
1754
1755 $this->db->begin();
1756
1757 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture_fourn SET';
1758 $sql .= ' fk_statut='.self::STATUS_ABANDONED;
1759 if ($close_code) {
1760 $sql .= ", close_code='".$this->db->escape($close_code)."'";
1761 }
1762 if ($close_note) {
1763 $sql .= ", close_note='".$this->db->escape($close_note)."'";
1764 }
1765 $sql .= " WHERE rowid = ".((int) $this->id);
1766
1767 $resql = $this->db->query($sql);
1768 if ($resql) {
1769 // Bound discounts are deducted from the invoice
1770 // as they have not been used since the invoice is abandoned.
1771 $sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
1772 $sql .= ' SET fk_invoice_supplier = NULL';
1773 $sql .= ' WHERE fk_invoice_supplier = '.((int) $this->id);
1774
1775 $resql = $this->db->query($sql);
1776 if ($resql) {
1777 // Call trigger
1778 $result = $this->call_trigger('BILL_SUPPLIER_CANCEL', $user);
1779 if ($result < 0) {
1780 $this->db->rollback();
1781 return -1;
1782 }
1783 // End call triggers
1784
1785 $this->db->commit();
1786 return 1;
1787 } else {
1788 $this->error = $this->db->error()." sql=".$sql;
1789 $this->db->rollback();
1790 return -1;
1791 }
1792 } else {
1793 $this->error = $this->db->error()." sql=".$sql;
1794 $this->db->rollback();
1795 return -2;
1796 }
1797 }
1798
1808 public function validate($user, $force_number = '', $idwarehouse = 0, $notrigger = 0)
1809 {
1810 global $mysoc, $conf, $langs;
1811
1812 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1813
1814 $now = dol_now();
1815
1816 $error = 0;
1817 dol_syslog(get_class($this).'::validate user='.$user->id.', force_number='.$force_number.', idwarehouse='.$idwarehouse);
1818
1819 // Force to have object complete for checks
1820 $this->fetch_thirdparty();
1821 $this->fetch_lines();
1822
1823 // Check parameters
1824 if ($this->status > self::STATUS_DRAFT) { // This is to avoid to validate twice (avoid errors on logs and stock management)
1825 dol_syslog(get_class($this)."::validate no draft status", LOG_WARNING);
1826 return 0;
1827 }
1828 if (preg_match('/^'.preg_quote($langs->trans("CopyOf").' ', '/').'/', $this->ref_supplier)) {
1829 $langs->load("errors");
1830 $this->error = $langs->trans("ErrorFieldFormat", $langs->transnoentities("RefSupplier")).'. '.$langs->trans('RemoveString', $langs->transnoentitiesnoconv("CopyOf"));
1831 return -1;
1832 }
1833 if (count($this->lines) <= 0) {
1834 $langs->load("errors");
1835 $this->error = $langs->trans("ErrorObjectMustHaveLinesToBeValidated", $this->ref);
1836 return -1;
1837 }
1838
1839 // Check for mandatory fields in thirdparty (defined into setup)
1840 if (!empty($this->thirdparty) && is_object($this->thirdparty)) {
1841 $array_to_check = array('IDPROF1', 'IDPROF2', 'IDPROF3', 'IDPROF4', 'IDPROF5', 'IDPROF6', 'EMAIL', 'ACCOUNTANCY_CODE_SUPPLIER');
1842 foreach ($array_to_check as $key) {
1843 $keymin = strtolower($key);
1844 if ($keymin == 'accountancy_code_supplier') {
1845 $keymin = 'code_compta_fournisseur';
1846 }
1847 if (!property_exists($this->thirdparty, $keymin)) {
1848 continue;
1849 }
1850 $vallabel = $this->thirdparty->$keymin;
1851
1852 $i = (int) preg_replace('/[^0-9]/', '', $key);
1853 if ($i > 0) {
1854 if ($this->thirdparty->isACompany()) {
1855 // Check for mandatory prof id (but only if country is other than ours)
1856 if ($mysoc->country_id > 0 && $this->thirdparty->country_id == $mysoc->country_id) {
1857 $idprof_mandatory = 'SOCIETE_'.$key.'_INVOICE_MANDATORY';
1858 if (!$vallabel && getDolGlobalString($idprof_mandatory)) {
1859 $langs->load("errors");
1860 $this->error = $langs->trans('ErrorProdIdIsMandatory', $langs->transcountry('ProfId'.$i, $this->thirdparty->country_code)).' ('.$langs->trans("ForbiddenBySetupRules").') ['.$langs->trans('Company').' : '.$this->thirdparty->name.']';
1861 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
1862 return -1;
1863 }
1864 }
1865 }
1866 } else {
1867 if ($key == 'EMAIL') {
1868 // Check for mandatory
1869 if (getDolGlobalString('SOCIETE_EMAIL_INVOICE_MANDATORY') && !isValidEmail($this->thirdparty->email)) {
1870 $langs->load("errors");
1871 $this->error = $langs->trans("ErrorBadEMail", $this->thirdparty->email).' ('.$langs->trans("ForbiddenBySetupRules").') ['.$langs->trans('Company').' : '.$this->thirdparty->name.']';
1872 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
1873 return -1;
1874 }
1875 } elseif ($key == 'ACCOUNTANCY_CODE_SUPPLIER') {
1876 // Check for mandatory
1877 if (getDolGlobalString('SOCIETE_ACCOUNTANCY_CODE_SUPPLIER_INVOICE_MANDATORY') && empty($this->thirdparty->code_compta_fournisseur)) {
1878 $langs->load("errors");
1879 $this->error = $langs->trans("ErrorAccountancyCodeSupplierIsMandatory", $this->thirdparty->name).' ('.$langs->trans("ForbiddenBySetupRules").')';
1880 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
1881 return -1;
1882 }
1883 }
1884 }
1885 }
1886 }
1887
1888 $this->db->begin();
1889
1890 // Define new ref
1891 if ($force_number) {
1892 $num = $force_number;
1893 } elseif (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
1894 $num = $this->getNextNumRef($this->thirdparty);
1895 } else {
1896 $num = (string) $this->ref;
1897 }
1898 $this->newref = dol_sanitizeFileName($num);
1899
1900 $sql = "UPDATE ".MAIN_DB_PREFIX."facture_fourn";
1901 $sql .= " SET ref='".$this->db->escape($num)."', fk_statut = 1, fk_user_valid = ".((int) $user->id).", date_valid = '".$this->db->idate($now)."'";
1902 $sql .= " WHERE rowid = ".((int) $this->id);
1903
1904 dol_syslog(get_class($this)."::validate", LOG_DEBUG);
1905 $resql = $this->db->query($sql);
1906 if ($resql) {
1907 // Si on incrémente le produit principal et ses composants à la validation de facture fournisseur
1908 if (isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL')) {
1909 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
1910 $langs->load("agenda");
1911
1912 $cpt = count($this->lines);
1913 for ($i = 0; $i < $cpt; $i++) {
1914 if ($this->lines[$i]->fk_product > 0) {
1915 $mouvP = new MouvementStock($this->db);
1916 $mouvP->origin = &$this;
1917 $mouvP->setOrigin($this->element, $this->id);
1918 // We increase stock for product
1919 $up_ht_disc = $this->lines[$i]->subprice;
1920 if (!empty($this->lines[$i]->remise_percent) && !getDolGlobalString('STOCK_EXCLUDE_DISCOUNT_FOR_PMP')) {
1921 $up_ht_disc = price2num($up_ht_disc * (100 - $this->lines[$i]->remise_percent) / 100, 'MU');
1922 }
1924 $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $up_ht_disc, $langs->trans("InvoiceValidatedInDolibarr", $num));
1925 } else {
1926 $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $up_ht_disc, $langs->trans("InvoiceValidatedInDolibarr", $num));
1927 }
1928 if ($result < 0) {
1929 $this->setErrorsFromObject($mouvP);
1930 return -2;
1931 }
1932 }
1933 }
1934 }
1935
1936 // Triggers call
1937 if (empty($notrigger)) {
1938 // Call trigger
1939 $result = $this->call_trigger('BILL_SUPPLIER_VALIDATE', $user);
1940 if ($result < 0) {
1941 $error++;
1942 }
1943 // End call triggers
1944 }
1945
1946 if (!$error) {
1947 $this->oldref = $this->ref;
1948
1949 // Rename directory if dir was a temporary ref
1950 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
1951 // Now we rename also files into index
1952 $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)."'";
1953 $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;
1954 $resql = $this->db->query($sql);
1955 if (!$resql) {
1956 $error++;
1957 $this->error = $this->db->lasterror();
1958 }
1959 $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)."'";
1960 $sql .= " WHERE filepath = 'fournisseur/facture/".get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier').$this->db->escape($this->ref)."' and entity = ".$conf->entity;
1961 $resql = $this->db->query($sql);
1962 if (!$resql) {
1963 $error++;
1964 $this->error = $this->db->lasterror();
1965 }
1966
1967 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
1968 $oldref = dol_sanitizeFileName($this->ref);
1969 $dirsource = $conf->fournisseur->facture->dir_output.'/'.get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier').$oldref;
1970 $dirdest = $conf->fournisseur->facture->dir_output.'/'.get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier').$this->newref;
1971 if (!$error && file_exists($dirsource)) {
1972 dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
1973
1974 if (@rename($dirsource, $dirdest)) {
1975 dol_syslog("Rename ok");
1976 // Rename docs starting with $oldref with $this->newref
1977 $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, '/'));
1978 foreach ($listoffiles as $fileentry) {
1979 $dirsource = $fileentry['name'];
1980 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $this->newref, $dirsource);
1981 $dirsource = $fileentry['path'].'/'.$dirsource;
1982 $dirdest = $fileentry['path'].'/'.$dirdest;
1983 @rename($dirsource, $dirdest);
1984 }
1985 }
1986 }
1987 }
1988 }
1989
1990 // Set new ref and define current status
1991 if (!$error) {
1992 $this->ref = $this->newref;
1993 $this->statut = self::STATUS_VALIDATED;
1995 //$this->date_validation=$now; this is stored into log table
1996 }
1997
1998 if (!$error) {
1999 $this->db->commit();
2000 return 1;
2001 } else {
2002 $this->db->rollback();
2003 return -1;
2004 }
2005 } else {
2006 $this->error = $this->db->error();
2007 $this->db->rollback();
2008 return -1;
2009 }
2010 }
2011
2020 public function setDraft($user, $idwarehouse = -1, $notrigger = 0)
2021 {
2022 // phpcs:enable
2023 global $conf, $langs;
2024
2025 $error = 0;
2026
2027 if ($this->status == self::STATUS_DRAFT) {
2028 dol_syslog(__METHOD__." already draft status", LOG_WARNING);
2029 return 0;
2030 }
2031
2032 dol_syslog(__METHOD__, LOG_DEBUG);
2033
2034 $this->db->begin();
2035
2036 $sql = "UPDATE ".MAIN_DB_PREFIX."facture_fourn";
2037 $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2038 $sql .= " WHERE rowid = ".((int) $this->id);
2039
2040 $result = $this->db->query($sql);
2041 if ($result) {
2042 $this->oldcopy = clone $this;
2043
2044 // Si on incremente le produit principal et ses composants a la validation de facture fournisseur, on decremente
2045 if ($result >= 0 && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL')) {
2046 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2047 $langs->load("agenda");
2048
2049 $cpt = count($this->lines);
2050 for ($i = 0; $i < $cpt; $i++) {
2051 if ($this->lines[$i]->fk_product > 0) {
2052 $mouvP = new MouvementStock($this->db);
2053 $mouvP->origin = &$this;
2054 $mouvP->setOrigin($this->element, $this->id);
2055 // We increase stock for product
2057 $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceBackToDraftInDolibarr", $this->ref));
2058 } else {
2059 $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceBackToDraftInDolibarr", $this->ref));
2060 }
2061 }
2062 }
2063 }
2064 // Triggers call
2065 if (empty($notrigger)) {
2066 // Call trigger
2067 $result = $this->call_trigger('BILL_SUPPLIER_UNVALIDATE', $user);
2068 if ($result < 0) {
2069 $error++;
2070 }
2071 // End call triggers
2072 }
2073 if ($error == 0) {
2074 $this->db->commit();
2075 return 1;
2076 } else {
2077 $this->db->rollback();
2078 return -1;
2079 }
2080 } else {
2081 $this->error = $this->db->error();
2082 $this->db->rollback();
2083 return -1;
2084 }
2085 }
2086
2087
2121 public function addline($desc, $pu, $txtva, $txlocaltax1, $txlocaltax2, $qty, $fk_product = 0, $remise_percent = 0, $date_start = 0, $date_end = 0, $fk_code_ventilation = 0, $info_bits = 0, $price_base_type = 'HT', $type = 0, $rang = -1, $notrigger = 0, $array_options = [], $fk_unit = null, $origin_id = 0, $pu_devise = 0, $ref_supplier = '', $special_code = 0, $fk_parent_line = 0, $fk_remise_except = 0)
2122 {
2123 global $langs, $mysoc;
2124
2125 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);
2126 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2127
2128 if ($this->status == self::STATUS_DRAFT) {
2129 // Clean parameters
2130 if (empty($remise_percent)) {
2131 $remise_percent = 0;
2132 }
2133 if (empty($qty)) {
2134 $qty = 0;
2135 }
2136 if (empty($info_bits)) {
2137 $info_bits = 0;
2138 }
2139 if (empty($rang)) {
2140 $rang = 0;
2141 }
2142 if (empty($fk_code_ventilation)) {
2143 $fk_code_ventilation = 0;
2144 }
2145 if (empty($txtva)) {
2146 $txtva = 0;
2147 }
2148 if (empty($txlocaltax1)) {
2149 $txlocaltax1 = 0;
2150 }
2151 if (empty($txlocaltax2)) {
2152 $txlocaltax2 = 0;
2153 }
2154
2155 $remise_percent = price2num($remise_percent);
2156 $qty = price2num($qty);
2157 $pu = price2num($pu);
2158 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
2159 $txtva = price2num($txtva); // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
2160 }
2161 $txlocaltax1 = price2num($txlocaltax1);
2162 $txlocaltax2 = price2num($txlocaltax2);
2163
2164 if ($date_start && $date_end && $date_start > $date_end) {
2165 $langs->load("errors");
2166 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
2167 return -1;
2168 }
2169
2170 $this->db->begin();
2171
2172 if ($fk_product > 0) {
2173 if (getDolGlobalInt('SUPPLIER_INVOICE_WITH_PREDEFINED_PRICES_ONLY') == 1) { // Not the common case
2174 // Check quantity is enough
2175 dol_syslog(get_class($this)."::addline we check supplier prices fk_product=".$fk_product." qty=".$qty." ref_supplier=".$ref_supplier);
2176 $prod = new ProductFournisseur($this->db);
2177 if ($prod->fetch($fk_product) > 0) {
2178 $product_type = $prod->type;
2179 $label = $prod->label;
2180 $fk_prod_fourn_price = 0;
2181
2182 // We use 'none' instead of $ref_supplier, because $ref_supplier may not exists anymore. So we will take the first supplier price ok.
2183 // If we want a dedicated supplier price, we must provide $fk_prod_fourn_price.
2184 $result = $prod->get_buyprice($fk_prod_fourn_price, (float) $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
2185 if ($result > 0) {
2186 if (empty($pu)) {
2187 $pu = $prod->fourn_pu; // Unit price supplier price set by get_buyprice
2188 }
2189 $ref_supplier = $prod->ref_supplier; // Ref supplier price set by get_buyprice
2190 // is remise percent not keyed but present for the product we add it
2191 if ($remise_percent == 0 && $prod->remise_percent != 0) {
2192 $remise_percent = $prod->remise_percent;
2193 }
2194 }
2195 if ($result == 0) { // If result == 0, we failed to found the supplier reference price
2196 $langs->load("errors");
2197 $this->error = "Ref ".$prod->ref." ".$langs->trans("ErrorQtyTooLowForThisSupplier");
2198 $this->db->rollback();
2199 dol_syslog(get_class($this)."::addline we did not found supplier price, so we can't guess unit price");
2200 //$pu = $prod->fourn_pu; // We do not overwrite unit price
2201 //$ref = $prod->ref_fourn; // We do not overwrite ref supplier price
2202 return -1;
2203 }
2204 if ($result == -1) {
2205 $langs->load("errors");
2206 $this->error = "Ref ".$prod->ref." ".$langs->trans("ErrorQtyTooLowForThisSupplier");
2207 $this->db->rollback();
2208 dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_DEBUG);
2209 return -1;
2210 }
2211 if ($result < -1) {
2212 $this->error = $prod->error;
2213 $this->db->rollback();
2214 dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_ERR);
2215 return -1;
2216 }
2217 } else {
2218 $this->error = $prod->error;
2219 $this->db->rollback();
2220 return -1;
2221 }
2222 }
2223 } else {
2224 $product_type = $type;
2225 }
2226
2227 if (isModEnabled("multicurrency") && $pu_devise > 0) {
2228 $pu = 0;
2229 }
2230
2231 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
2232
2233 // Clean vat code
2234 $reg = array();
2235 $vat_src_code = '';
2236 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
2237 $vat_src_code = $reg[1];
2238 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
2239 }
2240
2241 // Calcul du total TTC et de la TVA pour la ligne a partir de
2242 // qty, pu, remise_percent et txtva
2243 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2244 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2245
2246 $tabprice = calcul_price_total((float) $qty, $pu, $remise_percent, $txtva, (float) $txlocaltax1, (float) $txlocaltax2, 0, $price_base_type, $info_bits, $type, $this->thirdparty, $localtaxes_type, 100, $this->multicurrency_tx, $pu_devise);
2247 $total_ht = $tabprice[0];
2248 $total_tva = $tabprice[1];
2249 $total_ttc = $tabprice[2];
2250 $total_localtax1 = $tabprice[9];
2251 $total_localtax2 = $tabprice[10];
2252 $pu_ht = $tabprice[3];
2253
2254 // MultiCurrency
2255 $multicurrency_total_ht = $tabprice[16];
2256 $multicurrency_total_tva = $tabprice[17];
2257 $multicurrency_total_ttc = $tabprice[18];
2258 $pu_ht_devise = $tabprice[19];
2259
2260 // Check parameters
2261 if ($type < 0) {
2262 return -1;
2263 }
2264
2265 if ($rang < 0) {
2266 $rangmax = $this->line_max();
2267 $rang = $rangmax + 1;
2268 }
2269
2270 // Insert line
2271 $supplierinvoiceline = new SupplierInvoiceLine($this->db);
2272
2273 $supplierinvoiceline->context = $this->context;
2274
2275 $supplierinvoiceline->fk_facture_fourn = $this->id;
2276 //$supplierinvoiceline->label=$label; // deprecated
2277 $supplierinvoiceline->desc = $desc;
2278 $supplierinvoiceline->ref_supplier = $ref_supplier;
2279
2280 $supplierinvoiceline->qty = ($this->type == self::TYPE_CREDIT_NOTE ? abs((float) $qty) : (float) $qty); // For credit note, quantity is always positive and unit price negative
2281 $supplierinvoiceline->subprice = ($this->type == self::TYPE_CREDIT_NOTE ? -abs((float) $pu_ht) : (float) $pu_ht); // For credit note, unit price always negative, always positive otherwise
2282
2283 $supplierinvoiceline->vat_src_code = $vat_src_code;
2284 $supplierinvoiceline->tva_tx = $txtva;
2285 $supplierinvoiceline->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
2286 $supplierinvoiceline->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
2287 $supplierinvoiceline->localtax1_type = empty($localtaxes_type[0]) ? 0 : $localtaxes_type[0];
2288 $supplierinvoiceline->localtax2_type = empty($localtaxes_type[2]) ? 0 : $localtaxes_type[2];
2289
2290 $supplierinvoiceline->total_ht = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $total_ht) : (float) $total_ht); // For credit note and if qty is negative, total is negative
2291 $supplierinvoiceline->total_tva = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $total_tva) : (float) $total_tva); // For credit note and if qty is negative, total is negative
2292 $supplierinvoiceline->total_localtax1 = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $total_localtax1) : (float) $total_localtax1); // For credit note and if qty is negative, total is negative
2293 $supplierinvoiceline->total_localtax2 = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $total_localtax2) : (float) $total_localtax2); // For credit note and if qty is negative, total is negative
2294 $supplierinvoiceline->total_ttc = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $total_ttc) : (float) $total_ttc); // For credit note and if qty is negative, total is negative
2295
2296 $supplierinvoiceline->fk_product = $fk_product;
2297 $supplierinvoiceline->product_type = $type;
2298 $supplierinvoiceline->remise_percent = $remise_percent;
2299 $supplierinvoiceline->date_start = $date_start;
2300 $supplierinvoiceline->date_end = $date_end;
2301 $supplierinvoiceline->fk_code_ventilation = $fk_code_ventilation;
2302 $supplierinvoiceline->rang = $rang;
2303 $supplierinvoiceline->info_bits = $info_bits;
2304 $supplierinvoiceline->fk_remise_except = $fk_remise_except;
2305
2306
2307 $supplierinvoiceline->special_code = (int) $special_code;
2308 $supplierinvoiceline->fk_parent_line = $fk_parent_line;
2309 $supplierinvoiceline->origin = $this->origin;
2310 $supplierinvoiceline->origin_id = $origin_id;
2311 $supplierinvoiceline->fk_unit = $fk_unit;
2312
2313 // Multicurrency
2314 $supplierinvoiceline->fk_multicurrency = $this->fk_multicurrency;
2315 $supplierinvoiceline->multicurrency_code = $this->multicurrency_code;
2316 $supplierinvoiceline->multicurrency_subprice = ($this->type == self::TYPE_CREDIT_NOTE ? -abs((float) $pu_ht_devise) : (float) $pu_ht_devise); // For credit note, unit price always negative, always positive otherwise
2317
2318 $supplierinvoiceline->multicurrency_total_ht = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $multicurrency_total_ht) : (float) $multicurrency_total_ht); // For credit note and if qty is negative, total is negative
2319 $supplierinvoiceline->multicurrency_total_tva = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $multicurrency_total_tva) : (float) $multicurrency_total_tva); // For credit note and if qty is negative, total is negative
2320 $supplierinvoiceline->multicurrency_total_ttc = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $multicurrency_total_ttc) : (float) $multicurrency_total_ttc); // For credit note and if qty is negative, total is negative
2321
2322 if (is_array($array_options) && count($array_options) > 0) {
2323 $supplierinvoiceline->array_options = $array_options;
2324 }
2325
2326 $result = $supplierinvoiceline->insert($notrigger);
2327 if ($result > 0) {
2328 // Update denormalized fields at the order level
2329 $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.
2330
2331 if ($result > 0) {
2332 if (!isset($this->context['createfromclone'])) {
2333 if (!empty($fk_parent_line)) {
2334 // Always reorder if child line
2335 $this->line_order(true, 'DESC');
2336 } elseif ($rang > 0 && $rang <= count($this->lines)) {
2337 // Update all rank of all other lines starting from the same $ranktouse
2338 $linecount = count($this->lines);
2339 for ($ii = $rang; $ii <= $linecount; $ii++) {
2340 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
2341 }
2342 }
2343
2344 $this->lines[] = $supplierinvoiceline;
2345 }
2346
2347 $this->db->commit();
2348 return $supplierinvoiceline->id;
2349 } else {
2350 $this->error = $this->db->error();
2351 $this->db->rollback();
2352 return -1;
2353 }
2354 } else {
2355 $this->error = $supplierinvoiceline->error;
2356 $this->errors = $supplierinvoiceline->errors;
2357 $this->db->rollback();
2358 return -2;
2359 }
2360 } else {
2361 return 0;
2362 }
2363 }
2364
2390 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)
2391 {
2392 global $mysoc, $langs;
2393
2394 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);
2395 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2396
2397 $pu = price2num($pu);
2398 $qty = price2num($qty);
2399 $remise_percent = (float) price2num($remise_percent);
2400 $pu_devise = price2num($pu_devise);
2401
2402 // Check parameters
2403 //if (! is_numeric($pu) || ! is_numeric($qty)) return -1;
2404 if ($type < 0) {
2405 return -1;
2406 }
2407
2408 if ($date_start && $date_end && $date_start > $date_end) {
2409 $langs->load("errors");
2410 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
2411 return -1;
2412 }
2413
2414 // Clean parameters
2415 if (empty($vatrate)) {
2416 $vatrate = 0;
2417 }
2418 if (empty($txlocaltax1)) {
2419 $txlocaltax1 = 0;
2420 }
2421 if (empty($txlocaltax2)) {
2422 $txlocaltax2 = 0;
2423 }
2424
2425 $txlocaltax1 = (float) price2num($txlocaltax1);
2426 $txlocaltax2 = (float) price2num($txlocaltax2);
2427
2428 // Calcul du total TTC et de la TVA pour la ligne a partir de
2429 // qty, pu, remise_percent et txtva
2430 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2431 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2432
2433 $localtaxes_type = getLocalTaxesFromRate($vatrate, 0, $mysoc, $this->thirdparty);
2434
2435 $reg = array();
2436
2437 // Clean vat code
2438 $vat_src_code = '';
2439 if (preg_match('/\‍((.*)\‍)/', (string) $vatrate, $reg)) {
2440 $vat_src_code = $reg[1];
2441 $vatrate = preg_replace('/\s*\‍(.*\‍)/', '', (string) $vatrate); // Remove code into vatrate.
2442 }
2443
2444 $tabprice = calcul_price_total((float) $qty, (float) $pu, $remise_percent, $vatrate, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $this->thirdparty, $localtaxes_type, 100, $this->multicurrency_tx, (float) $pu_devise);
2445 $total_ht = $tabprice[0];
2446 $total_tva = $tabprice[1];
2447 $total_ttc = $tabprice[2];
2448 $pu_ht = $tabprice[3];
2449 $pu_tva = $tabprice[4];
2450 $pu_ttc = $tabprice[5];
2451 $total_localtax1 = $tabprice[9];
2452 $total_localtax2 = $tabprice[10];
2453
2454 // MultiCurrency
2455 $multicurrency_total_ht = $tabprice[16];
2456 $multicurrency_total_tva = $tabprice[17];
2457 $multicurrency_total_ttc = $tabprice[18];
2458 $pu_ht_devise = $tabprice[19];
2459
2460 if (empty($info_bits)) {
2461 $info_bits = 0;
2462 }
2463
2464 // Fetch current line from the database and then clone the object and set it in $oldline property
2465 $line = new SupplierInvoiceLine($this->db);
2466 $line->fetch($id);
2467 $line->fetch_optionals();
2468
2469 $staticline = clone $line;
2470
2471 if ($idproduct) {
2472 $product = new Product($this->db);
2473 $result = $product->fetch($idproduct);
2474 $product_type = $product->type;
2475 } else {
2476 $idproduct = $staticline->fk_product;
2477 $product_type = $type;
2478 }
2479
2480 $line->oldline = $staticline;
2481 $line->context = $this->context;
2482
2483 $line->description = $desc;
2484 $line->desc = $desc;
2485
2486 $line->qty = ($this->type == self::TYPE_CREDIT_NOTE ? abs((float) $qty) : (float) $qty); // For credit note, quantity is always positive and unit price negative
2487
2488 $line->subprice = ($this->type == self::TYPE_CREDIT_NOTE ? -abs((float) $pu_ht) : (float) $pu_ht); // For credit note, unit price always negative, always positive otherwise
2489 $line->pu_ht = $line->subprice; // deprecated
2490 $line->subprice_ttc = ($this->type == self::TYPE_CREDIT_NOTE ? -abs((float) $pu_ttc) : (float) $pu_ttc); // For credit note, unit price always negative, always positive otherwise
2491 $line->pu_ttc = $line->subprice_ttc; // deprecated
2492
2493 $line->remise_percent = $remise_percent;
2494 $line->ref_supplier = $ref_supplier;
2495
2496 $line->date_start = $date_start;
2497 $line->date_end = $date_end;
2498
2499 $line->vat_src_code = $vat_src_code;
2500 $line->tva_tx = $vatrate;
2501 $line->localtax1_tx = $txlocaltax1;
2502 $line->localtax2_tx = $txlocaltax2;
2503 $line->localtax1_type = empty($localtaxes_type[0]) ? 0 : $localtaxes_type[0];
2504 $line->localtax2_type = empty($localtaxes_type[2]) ? 0 : $localtaxes_type[2];
2505
2506 $line->total_ht = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $total_ht) : (float) $total_ht);
2507 $line->total_tva = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $total_tva) : (float) $total_tva);
2508 $line->total_localtax1 = (float) $total_localtax1;
2509 $line->total_localtax2 = (float) $total_localtax2;
2510 $line->total_ttc = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $total_ttc) : (float) $total_ttc);
2511
2512 $line->fk_product = $idproduct;
2513 $line->product_type = $product_type;
2514 $line->info_bits = $info_bits;
2515 $line->fk_unit = $fk_unit;
2516 $line->rang = $rang;
2517
2518 if (is_array($array_options) && count($array_options) > 0) {
2519 // We replace values in this->line->array_options only for entries defined into $array_options
2520 foreach ($array_options as $key => $value) {
2521 $line->array_options[$key] = $array_options[$key];
2522 }
2523 }
2524
2525 // Multicurrency
2526 $line->multicurrency_subprice = (float) $pu_ht_devise;
2527 $line->multicurrency_total_ht = (float) $multicurrency_total_ht;
2528 $line->multicurrency_total_tva = (float) $multicurrency_total_tva;
2529 $line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
2530
2531 $res = $line->update($notrigger);
2532
2533 if ($res < 1) {
2534 $this->setErrorsFromObject($line);
2535 } else {
2536 // Update total price into invoice record
2537 $res = $this->update_price(1, 'auto', 0, $this->thirdparty);
2538 }
2539
2540 return $res;
2541 }
2542
2550 public function deleteLine($rowid, $notrigger = 0)
2551 {
2552 if (!$rowid) {
2553 $rowid = $this->id;
2554 }
2555
2556 $this->db->begin();
2557
2558 // Free the discount linked to a line of invoice
2559 $sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
2560 $sql .= ' SET fk_invoice_supplier_line = NULL';
2561 $sql .= ' WHERE fk_invoice_supplier_line = '.((int) $rowid);
2562
2563 dol_syslog(get_class($this)."::deleteline", LOG_DEBUG);
2564 $result = $this->db->query($sql);
2565 if (!$result) {
2566 $this->error = $this->db->error();
2567 $this->db->rollback();
2568 return -2;
2569 }
2570
2571 $line = new SupplierInvoiceLine($this->db);
2572
2573 if ($line->fetch($rowid) < 1) {
2574 return -1;
2575 }
2576
2577 $res = $line->delete($notrigger);
2578
2579 if ($res < 1) {
2580 $this->errors[] = $line->error;
2581 $this->db->rollback();
2582 return -3;
2583 } else {
2584 $res = $this->update_price(1);
2585
2586 if ($res > 0) {
2587 $this->db->commit();
2588 return 1;
2589 } else {
2590 $this->db->rollback();
2591 $this->error = $this->db->lasterror();
2592 return -4;
2593 }
2594 }
2595 }
2596
2597
2604 public function info($id)
2605 {
2606 $sql = 'SELECT c.rowid, datec, tms as datem, ';
2607 $sql .= ' fk_user_author, fk_user_modif, fk_user_valid';
2608 $sql .= ' FROM '.MAIN_DB_PREFIX.'facture_fourn as c';
2609 $sql .= ' WHERE c.rowid = '.((int) $id);
2610
2611 $result = $this->db->query($sql);
2612 if ($result) {
2613 if ($this->db->num_rows($result)) {
2614 $obj = $this->db->fetch_object($result);
2615
2616 $this->id = $obj->rowid;
2617
2618 $this->user_creation_id = $obj->fk_user_author;
2619 $this->user_validation_id = $obj->fk_user_valid;
2620 $this->user_modification_id = $obj->fk_user_modif;
2621 $this->date_creation = $this->db->jdate($obj->datec);
2622 $this->date_modification = $this->db->jdate($obj->datem);
2623 //$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)
2624 }
2625 $this->db->free($result);
2626 } else {
2627 dol_print_error($this->db);
2628 }
2629 }
2630
2631 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2640 public function list_replacable_supplier_invoices($socid = 0)
2641 {
2642 // phpcs:enable
2643 global $conf;
2644
2645 $return = array();
2646
2647 $sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut,";
2648 $sql .= " ff.rowid as rowidnext";
2649 $sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn as f";
2650 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."paiementfourn_facturefourn as pf ON f.rowid = pf.fk_facturefourn";
2651 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facture_fourn as ff ON f.rowid = ff.fk_facture_source";
2652 $sql .= " WHERE (f.fk_statut = ".self::STATUS_VALIDATED." OR (f.fk_statut = ".self::STATUS_ABANDONED." AND f.close_code = '".self::CLOSECODE_ABANDONED."'))";
2653 $sql .= " AND f.entity = ".$conf->entity;
2654 $sql .= " AND f.paye = 0"; // Not closed completely
2655 $sql .= " AND pf.fk_paiementfourn IS NULL"; // No payment already done
2656 $sql .= " AND ff.fk_statut IS NULL"; // Return true (is null) if it is not a replacing invoice (we can't replace a replacing invoice)
2657 if ($socid > 0) {
2658 $sql .= " AND f.fk_soc = ".((int) $socid);
2659 }
2660 $sql .= " ORDER BY f.ref";
2661
2662 dol_syslog(get_class($this)."::list_replacable_supplier_invoices", LOG_DEBUG);
2663 $resql = $this->db->query($sql);
2664 if ($resql) {
2665 while ($obj = $this->db->fetch_object($resql)) {
2666 $return[$obj->rowid] = array(
2667 'id' => $obj->rowid,
2668 'ref' => $obj->ref,
2669 'status' => $obj->fk_statut
2670 );
2671 }
2672 //print_r($return);
2673 return $return;
2674 } else {
2675 $this->error = $this->db->error();
2676 return -1;
2677 }
2678 }
2679
2680 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2689 public function list_qualified_avoir_supplier_invoices($socid = 0)
2690 {
2691 // phpcs:enable
2692 global $conf;
2693
2694 $return = array();
2695
2696 $sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut, f.type, f.subtype, f.paye as paid, pf.fk_paiementfourn";
2697 $sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn as f";
2698 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."paiementfourn_facturefourn as pf ON f.rowid = pf.fk_facturefourn";
2699 $sql .= " WHERE f.entity = ".$conf->entity;
2700 $sql .= " AND f.fk_statut in (".self::STATUS_VALIDATED.",".self::STATUS_CLOSED.")";
2701 $sql .= " AND NOT EXISTS (SELECT rowid from ".MAIN_DB_PREFIX."facture_fourn as ff WHERE f.rowid = ff.fk_facture_source";
2702 $sql .= " AND ff.type=".self::TYPE_REPLACEMENT.")";
2703 $sql .= " AND f.type != ".self::TYPE_CREDIT_NOTE; // Type non 2 si facture non avoir
2704 if ($socid > 0) {
2705 $sql .= " AND f.fk_soc = ".((int) $socid);
2706 }
2707 $sql .= " ORDER BY f.ref";
2708
2709 dol_syslog(get_class($this)."::list_qualified_avoir_supplier_invoices", LOG_DEBUG);
2710 $resql = $this->db->query($sql);
2711 if ($resql) {
2712 while ($obj = $this->db->fetch_object($resql)) {
2713 $qualified = 0;
2714 if ($obj->fk_statut == self::STATUS_VALIDATED) {
2715 $qualified = 1;
2716 }
2717 if ($obj->fk_statut == self::STATUS_CLOSED) {
2718 $qualified = 1;
2719 }
2720 if ($qualified) {
2721 $paymentornot = ($obj->fk_paiementfourn ? 1 : 0);
2722 $return[$obj->rowid] = array('ref' => (string) $obj->ref, 'status' => (int) $obj->fk_statut, 'type' => (int) $obj->type, 'paid' => (int) $obj->paid, 'paye' => (int) $obj->paid, 'paymentornot' => (int) $paymentornot);
2723 }
2724 }
2725
2726 return $return;
2727 } else {
2728 $this->error = $this->db->error();
2729 return -1;
2730 }
2731 }
2732
2733 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2740 public function load_board($user)
2741 {
2742 // phpcs:enable
2743 global $conf, $langs;
2744
2745 $sql = 'SELECT ff.rowid, ff.date_lim_reglement as datefin, ff.fk_statut as status, ff.total_ht, ff.total_ttc';
2746 $sql .= ' FROM '.MAIN_DB_PREFIX.'facture_fourn as ff';
2747 if (empty($user->socid) && !$user->hasRight("societe", "client", "voir")) {
2748 $sql .= " JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON ff.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
2749 }
2750 $sql .= ' WHERE ff.paye = 0';
2751 $sql .= " AND ff.fk_statut IN (".self::STATUS_VALIDATED.")";
2752 $sql .= " AND ff.entity = ".$conf->entity;
2753 if ($user->socid) {
2754 $sql .= ' AND ff.fk_soc = '.((int) $user->socid);
2755 }
2756
2757 $resql = $this->db->query($sql);
2758 if ($resql) {
2759 $langs->load("bills");
2760 $now = dol_now();
2761
2762 $response = new WorkboardResponse();
2763 $response->warning_delay = $conf->facture->fournisseur->warning_delay / 60 / 60 / 24;
2764 $response->label = $langs->trans("SupplierBillsToPay");
2765 $response->labelShort = $langs->trans("StatusToPay");
2766
2767 $response->url = DOL_URL_ROOT.'/fourn/facture/list.php?search_status=1&mainmenu=billing&leftmenu=suppliers_bills';
2768 $response->img = img_object($langs->trans("Bills"), "bill");
2769
2770 $facturestatic = new FactureFournisseur($this->db);
2771
2772 while ($obj = $this->db->fetch_object($resql)) {
2773 $facturestatic->date_echeance = $this->db->jdate($obj->datefin);
2774 $facturestatic->statut = $obj->status; // For backward compatibility
2775 $facturestatic->status = $obj->status;
2776
2777 $response->nbtodo++;
2778 $response->total += $obj->total_ht;
2779
2780 if ($facturestatic->hasDelay()) {
2781 $response->nbtodolate++;
2782 $response->url_late = DOL_URL_ROOT.'/fourn/facture/list.php?search_option=late&mainmenu=billing&leftmenu=suppliers_bills';
2783 }
2784 }
2785
2786 $this->db->free($resql);
2787 return $response;
2788 } else {
2789 dol_print_error($this->db);
2790 $this->error = $this->db->error();
2791 return -1;
2792 }
2793 }
2794
2802 public function getTooltipContentArray($params)
2803 {
2804 global $conf, $langs, $mysoc;
2805
2806 $langs->load('bills');
2807
2808 $datas = [];
2809 $moretitle = $params['moretitle'] ?? '';
2810
2811 $picto = $this->picto;
2812 if ($this->type == self::TYPE_REPLACEMENT) {
2813 $picto .= 'r'; // Replacement invoice
2814 }
2815 if ($this->type == self::TYPE_CREDIT_NOTE) {
2816 $picto .= 'a'; // Credit note
2817 }
2818 if ($this->type == self::TYPE_DEPOSIT) {
2819 $picto .= 'd'; // Deposit invoice
2820 }
2821
2822 $datas['picto'] = img_picto('', $picto).' <u class="paddingrightonly">'.$langs->trans("SupplierInvoice").'</u>';
2823 if ($this->type == self::TYPE_REPLACEMENT) {
2824 $datas['picto'] .= '<u class="paddingrightonly">'.$langs->transnoentitiesnoconv("InvoiceReplacement").'</u>';
2825 } elseif ($this->type == self::TYPE_CREDIT_NOTE) {
2826 $datas['picto'] .= '<u class="paddingrightonly">'.$langs->transnoentitiesnoconv("CreditNote").'</u>';
2827 } elseif ($this->type == self::TYPE_DEPOSIT) {
2828 $datas['picto'] .= '<u class="paddingrightonly">'.$langs->transnoentitiesnoconv("Deposit").'</u>';
2829 }
2830 if (isset($this->status)) {
2831 $alreadypaid = -1;
2832 if (isset($this->totalpaid)) {
2833 $alreadypaid = $this->totalpaid;
2834 }
2835
2836 $datas['picto'] .= ' '.$this->getLibStatut(5, $alreadypaid);
2837 }
2838 if ($moretitle) {
2839 $datas['picto'] .= ' - '.$moretitle;
2840 }
2841 if (!empty($this->ref)) {
2842 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
2843 }
2844 if (!empty($this->ref_supplier)) {
2845 $datas['refsupplier'] = '<br><b>'.$langs->trans('RefSupplier').':</b> '.$this->ref_supplier;
2846 }
2847 if (!empty($this->label)) {
2848 $datas['label'] = '<br><b>'.$langs->trans('Label').':</b> '.$this->label;
2849 }
2850 if (!empty($this->date)) {
2851 $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
2852 }
2853 if (!empty($this->date_echeance)) {
2854 $datas['date_echeance'] = '<br><b>'.$langs->trans('DateDue').':</b> '.dol_print_date($this->date_echeance, 'day');
2855 }
2856 if (!empty($this->total_ht)) {
2857 $datas['amountht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
2858 }
2859 if (!empty($this->total_tva)) {
2860 $datas['totaltva'] = '<br><b>'.$langs->trans('AmountVAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
2861 }
2862 if (!empty($this->total_localtax1) && $this->total_localtax1 != 0) {
2863 // We keep test != 0 because $this->total_localtax1 can be '0.00000000'
2864 $datas['amountlt1'] = '<br><b>'.$langs->transcountry('AmountLT1', $mysoc->country_code).':</b> '.price($this->total_localtax1, 0, $langs, 0, -1, -1, $conf->currency);
2865 }
2866 if (!empty($this->total_localtax2) && $this->total_localtax2 != 0) {
2867 $datas['amountlt2'] = '<br><b>'.$langs->transcountry('AmountLT2', $mysoc->country_code).':</b> '.price($this->total_localtax2, 0, $langs, 0, -1, -1, $conf->currency);
2868 }
2869 if (!empty($this->revenuestamp)) {
2870 $datas['amountrevenustamp'] = '<br><b>'.$langs->trans('RevenueStamp').':</b> '.price($this->revenuestamp, 0, $langs, 0, -1, -1, $conf->currency);
2871 }
2872 if (!empty($this->total_ttc)) {
2873 $datas['totalttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
2874 }
2875 return $datas;
2876 }
2877
2891 public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $moretitle = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = 0)
2892 {
2893 global $langs, $conf, $user, $hookmanager;
2894
2895 $result = '';
2896
2897 if ($option == 'withdraw') {
2898 $url = DOL_URL_ROOT.'/compta/facture/prelevement.php?facid='.$this->id.'&type=bank-transfer';
2899 } elseif ($option == 'document') {
2900 $url = DOL_URL_ROOT.'/fourn/facture/document.php?facid='.$this->id;
2901 } else {
2902 $url = DOL_URL_ROOT.'/fourn/facture/card.php?facid='.$this->id;
2903 }
2904
2905 if ($short) {
2906 return $url;
2907 }
2908
2909 if ($option !== 'nolink') {
2910 // Add param to save lastsearch_values or not
2911 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
2912 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
2913 $add_save_lastsearch_values = 1;
2914 }
2915 if ($add_save_lastsearch_values) {
2916 $url .= '&save_lastsearch_values=1';
2917 }
2918 }
2919
2920 $picto = $this->picto;
2921 if ($this->type == self::TYPE_REPLACEMENT) {
2922 $picto .= 'r'; // Replacement invoice
2923 }
2924 if ($this->type == self::TYPE_CREDIT_NOTE) {
2925 $picto .= 'a'; // Credit note
2926 }
2927 if ($this->type == self::TYPE_DEPOSIT) {
2928 $picto .= 'd'; // Deposit invoice
2929 }
2930
2931 $params = [
2932 'id' => $this->id,
2933 'objecttype' => $this->element,
2934 'option' => $option,
2935 'moretitle' => $moretitle,
2936 ];
2937 $classfortooltip = 'classfortooltip';
2938 $dataparams = '';
2939 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
2940 $classfortooltip = 'classforajaxtooltip';
2941 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
2942 $label = '';
2943 } else {
2944 $label = implode($this->getTooltipContentArray($params));
2945 }
2946
2947 $ref = $this->ref;
2948 if (empty($ref)) {
2949 $ref = $this->id;
2950 }
2951
2952 $linkclose = '';
2953 if (empty($notooltip)) {
2954 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2955 $label = $langs->trans("ShowSupplierInvoice");
2956 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
2957 }
2958 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
2959 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
2960 }
2961
2962 $linkstart = '<a href="'.$url.'"';
2963 $linkstart .= $linkclose.'>';
2964 $linkend = '</a>';
2965
2966 $result .= $linkstart;
2967 if ($withpicto) {
2968 $result .= img_object(($notooltip ? '' : $label), ($picto ? $picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'"'), 0, 0, $notooltip ? 0 : 1);
2969 }
2970 if ($withpicto != 2) {
2971 $result .= ($max ? dol_trunc($ref, $max) : $ref);
2972 }
2973 $result .= $linkend;
2974
2975 if ($addlinktonotes) {
2976 $txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
2977 if ($txttoshow) {
2978 $notetoshow = $langs->trans("ViewPrivateNote").':<br>'.dol_string_nohtmltag($txttoshow, 1);
2979 $result .= ' <span class="note inline-block">';
2980 $result .= '<a href="'.DOL_URL_ROOT.'/fourn/facture/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($notetoshow).'">';
2981 $result .= img_picto('', 'note');
2982 $result .= '</a>';
2983 $result .= '</span>';
2984 }
2985 }
2986 global $action;
2987 $hookmanager->initHooks(array($this->element . 'dao'));
2988 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
2989 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2990 if ($reshook > 0) {
2991 $result = $hookmanager->resPrint;
2992 } else {
2993 $result .= $hookmanager->resPrint;
2994 }
2995 return $result;
2996 }
2997
3008 public function setCategories($categories)
3009 {
3010 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
3011 return parent::setCategoriesCommon($categories, Categorie::TYPE_SUPPLIER_INVOICE);
3012 }
3013
3022 public function getNextNumRef($soc, $mode = 'next')
3023 {
3024 global $db, $langs, $conf;
3025 $langs->load("orders");
3026
3027 // Clean parameters (if not defined or using deprecated value)
3028 if (!getDolGlobalString('INVOICE_SUPPLIER_ADDON_NUMBER')) {
3029 $conf->global->INVOICE_SUPPLIER_ADDON_NUMBER = 'mod_facture_fournisseur_cactus';
3030 }
3031
3032 $mybool = false;
3033
3034 $file = getDolGlobalString('INVOICE_SUPPLIER_ADDON_NUMBER') . ".php";
3035 $classname = getDolGlobalString('INVOICE_SUPPLIER_ADDON_NUMBER');
3036
3037 // Include file with class
3038 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3039
3040 foreach ($dirmodels as $reldir) {
3041 $dir = dol_buildpath($reldir."core/modules/supplier_invoice/");
3042
3043 // Load file with numbering class (if found)
3044 $mybool = ((bool) @include_once $dir.$file) || $mybool;
3045 }
3046
3047 if (!$mybool) {
3048 dol_print_error(null, "Failed to include file ".$file);
3049 return '';
3050 }
3051
3052 $obj = new $classname();
3053 '@phan-var-force ModeleNumRefSuppliersInvoices $obj';
3054 $numref = "";
3055 $numref = $obj->getNextValue($soc, $this, $mode);
3056
3057 if ($numref != "") {
3058 return $numref;
3059 } else {
3060 $this->error = $obj->error;
3061 return -1;
3062 }
3063 }
3064
3065
3074 public function initAsSpecimen($option = '')
3075 {
3076 global $langs, $conf;
3077 include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
3078
3079 $now = dol_now();
3080
3081 // Load array of products prodids
3082 $num_prods = 0;
3083 $prodids = array();
3084
3085 $sql = "SELECT rowid";
3086 $sql .= " FROM ".MAIN_DB_PREFIX."product";
3087 $sql .= " WHERE entity IN (".getEntity('product').")";
3088 $sql .= $this->db->plimit(100);
3089
3090 $resql = $this->db->query($sql);
3091 if ($resql) {
3092 $num_prods = $this->db->num_rows($resql);
3093 $i = 0;
3094 while ($i < $num_prods) {
3095 $i++;
3096 $row = $this->db->fetch_row($resql);
3097 $prodids[$i] = $row[0];
3098 }
3099 }
3100
3101 // Initialise parameters
3102 $this->id = 0;
3103 $this->ref = 'SPECIMEN';
3104 $this->ref_supplier = 'SUPPLIER_REF_SPECIMEN';
3105 $this->specimen = 1;
3106 $this->socid = 1;
3107 $this->date = $now;
3108 $this->date_lim_reglement = $this->date + 3600 * 24 * 30;
3109 $this->cond_reglement_code = 'RECEP';
3110 $this->mode_reglement_code = 'CHQ';
3111
3112 $this->note_public = 'This is a comment (public)';
3113 $this->note_private = 'This is a comment (private)';
3114
3115 $this->multicurrency_tx = 1;
3116 $this->multicurrency_code = $conf->currency;
3117
3118 $xnbp = 0;
3119 if (empty($option) || $option != 'nolines') {
3120 // Lines
3121 $nbp = min(1000, GETPOSTINT('nblines') ? GETPOSTINT('nblines') : 5); // We can force the nb of lines to test from command line (but not more than 1000)
3122 while ($xnbp < $nbp) {
3123 $line = new SupplierInvoiceLine($this->db);
3124 $line->desc = $langs->trans("Description")." ".$xnbp;
3125 $line->qty = 1;
3126 $line->subprice = 100;
3127 $line->price = 100;
3128 $line->tva_tx = 19.6;
3129 $line->localtax1_tx = 0;
3130 $line->localtax2_tx = 0;
3131 if ($xnbp == 2) {
3132 $line->total_ht = 50;
3133 $line->total_ttc = 59.8;
3134 $line->total_tva = 9.8;
3135 $line->remise_percent = 50;
3136 } else {
3137 $line->total_ht = 100;
3138 $line->total_ttc = 119.6;
3139 $line->total_tva = 19.6;
3140 $line->remise_percent = 0;
3141 }
3142
3143 if ($num_prods > 0) {
3144 $prodid = mt_rand(1, $num_prods);
3145 $line->fk_product = $prodids[$prodid];
3146 }
3147 $line->product_type = 0;
3148
3149 $this->lines[$xnbp] = $line;
3150
3151 $this->total_ht += $line->total_ht;
3152 $this->total_tva += $line->total_tva;
3153 $this->total_ttc += $line->total_ttc;
3154
3155 $xnbp++;
3156 }
3157 }
3158
3159 $this->total_ht = $xnbp * 100;
3160 $this->total_tva = $xnbp * 19.6;
3161 $this->total_ttc = $xnbp * 119.6;
3162
3163 return 1;
3164 }
3165
3171 public function loadStateBoard()
3172 {
3173 global $conf, $user;
3174
3175 $this->nb = array();
3176
3177 $clause = "WHERE";
3178
3179 $sql = "SELECT count(f.rowid) as nb";
3180 $sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn as f";
3181 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON f.fk_soc = s.rowid";
3182 if (empty($user->socid) && !$user->hasRight("societe", "client", "voir")) {
3183 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3184 $sql .= " WHERE sc.fk_user = ".((int) $user->id);
3185 $clause = "AND";
3186 }
3187 $sql .= " ".$clause." f.entity = ".((int) $conf->entity);
3188
3189 $resql = $this->db->query($sql);
3190 if ($resql) {
3191 while ($obj = $this->db->fetch_object($resql)) {
3192 $this->nb["supplier_invoices"] = $obj->nb;
3193 }
3194 $this->db->free($resql);
3195 return 1;
3196 } else {
3197 dol_print_error($this->db);
3198 $this->error = $this->db->error();
3199 return -1;
3200 }
3201 }
3202
3211 public function createFromClone(User $user, $fromid, $invertdetail = 0)
3212 {
3213 global $conf, $langs;
3214
3215 $error = 0;
3216
3217 $object = new FactureFournisseur($this->db);
3218
3219 $this->db->begin();
3220
3221 // Load source object
3222 $object->fetch($fromid);
3223 $object->id = 0;
3224 $object->statut = self::STATUS_DRAFT; // For backward compatibility
3225 $object->status = self::STATUS_DRAFT;
3226
3227 $object->fetch_thirdparty(); // We need it to recalculate VAT localtaxes according to main sale taxes and vendor
3228
3229 // Clear fields
3230 $object->ref_supplier = (empty($this->ref_supplier) ? $langs->trans("CopyOf").' '.$object->ref_supplier : $this->ref_supplier);
3231 $object->author = $user->id; // FIXME? user_validation_id is replacement for author
3232 $object->user_validation_id = 0; // FIXME? user_validation_id is replacement for author
3233 $object->fk_facture_source = 0;
3234 $object->date_creation = '';
3235 $object->date_validation = '';
3236 $object->date = (empty($this->date) ? dol_now() : $this->date);
3237 $object->ref_client = '';
3238 $object->close_code = '';
3239 $object->close_note = '';
3240 if (getDolGlobalInt('MAIN_DONT_KEEP_NOTE_ON_CLONING') == 1) {
3241 $object->note_private = '';
3242 $object->note_public = '';
3243 }
3244
3245 $object->date_echeance = $object->calculate_date_lim_reglement();
3246
3247 // Loop on each line of new invoice
3248 foreach ($object->lines as $i => $line) {
3249 if (isset($object->lines[$i]->info_bits) && ($object->lines[$i]->info_bits & 0x02) == 0x02) { // We do not clone line of discounts
3250 unset($object->lines[$i]);
3251 }
3252 }
3253
3254 // Create clone
3255 $object->context['createfromclone'] = 'createfromclone';
3256 $result = $object->create($user);
3257
3258 // Other options
3259 if ($result < 0) {
3260 $this->error = $object->error;
3261 $this->errors = $object->errors;
3262 $error++;
3263 }
3264
3265 if (!$error) {
3266 }
3267
3268 unset($object->context['createfromclone']);
3269
3270 // End
3271 if (!$error) {
3272 $this->db->commit();
3273 return $object->id;
3274 } else {
3275 $this->db->rollback();
3276 return -1;
3277 }
3278 }
3279
3291 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3292 {
3293 global $langs;
3294
3295 $langs->load("suppliers");
3296 $outputlangs->load("products");
3297
3298 // Set the model on the model name to use
3299 if (empty($modele)) {
3300 if (getDolGlobalString('INVOICE_SUPPLIER_ADDON_PDF')) {
3301 $modele = getDolGlobalString('INVOICE_SUPPLIER_ADDON_PDF');
3302 } else {
3303 $modele = ''; // No default value. For supplier invoice, we allow to disable all PDF generation
3304 }
3305 } elseif ($modele == 'auto') {
3306 $modele = 'canelle';
3307 }
3308
3309 if (empty($modele)) {
3310 return 0;
3311 } else {
3312 $modelpath = "core/modules/supplier_invoice/doc/";
3313
3314 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3315 }
3316 }
3317
3322 public function getRights()
3323 {
3324 global $user;
3325
3326 return $user->hasRight("fournisseur", "facture");
3327 }
3328
3337 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
3338 {
3339 $tables = array(
3340 'facture_fourn'
3341 );
3342
3343 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
3344 }
3345
3354 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
3355 {
3356 $tables = array(
3357 'facture_fourn_det'
3358 );
3359
3360 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
3361 }
3362
3368 public function hasDelay()
3369 {
3370 global $conf;
3371
3372 $now = dol_now();
3373
3374 if (!$this->date_echeance) {
3375 return false;
3376 }
3377
3378 $status = isset($this->status) ? $this->status : $this->statut;
3379
3380 return ($status == self::STATUS_VALIDATED) && ($this->date_echeance < ($now - $conf->facture->fournisseur->warning_delay));
3381 }
3382
3388 public function isCreditNoteUsed()
3389 {
3390 $isUsed = false;
3391
3392 $sql = "SELECT fk_invoice_supplier FROM ".MAIN_DB_PREFIX."societe_remise_except WHERE fk_invoice_supplier_source = ".((int) $this->id);
3393 $resql = $this->db->query($sql);
3394 if (!empty($resql)) {
3395 $obj = $this->db->fetch_object($resql);
3396 if (!empty($obj->fk_invoice_supplier)) {
3397 $isUsed = true;
3398 }
3399 }
3400
3401 return $isUsed;
3402 }
3410 public function getKanbanView($option = '', $arraydata = null)
3411 {
3412 global $langs;
3413
3414 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
3415
3416 $picto = $this->picto;
3417 if ($this->type == self::TYPE_REPLACEMENT) {
3418 $picto .= 'r'; // Replacement invoice
3419 }
3420 if ($this->type == self::TYPE_CREDIT_NOTE) {
3421 $picto .= 'a'; // Credit note
3422 }
3423 if ($this->type == self::TYPE_DEPOSIT) {
3424 $picto .= 'd'; // Deposit invoice
3425 }
3426
3427 $return = '<div class="box-flex-item box-flex-grow-zero">';
3428 $return .= '<div class="info-box info-box-sm">';
3429 $return .= '<span class="info-box-icon bg-infobox-action">';
3430 $return .= img_picto('', $picto);
3431 $return .= '</span>';
3432 $return .= '<div class="info-box-content">';
3433 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . $this->getNomUrl(1) . '</span>';
3434 if ($selected >= 0) {
3435 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
3436 }
3437 if (!empty($arraydata['thirdparty'])) {
3438 $return .= '<br><span class="info-box-label">'.$arraydata['thirdparty'].'</span>';
3439 }
3440 if (!empty($this->date)) {
3441 $return .= '<br><span class="info-box-label">'.dol_print_date($this->date, 'day').'</span>';
3442 }
3443 if (!empty($this->total_ht)) {
3444 $return .= ' &nbsp; <span class="info-box-label amount" title="'.dol_escape_htmltag($langs->trans("AmountHT")).'">'.price($this->total_ht);
3445 $return .= ' '.$langs->trans("HT");
3446 $return .= '</span>';
3447 }
3448 $alreadypaid = (empty($arraydata['alreadypaid']) ? 0 : $arraydata['alreadypaid']);
3449 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3, $alreadypaid).'</div>';
3450
3451 $return .= '</div>';
3452 $return .= '</div>';
3453 $return .= '</div>';
3454
3455 return $return;
3456 }
3457
3464 public function setVATReverseCharge($vatreversecharge)
3465 {
3466 if (!$this->table_element) {
3467 dol_syslog(get_class($this)."::setVATReverseCharge was called on object with property table_element not defined", LOG_ERR);
3468 return -1;
3469 }
3470
3471 dol_syslog(get_class($this).'::setVATReverseCharge('.$vatreversecharge.')');
3472
3473 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
3474 $sql .= " SET vat_reverse_charge = ".((int) $vatreversecharge);
3475 $sql .= " WHERE rowid=".((int) $this->id);
3476
3477 if ($this->db->query($sql)) {
3478 $this->vat_reverse_charge = ($vatreversecharge == 0) ? 0 : 1;
3479 return 1;
3480 } else {
3481 dol_syslog(get_class($this).'::setVATReverseCharge Error ', LOG_DEBUG);
3482 $this->error = $this->db->error();
3483 return 0;
3484 }
3485 }
3486
3498 public function sendEmailsRemindersOnSupplierInvoiceDueDate($nbdays = 0, $paymentmode = 'all', $template = '', $datetouse = 'duedate', $forcerecipient = '')
3499 {
3500 global $conf, $langs, $user;
3501
3502 $this->output = '';
3503 $this->error = '';
3504 $nbMailSend = 0;
3505
3506 $error = 0;
3507 $errorsMsg = array();
3508
3509 $langs->load('bills');
3510
3511 if (!isModEnabled(!getDolGlobalString('MAIN_USE_NEW_SUPPLIERMOD') ? 'fournisseur' : 'supplier_invoice')) { // Should not happen. If module disabled, cron job should not be visible.
3512 $this->output .= $langs->trans('ModuleNotEnabled', $langs->transnoentitiesnoconv('Suppliers'));
3513 return 0;
3514 }
3515 if (!in_array($datetouse, array('duedate', 'invoicedate'))) {
3516 $this->output .= 'Bad value for parameter datetouse. Must be "duedate" or "invoicedate"';
3517 return 0;
3518 }
3519
3520 require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
3521 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php';
3522 require_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
3523 $formmail = new FormMail($this->db);
3524
3525 $now = dol_now();
3526 $tmpidate = dol_get_first_hour(dol_time_plus_duree($now, $nbdays, 'd'), 'gmt');
3527
3528 $tmpinvoice = new FactureFournisseur($this->db);
3529
3530 dol_syslog(__METHOD__." start", LOG_INFO);
3531
3532 // Select all action comm reminder
3533 $sql = "SELECT rowid as id FROM ".MAIN_DB_PREFIX."facture_fourn as f";
3534 if (!empty($paymentmode) && $paymentmode != 'all') {
3535 $sql .= ", ".MAIN_DB_PREFIX."c_paiement as cp";
3536 }
3537 $sql .= " WHERE f.paye = 0"; // Only unpaid
3538 $sql .= " AND f.fk_statut = ".self::STATUS_VALIDATED; // Only validated status
3539 if ($datetouse == 'invoicedate') {
3540 $sql .= " AND f.datef = '".$this->db->idate($tmpidate, 'gmt')."'";
3541 } else {
3542 $sql .= " AND f.date_lim_reglement = '".$this->db->idate($tmpidate, 'gmt')."'";
3543 }
3544 $sql .= " AND f.entity IN (".getEntity('supplier_invoice', 0).")"; // One batch process only one company (no sharing)
3545 if (!empty($paymentmode) && $paymentmode != 'all') {
3546 $sql .= " AND f.fk_mode_reglement = cp.id AND cp.code = '".$this->db->escape($paymentmode)."'";
3547 }
3548 // TODO Add a filter to check there is no payment started yet
3549 if ($datetouse == 'invoicedate') {
3550 $sql .= $this->db->order("datef", "ASC");
3551 } else {
3552 $sql .= $this->db->order("date_lim_reglement", "ASC");
3553 }
3554
3555 $resql = $this->db->query($sql);
3556
3557 $stmpidate = dol_print_date($tmpidate, 'day', 'gmt');
3558 if ($datetouse == 'invoicedate') {
3559 $this->output .= $langs->transnoentitiesnoconv("SearchValidatedSupplierInvoicesWithDate", $stmpidate);
3560 } else {
3561 $this->output .= $langs->transnoentitiesnoconv("SearchUnpaidSupplierInvoicesWithDueDate", $stmpidate);
3562 }
3563 if (!empty($paymentmode) && $paymentmode != 'all') {
3564 $this->output .= ' ('.$langs->transnoentitiesnoconv("PaymentMode").' '.$paymentmode.')';
3565 }
3566 $this->output .= '<br>';
3567
3568 if ($resql) {
3569 while ($obj = $this->db->fetch_object($resql)) {
3570 if (!$error) {
3571 // Load event
3572 $res = $tmpinvoice->fetch($obj->id);
3573 if ($res > 0) {
3574 $tmpinvoice->fetch_thirdparty();
3575
3576 $outputlangs = new Translate('', $conf);
3577 if ($tmpinvoice->thirdparty->default_lang) {
3578 $outputlangs->setDefaultLang($tmpinvoice->thirdparty->default_lang);
3579 $outputlangs->loadLangs(array("main", "suppliers"));
3580 } else {
3581 $outputlangs = $langs;
3582 }
3583
3584 // Select email template according to language of recipient
3585 $templateId = 0;
3586 $templateLabel = '';
3587 if (empty($template) || $template == 'EmailTemplateCode') {
3588 $templateLabel = '(SendingReminderEmailOnUnpaidSupplierInvoice)';
3589 } else {
3590 if (is_numeric($template)) {
3591 $templateId = $template;
3592 } else {
3593 $templateLabel = $template;
3594 }
3595 }
3596
3597 $arraymessage = $formmail->getEMailTemplate($this->db, 'invoice_supplier_send', $user, $outputlangs, $templateId, 1, $templateLabel);
3598 if (is_numeric($arraymessage) && $arraymessage <= 0) {
3599 $langs->load("errors");
3600 $this->output .= $langs->trans('ErrorFailedToFindEmailTemplate', $template);
3601 return 0;
3602 }
3603
3604 // PREPARE EMAIL
3605 $errormesg = '';
3606
3607 // Make substitution in email content
3608 $substitutionarray = getCommonSubstitutionArray($outputlangs, 0, null, $tmpinvoice);
3609
3610 complete_substitutions_array($substitutionarray, $outputlangs, $tmpinvoice);
3611
3612 // Topic
3613 $sendTopic = make_substitutions(empty($arraymessage->topic) ? $outputlangs->transnoentitiesnoconv('InformationMessage') : $arraymessage->topic, $substitutionarray, $outputlangs, 1);
3614
3615 // Content
3616 $content = $outputlangs->transnoentitiesnoconv($arraymessage->content);
3617
3618 $sendContent = make_substitutions($content, $substitutionarray, $outputlangs, 1);
3619
3620 // Recipient
3621 $to = array();
3622 if ($forcerecipient) { // If a recipient was forced
3623 $to = array($forcerecipient);
3624 } else {
3625 $res = $tmpinvoice->fetch_thirdparty();
3626 $recipient = $tmpinvoice->thirdparty;
3627 if ($res > 0) {
3628 $tmparraycontact = $tmpinvoice->liste_contact(-1, 'internal', 0, 'SALESREPFOLL');
3629 if (is_array($tmparraycontact) && count($tmparraycontact) > 0) {
3630 foreach ($tmparraycontact as $data_email) {
3631 if (!empty($data_email['email'])) {
3632 $to[] = $data_email['email'];
3633 }
3634 }
3635 }
3636 if (empty($to) && !empty($recipient->email)) {
3637 $to[] = $recipient->email;
3638 }
3639 if (empty($to)) {
3640 $errormesg = "Failed to send remind to thirdparty id=".$tmpinvoice->socid.". No email defined for supplier invoice or customer.";
3641 $error++;
3642 }
3643 } else {
3644 $errormesg = "Failed to load recipient with thirdparty id=".$tmpinvoice->socid;
3645 $error++;
3646 }
3647 }
3648
3649 // Sender
3650 $from = getDolGlobalString('MAIN_MAIL_EMAIL_FROM');
3651 if (!empty($arraymessage->email_from)) { // If a sender is defined into template, we use it in priority
3652 $from = (string) $arraymessage->email_from;
3653 }
3654 if (empty($from)) {
3655 $errormesg = "Failed to get sender into global setup MAIN_MAIL_EMAIL_FROM";
3656 $error++;
3657 }
3658
3659 if (!$error && !empty($to)) {
3660 $this->db->begin();
3661
3662 $to = implode(',', $to);
3663 if (!empty($arraymessage->email_to)) { // If a recipient is defined into template, we add it
3664 $to = $to.','.$arraymessage->email_to;
3665 }
3666
3667 // Errors Recipient
3668 $errors_to = getDolGlobalString('MAIN_MAIL_ERRORS_TO');
3669
3670 $trackid = 'inv'.$tmpinvoice->id;
3671 $sendcontext = 'standard';
3672
3673 $email_tocc = '';
3674 if (!empty($arraymessage->email_tocc)) { // If a CC is defined into template, we use it
3675 $email_tocc = (string) $arraymessage->email_tocc;
3676 }
3677
3678 $email_tobcc = '';
3679 if (!empty($arraymessage->email_tobcc)) { // If a BCC is defined into template, we use it
3680 $email_tobcc = (string) $arraymessage->email_tobcc;
3681 }
3682
3683 // Mail Creation
3684 $cMailFile = new CMailFile($sendTopic, $to, $from, $sendContent, array(), array(), array(), $email_tocc, $email_tobcc, 0, 1, $errors_to, '', $trackid, '', $sendcontext, '');
3685
3686 // Sending Mail
3687 if ($cMailFile->sendfile()) {
3688 $nbMailSend++;
3689
3690 // Add a line into event table
3691 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
3692
3693 // Insert record of emails sent
3694 $actioncomm = new ActionComm($this->db);
3695
3696 $actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
3697 $actioncomm->socid = $tmpinvoice->thirdparty->id; // To link to a company
3698 $actioncomm->contact_id = 0;
3699
3700 $actioncomm->code = 'AC_EMAIL';
3701 $actioncomm->label = 'sendEmailsRemindersOnInvoiceDueDateOK (nbdays='.$nbdays.' paymentmode='.$paymentmode.' template='.$template.' datetouse='.$datetouse.' forcerecipient='.$forcerecipient.')';
3702 $actioncomm->note_private = $sendContent;
3703 $actioncomm->fk_project = $tmpinvoice->fk_project;
3704 $actioncomm->datep = dol_now();
3705 $actioncomm->datef = $actioncomm->datep;
3706 $actioncomm->percentage = -1; // Not applicable
3707 $actioncomm->authorid = $user->id; // User saving action
3708 $actioncomm->userownerid = $user->id; // Owner of action
3709 // Fields when action is an email (content should be added into note)
3710 $actioncomm->email_msgid = $cMailFile->msgid;
3711 $actioncomm->email_subject = $sendTopic;
3712 $actioncomm->email_from = $from;
3713 $actioncomm->email_sender = '';
3714 $actioncomm->email_to = $to;
3715 //$actioncomm->email_tocc = $sendtocc;
3716 //$actioncomm->email_tobcc = $sendtobcc;
3717 //$actioncomm->email_subject = $subject;
3718 $actioncomm->errors_to = $errors_to;
3719
3720 $actioncomm->elementtype = 'invoice_supplier';
3721 $actioncomm->elementid = $tmpinvoice->id;
3722 $actioncomm->fk_element = $tmpinvoice->id;
3723
3724 //$actioncomm->extraparams = $extraparams;
3725
3726 $actioncomm->create($user);
3727 } else {
3728 $errormesg = $cMailFile->error.' : '.$to;
3729 $error++;
3730
3731 // Add a line into event table
3732 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
3733
3734 // Insert record of emails sent
3735 $actioncomm = new ActionComm($this->db);
3736
3737 $actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
3738 $actioncomm->socid = $tmpinvoice->thirdparty->id; // To link to a company
3739 $actioncomm->contact_id = 0;
3740
3741 $actioncomm->code = 'AC_EMAIL';
3742 $actioncomm->label = 'sendEmailsRemindersOnInvoiceDueDateKO';
3743 $actioncomm->note_private = $errormesg;
3744 $actioncomm->fk_project = $tmpinvoice->fk_project;
3745 $actioncomm->datep = dol_now();
3746 $actioncomm->datef = $actioncomm->datep;
3747 $actioncomm->percentage = -1; // Not applicable
3748 $actioncomm->authorid = $user->id; // User saving action
3749 $actioncomm->userownerid = $user->id; // Owner of action
3750 // Fields when action is an email (content should be added into note)
3751 $actioncomm->email_msgid = $cMailFile->msgid;
3752 $actioncomm->email_from = $from;
3753 $actioncomm->email_sender = '';
3754 $actioncomm->email_to = $to;
3755 //$actioncomm->email_tocc = $sendtocc;
3756 //$actioncomm->email_tobcc = $sendtobcc;
3757 //$actioncomm->email_subject = $subject;
3758 $actioncomm->errors_to = $errors_to;
3759
3760 //$actioncomm->extraparams = $extraparams;
3761
3762 $actioncomm->create($user);
3763 }
3764
3765 $this->db->commit(); // We always commit
3766 }
3767
3768 if ($errormesg) {
3769 $errorsMsg[] = $errormesg;
3770 }
3771 } else {
3772 $errorsMsg[] = 'Failed to fetch record invoice with ID = '.$obj->id;
3773 $error++;
3774 }
3775 }
3776 }
3777 } else {
3778 $error++;
3779 }
3780
3781 if (!$error) {
3782 $this->output .= 'Nb of emails sent : '.$nbMailSend;
3783
3784 dol_syslog(__METHOD__." end - ".$this->output, LOG_INFO);
3785
3786 return 0;
3787 } else {
3788 $this->error = 'Nb of emails sent : '.$nbMailSend.', '.(empty($errorsMsg) ? $error : implode(', ', $errorsMsg));
3789
3790 dol_syslog(__METHOD__." end - ".$this->error, LOG_INFO);
3791
3792 return $error;
3793 }
3794 }
3795}
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
$object ref
Definition info.php:90
Class to manage agenda events (actions)
Class to send emails (with attachments or not) Usage: $mailfile = new CMailFile($subject,...
Superclass for invoice classes.
calculate_date_lim_reglement($cond_reglement=0)
Returns an invoice payment deadline based on the invoice settlement conditions and billing date.
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this->array_options This method is in most cases call...
line_order($renum=false, $rowidorder='ASC', $fk_parent_line=true)
Save a new position (field rang) for details lines.
deleteEcmFiles($mode=0)
Delete related files of object in database.
update_price($exclspec=0, $roundingadjust='auto', $nodatabaseupdate=0, $seller=null)
Update total_ht, total_ttc, total_vat, total_localtax1, total_localtax2 for an object (sum of lines).
add_object_linked($origin=null, $origin_id=null, $f_user=null, $notrigger=0)
Add an object link into llx_element_element.
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
fetch_thirdparty($force_thirdparty_id=0)
Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty.
deleteObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $rowid=0, $f_user=null, $notrigger=0)
Delete all links between an object $this.
setErrorsFromObject($object)
setErrorsFromObject
updateRangOfLine($rowid, $rang)
Update position of line (rang)
deleteExtraFields()
Delete all extra fields values for the current object.
static commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
static commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a product id with another one.
line_max($fk_parent_line=0)
Get max value used for position of line (rang)
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
Class to manage absolute discounts.
Class to manage Dolibarr database access.
Class to manage suppliers invoices.
const TYPE_DEPOSIT
Deposit invoice.
create($user)
Create supplier invoice into database.
list_qualified_avoir_supplier_invoices($socid=0)
Return list of qualifying invoices for correction by credit note Invoices that respect the following ...
list_replacable_supplier_invoices($socid=0)
Return list of replaceable invoices Status valid or abandoned for other reason + not paid + no paymen...
deleteLine($rowid, $notrigger=0)
Delete a detail line from database.
set_unpaid($user)
Tag the invoice as not fully paid + trigger call BILL_UNPAYED Function used when a direct debit payme...
setCanceled($user, $close_code='', $close_note='')
Tag invoice as canceled, with no payment on it (example for replacement invoice or payment never rece...
fetch($id=0, $ref='', $ref_ext='')
Load object in memory from database.
setCategories($categories)
Sets object to given categories.
addline($desc, $pu, $txtva, $txlocaltax1, $txlocaltax2, $qty, $fk_product=0, $remise_percent=0, $date_start=0, $date_end=0, $fk_code_ventilation=0, $info_bits=0, $price_base_type='HT', $type=0, $rang=-1, $notrigger=0, $array_options=[], $fk_unit=null, $origin_id=0, $pu_devise=0, $ref_supplier='', $special_code=0, $fk_parent_line=0, $fk_remise_except=0)
Adds an invoice line (associated with no predefined product/service) The parameters are already suppo...
info($id)
Loads the info order information into the invoice object.
const TYPE_CREDIT_NOTE
Credit note invoice.
isCreditNoteUsed()
Is credit note used.
getKanbanView($option='', $arraydata=null)
Return clickable link of object (with eventually picto)
load_board($user)
Load indicators for dashboard (this->nbtodo and this->nbtodolate)
getNomUrl($withpicto=0, $option='', $max=0, $short=0, $moretitle='', $notooltip=0, $save_lastsearch_value=-1, $addlinktonotes=0)
Return clickable name (with optional picto)
getTooltipContentArray($params)
getTooltipContentArray
setPaid($user, $close_code='', $close_note='')
Tag invoice as a paid invoice.
update($user=null, $notrigger=0)
Update database.
static replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
const TYPE_REPLACEMENT
Replacement invoice.
setUnpaid($user)
Tag the invoice as not fully paid + trigger call BILL_UNPAYED Function used when a direct debit payme...
const STATUS_VALIDATED
Validated (need to be paid)
setDraft($user, $idwarehouse=-1, $notrigger=0)
Set draft status.
getNextNumRef($soc, $mode='next')
Return next reference of supplier invoice not already used (or last reference) according to numbering...
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)
Update a line detail in the database.
sendEmailsRemindersOnSupplierInvoiceDueDate($nbdays=0, $paymentmode='all', $template='', $datetouse='duedate', $forcerecipient='')
Send reminders by emails for supplier invoices validated that are due.
insert_discount($idremise)
Add a discount line into an invoice (as an invoice line) using an existing absolute discount (Consume...
initAsSpecimen($option='')
Initialise an instance with random values.
const TYPE_STANDARD
Standard invoice.
validate($user, $force_number='', $idwarehouse=0, $notrigger=0)
Tag invoice as validated + call trigger BILL_VALIDATE.
set_paid($user, $close_code='', $close_note='')
Tag invoice as a paid invoice.
createFromClone(User $user, $fromid, $invertdetail=0)
Load an object from its id and create a new one in database.
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null)
Create a document onto disk according to template model.
getRights()
Returns the rights used for this class.
const STATUS_ABANDONED
Classified abandoned and no payment done.
hasDelay()
Is the payment of the supplier invoice having a delay?
static replaceProduct(DoliDB $db, $origin_id, $dest_id)
Function used to replace a product id with another one.
loadStateBoard()
Load indicators for dashboard (this->nbtodo and this->nbtodolate)
const STATUS_CLOSED
Classified paid.
setVATReverseCharge($vatreversecharge)
Change the option VAT reverse charge.
Class to manage invoice templates.
Class permettant la generation du formulaire html d'envoi de mail unitaire Usage: $formail = new Form...
Class permettant la generation de composants html autre Only common components are here.
Class to manage stock movements.
static getIdAndTxFromCode($dbs, $code, $date_document=0)
Get id and rate of currency from code.
static getIdFromCode($dbs, $code)
Get id of currency from code.
Class to manage predefined suppliers products.
Class to manage products or services.
Class to manage line invoices.
Class to manage translations.
Class to manage Dolibarr users.
print $langs trans("Ref").' m titre as m m statut as status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition index.php:171
global $mysoc
dol_get_first_hour($date, $gm='tzserver')
Return GMT time for first hour of a given GMT date (it removes hours, min and second part)
Definition date.lib.php:663
dol_time_plus_duree($time, $duration_value, $duration_unit, $ruleforendofmonth=0)
Add a delay to a date.
Definition date.lib.php:125
dol_delete_file($file, $disableglob=0, $nophperrors=0, $nohook=0, $object=null, $allowdotdot=false, $indexdatabase=1, $nolog=0)
Remove a file or several files with a mask.
dol_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0, $level=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
dol_dir_list($utf8_path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0, $nbsecondsold=0)
Scan a directory and return a list of files/directories.
Definition files.lib.php:64
dol_now($mode='gmt')
Return date for now.
setEntity($currentobject)
Set entity id to use when to create an object.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2, $allowothertags=array())
Show picto whatever it's its name (generic function)
GETPOSTINT($paramname, $method=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
dol_string_nohtmltag($stringtoclean, $removelinefeed=1, $pagecodeto='UTF-8', $strip_tags=0, $removedoublespaces=1)
Clean a string from all HTML tags and entities.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $srconly=0, $notitle=0, $allowothertags=array())
Show a picto called object_picto (generic function)
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0, $allowdash=0)
Clean a string to use it as a file name.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid=0)
Get type and rate of localtaxes for a particular vat rate/country of a thirdparty.
dol_concatdesc($text1, $text2, $forxml=false, $invert=false)
Concat 2 descriptions with a new line between them (second operand after first one with appropriate n...
complete_substitutions_array(&$substitutionarray, $outputlangs, $object=null, $parameters=null, $callfunc="completesubstitutionarray")
Complete the $substitutionarray with more entries coming from external module that had set the "subst...
make_substitutions($text, $substitutionarray, $outputlangs=null, $converttextinhtmlifnecessary=0)
Make substitution into a text string, replacing keys with vals from $substitutionarray (oldval=>newva...
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '…' if string larger than length.
isValidEmail($address, $acceptsupervisorkey=0, $acceptuserkey=0)
Return true if email syntax is ok.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart='')
Return a path to have a the directory according to object where files are stored.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $remise_percent_global, $price_base_type, $info_bits, $type, $seller=null, $localtaxes_array=[], $progress=100, $multicurrency_tx=1, $pu_devise=0, $multicurrency_code='')
Calculate totals (net, vat, ...) of a line.
Definition price.lib.php:90
if(getDolGlobalString( 'TAKEPOS_SHOW_CUSTOMER')) print $langs trans('Date')." left Label right Qty right Price right TotalHT right TotalTTC right right right right right right right right right centpercent right TotalHT right n right VAT right n right TotalVAT right n No sujeto a RE IRPF right TotalLT1 right n right TotalLT2 right n right TotalTTC right n takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency right TotalTTC takeposcustomercurrency right takeposcustomercurrency n right PaymentTypeShortLIQ right SELECT p pos_change as p datep as date
Definition receipt.php:464
if(preg_match('/(crypted|dolcrypt):/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
'integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]',...
Definition repair.php:125