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 // Remove linked categories.
1487 $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_supplier_invoice";
1488 $sql .= " WHERE fk_supplier_invoice = ".((int) $this->id);
1489
1490 $result = $this->db->query($sql);
1491 if (!$result) {
1492 $error++;
1493 $this->error = $this->db->lasterror();
1494 $this->errors[] = $this->error;
1495 }
1496
1497 if (!$error) {
1498 // If invoice was converted into a discount not yet consumed, we remove discount
1499 $sql = 'DELETE FROM '.MAIN_DB_PREFIX.'societe_remise_except';
1500 $sql .= ' WHERE fk_invoice_supplier_source = '.((int) $rowid);
1501 $sql .= ' AND fk_invoice_supplier_line IS NULL';
1502 $resql = $this->db->query($sql);
1503
1504 // If invoice has consumned discounts
1505 $this->fetch_lines();
1506 $list_rowid_det = array();
1507 foreach ($this->lines as $key => $invoiceline) {
1508 $list_rowid_det[] = $invoiceline->id;
1509 }
1510
1511 // Consumned discounts are freed
1512 if (count($list_rowid_det)) {
1513 $sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
1514 $sql .= ' SET fk_invoice_supplier = NULL, fk_invoice_supplier_line = NULL';
1515 $sql .= ' WHERE fk_invoice_supplier_line IN ('.$this->db->sanitize(implode(',', $list_rowid_det)).')';
1516
1517 dol_syslog(get_class($this)."::delete", LOG_DEBUG);
1518 if (!$this->db->query($sql)) {
1519 $error++;
1520 }
1521 }
1522 }
1523
1524 if (!$error) {
1525 $main = MAIN_DB_PREFIX.'facture_fourn_det';
1526 $ef = $main."_extrafields";
1527 $sqlef = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM ".$main." WHERE fk_facture_fourn = ".((int) $rowid).")";
1528 $resqlef = $this->db->query($sqlef);
1529 $sql = 'DELETE FROM '.MAIN_DB_PREFIX.'facture_fourn_det WHERE fk_facture_fourn = '.((int) $rowid);
1530 dol_syslog(get_class($this)."::delete", LOG_DEBUG);
1531 $resql = $this->db->query($sql);
1532 if ($resqlef && $resql) {
1533 $sql = 'DELETE FROM '.MAIN_DB_PREFIX.'facture_fourn WHERE rowid = '.((int) $rowid);
1534 dol_syslog(get_class($this)."::delete", LOG_DEBUG);
1535 $resql2 = $this->db->query($sql);
1536 if (!$resql2) {
1537 $error++;
1538 }
1539 } else {
1540 $error++;
1541 }
1542 }
1543
1544 if (!$error) {
1545 // Delete linked object
1546 $res = $this->deleteObjectLinked();
1547 if ($res < 0) {
1548 $error++;
1549 }
1550 }
1551
1552 if (!$error) {
1553 // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
1554 $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1555 $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1556
1557 // We remove directory
1558 if ($conf->fournisseur->facture->dir_output) {
1559 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1560
1561 $ref = dol_sanitizeFileName($this->ref);
1562 $dir = $conf->fournisseur->facture->dir_output.'/'.get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier').$ref;
1563 $file = $dir."/".$ref.".pdf";
1564 if (file_exists($file)) {
1565 if (!dol_delete_file($file, 0, 0, 0, $this)) { // For triggers
1566 $this->error = 'ErrorFailToDeleteFile';
1567 $error++;
1568 }
1569 }
1570 if (file_exists($dir)) {
1571 $res = @dol_delete_dir_recursive($dir);
1572
1573 if (!$res) {
1574 $this->error = 'ErrorFailToDeleteDir';
1575 $error++;
1576 }
1577 }
1578 }
1579 }
1580
1581 // Remove extrafields
1582 if (!$error) {
1583 $result = $this->deleteExtraFields();
1584 if ($result < 0) {
1585 $error++;
1586 dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
1587 }
1588 }
1589
1590 if (!$error) {
1591 dol_syslog(get_class($this)."::delete $this->id by $user->id", LOG_DEBUG);
1592 $this->db->commit();
1593 return 1;
1594 } else {
1595 $this->error = $this->db->lasterror();
1596 $this->db->rollback();
1597 return -$error;
1598 }
1599 }
1600
1601
1602 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1613 public function set_paid($user, $close_code = '', $close_note = '')
1614 {
1615 // phpcs:enable
1616 dol_syslog(get_class($this)."::set_paid is deprecated, use setPaid instead", LOG_NOTICE);
1617 return $this->setPaid($user, $close_code, $close_note);
1618 }
1619
1628 public function setPaid($user, $close_code = '', $close_note = '')
1629 {
1630 $error = 0;
1631
1632 if ($this->paid != 1) {
1633 $this->db->begin();
1634
1635 $now = dol_now();
1636
1637 dol_syslog("FactureFournisseur::setPaid", LOG_DEBUG);
1638
1639 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture_fourn SET';
1640 $sql .= ' fk_statut = '.self::STATUS_CLOSED;
1641 if (!$close_code) {
1642 $sql .= ', paye=1';
1643 }
1644 if ($close_code) {
1645 $sql .= ", close_code='".$this->db->escape($close_code)."'";
1646 }
1647 if ($close_note) {
1648 $sql .= ", close_note='".$this->db->escape($close_note)."'";
1649 }
1650 $sql .= ', fk_user_closing = '.((int) $user->id);
1651 $sql .= ", date_closing = '".$this->db->idate($now)."'";
1652 $sql .= ' WHERE rowid = '.((int) $this->id);
1653
1654 $resql = $this->db->query($sql);
1655 if ($resql) {
1656 // Call trigger
1657 $result = $this->call_trigger('BILL_SUPPLIER_PAYED', $user);
1658 if ($result < 0) {
1659 $error++;
1660 }
1661 // End call triggers
1662 } else {
1663 $error++;
1664 $this->error = $this->db->error();
1665 dol_print_error($this->db);
1666 }
1667
1668 if (!$error) {
1669 $this->db->commit();
1670 return 1;
1671 } else {
1672 $this->db->rollback();
1673 return -1;
1674 }
1675 } else {
1676 return 0;
1677 }
1678 }
1679
1680 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1691 public function set_unpaid($user)
1692 {
1693 // phpcs:enable
1694 dol_syslog(get_class($this)."::set_unpaid is deprecated, use setUnpaid instead", LOG_NOTICE);
1695 return $this->setUnpaid($user);
1696 }
1697
1706 public function setUnpaid($user)
1707 {
1708 $error = 0;
1709
1710 $this->db->begin();
1711
1712 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture_fourn';
1713 $sql .= ' SET paye=0, fk_statut='.self::STATUS_VALIDATED.', close_code=null, close_note=null,';
1714 $sql .= ' date_closing=null,';
1715 $sql .= ' fk_user_closing=null';
1716 $sql .= ' WHERE rowid = '.((int) $this->id);
1717
1718 dol_syslog(get_class($this)."::set_unpaid", LOG_DEBUG);
1719 $resql = $this->db->query($sql);
1720 if ($resql) {
1721 // Call trigger
1722 $result = $this->call_trigger('BILL_SUPPLIER_UNPAYED', $user);
1723 if ($result < 0) {
1724 $error++;
1725 }
1726 // End call triggers
1727 } else {
1728 $error++;
1729 $this->error = $this->db->error();
1730 dol_print_error($this->db);
1731 }
1732
1733 if (!$error) {
1734 $this->db->commit();
1735 return 1;
1736 } else {
1737 $this->db->rollback();
1738 return -1;
1739 }
1740 }
1741
1752 public function setCanceled($user, $close_code = '', $close_note = '')
1753 {
1754 dol_syslog(get_class($this)."::setCanceled rowid=".((int) $this->id), LOG_DEBUG);
1755
1756 $this->db->begin();
1757
1758 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture_fourn SET';
1759 $sql .= ' fk_statut='.self::STATUS_ABANDONED;
1760 if ($close_code) {
1761 $sql .= ", close_code='".$this->db->escape($close_code)."'";
1762 }
1763 if ($close_note) {
1764 $sql .= ", close_note='".$this->db->escape($close_note)."'";
1765 }
1766 $sql .= " WHERE rowid = ".((int) $this->id);
1767
1768 $resql = $this->db->query($sql);
1769 if ($resql) {
1770 // Bound discounts are deducted from the invoice
1771 // as they have not been used since the invoice is abandoned.
1772 $sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
1773 $sql .= ' SET fk_invoice_supplier = NULL';
1774 $sql .= ' WHERE fk_invoice_supplier = '.((int) $this->id);
1775
1776 $resql = $this->db->query($sql);
1777 if ($resql) {
1778 // Call trigger
1779 $result = $this->call_trigger('BILL_SUPPLIER_CANCEL', $user);
1780 if ($result < 0) {
1781 $this->db->rollback();
1782 return -1;
1783 }
1784 // End call triggers
1785
1786 $this->db->commit();
1787 return 1;
1788 } else {
1789 $this->error = $this->db->error()." sql=".$sql;
1790 $this->db->rollback();
1791 return -1;
1792 }
1793 } else {
1794 $this->error = $this->db->error()." sql=".$sql;
1795 $this->db->rollback();
1796 return -2;
1797 }
1798 }
1799
1809 public function validate($user, $force_number = '', $idwarehouse = 0, $notrigger = 0)
1810 {
1811 global $mysoc, $conf, $langs;
1812
1813 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1814
1815 $now = dol_now();
1816
1817 $error = 0;
1818 dol_syslog(get_class($this).'::validate user='.$user->id.', force_number='.$force_number.', idwarehouse='.$idwarehouse);
1819
1820 // Force to have object complete for checks
1821 $this->fetch_thirdparty();
1822 $this->fetch_lines();
1823
1824 // Check parameters
1825 if ($this->status > self::STATUS_DRAFT) { // This is to avoid to validate twice (avoid errors on logs and stock management)
1826 dol_syslog(get_class($this)."::validate no draft status", LOG_WARNING);
1827 return 0;
1828 }
1829 if (preg_match('/^'.preg_quote($langs->trans("CopyOf").' ', '/').'/', $this->ref_supplier)) {
1830 $langs->load("errors");
1831 $this->error = $langs->trans("ErrorFieldFormat", $langs->transnoentities("RefSupplier")).'. '.$langs->trans('RemoveString', $langs->transnoentitiesnoconv("CopyOf"));
1832 return -1;
1833 }
1834 if (count($this->lines) <= 0) {
1835 $langs->load("errors");
1836 $this->error = $langs->trans("ErrorObjectMustHaveLinesToBeValidated", $this->ref);
1837 return -1;
1838 }
1839
1840 // Check for mandatory fields in thirdparty (defined into setup)
1841 if (!empty($this->thirdparty) && is_object($this->thirdparty)) {
1842 $array_to_check = array('IDPROF1', 'IDPROF2', 'IDPROF3', 'IDPROF4', 'IDPROF5', 'IDPROF6', 'EMAIL', 'ACCOUNTANCY_CODE_SUPPLIER');
1843 foreach ($array_to_check as $key) {
1844 $keymin = strtolower($key);
1845 if ($keymin == 'accountancy_code_supplier') {
1846 $keymin = 'code_compta_fournisseur';
1847 }
1848 if (!property_exists($this->thirdparty, $keymin)) {
1849 continue;
1850 }
1851 $vallabel = $this->thirdparty->$keymin;
1852
1853 $i = (int) preg_replace('/[^0-9]/', '', $key);
1854 if ($i > 0) {
1855 if ($this->thirdparty->isACompany()) {
1856 // Check for mandatory prof id (but only if country is other than ours)
1857 if ($mysoc->country_id > 0 && $this->thirdparty->country_id == $mysoc->country_id) {
1858 $idprof_mandatory = 'SOCIETE_'.$key.'_INVOICE_MANDATORY';
1859 if (!$vallabel && getDolGlobalString($idprof_mandatory)) {
1860 $langs->load("errors");
1861 $this->error = $langs->trans('ErrorProdIdIsMandatory', $langs->transcountry('ProfId'.$i, $this->thirdparty->country_code)).' ('.$langs->trans("ForbiddenBySetupRules").') ['.$langs->trans('Company').' : '.$this->thirdparty->name.']';
1862 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
1863 return -1;
1864 }
1865 }
1866 }
1867 } else {
1868 if ($key == 'EMAIL') {
1869 // Check for mandatory
1870 if (getDolGlobalString('SOCIETE_EMAIL_INVOICE_MANDATORY') && !isValidEmail($this->thirdparty->email)) {
1871 $langs->load("errors");
1872 $this->error = $langs->trans("ErrorBadEMail", $this->thirdparty->email).' ('.$langs->trans("ForbiddenBySetupRules").') ['.$langs->trans('Company').' : '.$this->thirdparty->name.']';
1873 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
1874 return -1;
1875 }
1876 } elseif ($key == 'ACCOUNTANCY_CODE_SUPPLIER') {
1877 // Check for mandatory
1878 if (getDolGlobalString('SOCIETE_ACCOUNTANCY_CODE_SUPPLIER_INVOICE_MANDATORY') && empty($this->thirdparty->code_compta_fournisseur)) {
1879 $langs->load("errors");
1880 $this->error = $langs->trans("ErrorAccountancyCodeSupplierIsMandatory", $this->thirdparty->name).' ('.$langs->trans("ForbiddenBySetupRules").')';
1881 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
1882 return -1;
1883 }
1884 }
1885 }
1886 }
1887 }
1888
1889 $this->db->begin();
1890
1891 // Define new ref
1892 if ($force_number) {
1893 $num = $force_number;
1894 } elseif (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
1895 $num = $this->getNextNumRef($this->thirdparty);
1896 } else {
1897 $num = (string) $this->ref;
1898 }
1899 $this->newref = dol_sanitizeFileName($num);
1900
1901 $sql = "UPDATE ".MAIN_DB_PREFIX."facture_fourn";
1902 $sql .= " SET ref='".$this->db->escape($num)."', fk_statut = 1, fk_user_valid = ".((int) $user->id).", date_valid = '".$this->db->idate($now)."'";
1903 $sql .= " WHERE rowid = ".((int) $this->id);
1904
1905 dol_syslog(get_class($this)."::validate", LOG_DEBUG);
1906 $resql = $this->db->query($sql);
1907 if ($resql) {
1908 // Si on incrémente le produit principal et ses composants à la validation de facture fournisseur
1909 if (isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL')) {
1910 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
1911 $langs->load("agenda");
1912
1913 $cpt = count($this->lines);
1914 for ($i = 0; $i < $cpt; $i++) {
1915 if ($this->lines[$i]->fk_product > 0) {
1916 $mouvP = new MouvementStock($this->db);
1917 $mouvP->origin = &$this;
1918 $mouvP->setOrigin($this->element, $this->id);
1919 // We increase stock for product
1920 $up_ht_disc = $this->lines[$i]->subprice;
1921 if (!empty($this->lines[$i]->remise_percent) && !getDolGlobalString('STOCK_EXCLUDE_DISCOUNT_FOR_PMP')) {
1922 $up_ht_disc = price2num($up_ht_disc * (100 - $this->lines[$i]->remise_percent) / 100, 'MU');
1923 }
1925 $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $up_ht_disc, $langs->trans("InvoiceValidatedInDolibarr", $num));
1926 } else {
1927 $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $up_ht_disc, $langs->trans("InvoiceValidatedInDolibarr", $num));
1928 }
1929 if ($result < 0) {
1930 $this->setErrorsFromObject($mouvP);
1931 return -2;
1932 }
1933 }
1934 }
1935 }
1936
1937 // Triggers call
1938 if (empty($notrigger)) {
1939 // Call trigger
1940 $result = $this->call_trigger('BILL_SUPPLIER_VALIDATE', $user);
1941 if ($result < 0) {
1942 $error++;
1943 }
1944 // End call triggers
1945 }
1946
1947 if (!$error) {
1948 $this->oldref = $this->ref;
1949
1950 // Rename directory if dir was a temporary ref
1951 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
1952 // Now we rename also files into index
1953 $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)."'";
1954 $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;
1955 $resql = $this->db->query($sql);
1956 if (!$resql) {
1957 $error++;
1958 $this->error = $this->db->lasterror();
1959 }
1960 $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)."'";
1961 $sql .= " WHERE filepath = 'fournisseur/facture/".get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier').$this->db->escape($this->ref)."' and entity = ".$conf->entity;
1962 $resql = $this->db->query($sql);
1963 if (!$resql) {
1964 $error++;
1965 $this->error = $this->db->lasterror();
1966 }
1967
1968 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
1969 $oldref = dol_sanitizeFileName($this->ref);
1970 $dirsource = $conf->fournisseur->facture->dir_output.'/'.get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier').$oldref;
1971 $dirdest = $conf->fournisseur->facture->dir_output.'/'.get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier').$this->newref;
1972 if (!$error && file_exists($dirsource)) {
1973 dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
1974
1975 if (@rename($dirsource, $dirdest)) {
1976 dol_syslog("Rename ok");
1977 // Rename docs starting with $oldref with $this->newref
1978 $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, '/'));
1979 foreach ($listoffiles as $fileentry) {
1980 $dirsource = $fileentry['name'];
1981 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $this->newref, $dirsource);
1982 $dirsource = $fileentry['path'].'/'.$dirsource;
1983 $dirdest = $fileentry['path'].'/'.$dirdest;
1984 @rename($dirsource, $dirdest);
1985 }
1986 }
1987 }
1988 }
1989 }
1990
1991 // Set new ref and define current status
1992 if (!$error) {
1993 $this->ref = $this->newref;
1994 $this->statut = self::STATUS_VALIDATED;
1996 //$this->date_validation=$now; this is stored into log table
1997 }
1998
1999 if (!$error) {
2000 $this->db->commit();
2001 return 1;
2002 } else {
2003 $this->db->rollback();
2004 return -1;
2005 }
2006 } else {
2007 $this->error = $this->db->error();
2008 $this->db->rollback();
2009 return -1;
2010 }
2011 }
2012
2021 public function setDraft($user, $idwarehouse = -1, $notrigger = 0)
2022 {
2023 // phpcs:enable
2024 global $conf, $langs;
2025
2026 $error = 0;
2027
2028 if ($this->status == self::STATUS_DRAFT) {
2029 dol_syslog(__METHOD__." already draft status", LOG_WARNING);
2030 return 0;
2031 }
2032
2033 dol_syslog(__METHOD__, LOG_DEBUG);
2034
2035 $this->db->begin();
2036
2037 $sql = "UPDATE ".MAIN_DB_PREFIX."facture_fourn";
2038 $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2039 $sql .= " WHERE rowid = ".((int) $this->id);
2040
2041 $result = $this->db->query($sql);
2042 if ($result) {
2043 $this->oldcopy = clone $this;
2044
2045 // Si on incremente le produit principal et ses composants a la validation de facture fournisseur, on decremente
2046 if ($result >= 0 && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL')) {
2047 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2048 $langs->load("agenda");
2049
2050 $cpt = count($this->lines);
2051 for ($i = 0; $i < $cpt; $i++) {
2052 if ($this->lines[$i]->fk_product > 0) {
2053 $mouvP = new MouvementStock($this->db);
2054 $mouvP->origin = &$this;
2055 $mouvP->setOrigin($this->element, $this->id);
2056 // We increase stock for product
2058 $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceBackToDraftInDolibarr", $this->ref));
2059 } else {
2060 $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceBackToDraftInDolibarr", $this->ref));
2061 }
2062 }
2063 }
2064 }
2065 // Triggers call
2066 if (empty($notrigger)) {
2067 // Call trigger
2068 $result = $this->call_trigger('BILL_SUPPLIER_UNVALIDATE', $user);
2069 if ($result < 0) {
2070 $error++;
2071 }
2072 // End call triggers
2073 }
2074 if ($error == 0) {
2075 $this->db->commit();
2076 return 1;
2077 } else {
2078 $this->db->rollback();
2079 return -1;
2080 }
2081 } else {
2082 $this->error = $this->db->error();
2083 $this->db->rollback();
2084 return -1;
2085 }
2086 }
2087
2088
2122 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)
2123 {
2124 global $langs, $mysoc;
2125
2126 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);
2127 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2128
2129 if ($this->status == self::STATUS_DRAFT) {
2130 // Clean parameters
2131 if (empty($remise_percent)) {
2132 $remise_percent = 0;
2133 }
2134 if (empty($qty)) {
2135 $qty = 0;
2136 }
2137 if (empty($info_bits)) {
2138 $info_bits = 0;
2139 }
2140 if (empty($rang)) {
2141 $rang = 0;
2142 }
2143 if (empty($fk_code_ventilation)) {
2144 $fk_code_ventilation = 0;
2145 }
2146 if (empty($txtva)) {
2147 $txtva = 0;
2148 }
2149 if (empty($txlocaltax1)) {
2150 $txlocaltax1 = 0;
2151 }
2152 if (empty($txlocaltax2)) {
2153 $txlocaltax2 = 0;
2154 }
2155
2156 $remise_percent = price2num($remise_percent);
2157 $qty = price2num($qty);
2158 $pu = price2num($pu);
2159 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
2160 $txtva = price2num($txtva); // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
2161 }
2162 $txlocaltax1 = price2num($txlocaltax1);
2163 $txlocaltax2 = price2num($txlocaltax2);
2164
2165 if ($date_start && $date_end && $date_start > $date_end) {
2166 $langs->load("errors");
2167 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
2168 return -1;
2169 }
2170
2171 $this->db->begin();
2172
2173 if ($fk_product > 0) {
2174 if (getDolGlobalInt('SUPPLIER_INVOICE_WITH_PREDEFINED_PRICES_ONLY') == 1) { // Not the common case
2175 // Check quantity is enough
2176 dol_syslog(get_class($this)."::addline we check supplier prices fk_product=".$fk_product." qty=".$qty." ref_supplier=".$ref_supplier);
2177 $prod = new ProductFournisseur($this->db);
2178 if ($prod->fetch($fk_product) > 0) {
2179 $product_type = $prod->type;
2180 $label = $prod->label;
2181 $fk_prod_fourn_price = 0;
2182
2183 // We use 'none' instead of $ref_supplier, because $ref_supplier may not exists anymore. So we will take the first supplier price ok.
2184 // If we want a dedicated supplier price, we must provide $fk_prod_fourn_price.
2185 $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
2186 if ($result > 0) {
2187 if (empty($pu)) {
2188 $pu = $prod->fourn_pu; // Unit price supplier price set by get_buyprice
2189 }
2190 $ref_supplier = $prod->ref_supplier; // Ref supplier price set by get_buyprice
2191 // is remise percent not keyed but present for the product we add it
2192 if ($remise_percent == 0 && $prod->remise_percent != 0) {
2193 $remise_percent = $prod->remise_percent;
2194 }
2195 }
2196 if ($result == 0) { // If result == 0, we failed to found the supplier reference price
2197 $langs->load("errors");
2198 $this->error = "Ref ".$prod->ref." ".$langs->trans("ErrorQtyTooLowForThisSupplier");
2199 $this->db->rollback();
2200 dol_syslog(get_class($this)."::addline we did not found supplier price, so we can't guess unit price");
2201 //$pu = $prod->fourn_pu; // We do not overwrite unit price
2202 //$ref = $prod->ref_fourn; // We do not overwrite ref supplier price
2203 return -1;
2204 }
2205 if ($result == -1) {
2206 $langs->load("errors");
2207 $this->error = "Ref ".$prod->ref." ".$langs->trans("ErrorQtyTooLowForThisSupplier");
2208 $this->db->rollback();
2209 dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_DEBUG);
2210 return -1;
2211 }
2212 if ($result < -1) {
2213 $this->error = $prod->error;
2214 $this->db->rollback();
2215 dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_ERR);
2216 return -1;
2217 }
2218 } else {
2219 $this->error = $prod->error;
2220 $this->db->rollback();
2221 return -1;
2222 }
2223 }
2224 } else {
2225 $product_type = $type;
2226 }
2227
2228 if (isModEnabled("multicurrency") && $pu_devise > 0) {
2229 $pu = 0;
2230 }
2231
2232 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
2233
2234 // Clean vat code
2235 $reg = array();
2236 $vat_src_code = '';
2237 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
2238 $vat_src_code = $reg[1];
2239 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
2240 }
2241
2242 // Calcul du total TTC et de la TVA pour la ligne a partir de
2243 // qty, pu, remise_percent et txtva
2244 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2245 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2246
2247 $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);
2248 $total_ht = $tabprice[0];
2249 $total_tva = $tabprice[1];
2250 $total_ttc = $tabprice[2];
2251 $total_localtax1 = $tabprice[9];
2252 $total_localtax2 = $tabprice[10];
2253 $pu_ht = $tabprice[3];
2254
2255 // MultiCurrency
2256 $multicurrency_total_ht = $tabprice[16];
2257 $multicurrency_total_tva = $tabprice[17];
2258 $multicurrency_total_ttc = $tabprice[18];
2259 $pu_ht_devise = $tabprice[19];
2260
2261 // Check parameters
2262 if ($type < 0) {
2263 return -1;
2264 }
2265
2266 if ($rang < 0) {
2267 $rangmax = $this->line_max();
2268 $rang = $rangmax + 1;
2269 }
2270
2271 // Insert line
2272 $supplierinvoiceline = new SupplierInvoiceLine($this->db);
2273
2274 $supplierinvoiceline->context = $this->context;
2275
2276 $supplierinvoiceline->fk_facture_fourn = $this->id;
2277 //$supplierinvoiceline->label=$label; // deprecated
2278 $supplierinvoiceline->desc = $desc;
2279 $supplierinvoiceline->ref_supplier = $ref_supplier;
2280
2281 $supplierinvoiceline->qty = ($this->type == self::TYPE_CREDIT_NOTE ? abs((float) $qty) : (float) $qty); // For credit note, quantity is always positive and unit price negative
2282 $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
2283
2284 $supplierinvoiceline->vat_src_code = $vat_src_code;
2285 $supplierinvoiceline->tva_tx = $txtva;
2286 $supplierinvoiceline->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
2287 $supplierinvoiceline->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
2288 $supplierinvoiceline->localtax1_type = empty($localtaxes_type[0]) ? 0 : $localtaxes_type[0];
2289 $supplierinvoiceline->localtax2_type = empty($localtaxes_type[2]) ? 0 : $localtaxes_type[2];
2290
2291 $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
2292 $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
2293 $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
2294 $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
2295 $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
2296
2297 $supplierinvoiceline->fk_product = $fk_product;
2298 $supplierinvoiceline->product_type = $type;
2299 $supplierinvoiceline->remise_percent = $remise_percent;
2300 $supplierinvoiceline->date_start = $date_start;
2301 $supplierinvoiceline->date_end = $date_end;
2302 $supplierinvoiceline->fk_code_ventilation = $fk_code_ventilation;
2303 $supplierinvoiceline->rang = $rang;
2304 $supplierinvoiceline->info_bits = $info_bits;
2305 $supplierinvoiceline->fk_remise_except = $fk_remise_except;
2306
2307
2308 $supplierinvoiceline->special_code = (int) $special_code;
2309 $supplierinvoiceline->fk_parent_line = $fk_parent_line;
2310 $supplierinvoiceline->origin = $this->origin;
2311 $supplierinvoiceline->origin_id = $origin_id;
2312 $supplierinvoiceline->fk_unit = $fk_unit;
2313
2314 // Multicurrency
2315 $supplierinvoiceline->fk_multicurrency = $this->fk_multicurrency;
2316 $supplierinvoiceline->multicurrency_code = $this->multicurrency_code;
2317 $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
2318
2319 $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
2320 $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
2321 $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
2322
2323 if (is_array($array_options) && count($array_options) > 0) {
2324 $supplierinvoiceline->array_options = $array_options;
2325 }
2326
2327 $result = $supplierinvoiceline->insert($notrigger);
2328 if ($result > 0) {
2329 // Update denormalized fields at the order level
2330 $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.
2331
2332 if ($result > 0) {
2333 if (!isset($this->context['createfromclone'])) {
2334 if (!empty($fk_parent_line)) {
2335 // Always reorder if child line
2336 $this->line_order(true, 'DESC');
2337 } elseif ($rang > 0 && $rang <= count($this->lines)) {
2338 // Update all rank of all other lines starting from the same $ranktouse
2339 $linecount = count($this->lines);
2340 for ($ii = $rang; $ii <= $linecount; $ii++) {
2341 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
2342 }
2343 }
2344
2345 $this->lines[] = $supplierinvoiceline;
2346 }
2347
2348 $this->db->commit();
2349 return $supplierinvoiceline->id;
2350 } else {
2351 $this->error = $this->db->error();
2352 $this->db->rollback();
2353 return -1;
2354 }
2355 } else {
2356 $this->error = $supplierinvoiceline->error;
2357 $this->errors = $supplierinvoiceline->errors;
2358 $this->db->rollback();
2359 return -2;
2360 }
2361 } else {
2362 return 0;
2363 }
2364 }
2365
2391 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)
2392 {
2393 global $mysoc, $langs;
2394
2395 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);
2396 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2397
2398 $pu = price2num($pu);
2399 $qty = price2num($qty);
2400 $remise_percent = (float) price2num($remise_percent);
2401 $pu_devise = price2num($pu_devise);
2402
2403 // Check parameters
2404 //if (! is_numeric($pu) || ! is_numeric($qty)) return -1;
2405 if ($type < 0) {
2406 return -1;
2407 }
2408
2409 if ($date_start && $date_end && $date_start > $date_end) {
2410 $langs->load("errors");
2411 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
2412 return -1;
2413 }
2414
2415 // Clean parameters
2416 if (empty($vatrate)) {
2417 $vatrate = 0;
2418 }
2419 if (empty($txlocaltax1)) {
2420 $txlocaltax1 = 0;
2421 }
2422 if (empty($txlocaltax2)) {
2423 $txlocaltax2 = 0;
2424 }
2425
2426 $txlocaltax1 = (float) price2num($txlocaltax1);
2427 $txlocaltax2 = (float) price2num($txlocaltax2);
2428
2429 // Calcul du total TTC et de la TVA pour la ligne a partir de
2430 // qty, pu, remise_percent et txtva
2431 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2432 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2433
2434 $localtaxes_type = getLocalTaxesFromRate($vatrate, 0, $mysoc, $this->thirdparty);
2435
2436 $reg = array();
2437
2438 // Clean vat code
2439 $vat_src_code = '';
2440 if (preg_match('/\‍((.*)\‍)/', (string) $vatrate, $reg)) {
2441 $vat_src_code = $reg[1];
2442 $vatrate = preg_replace('/\s*\‍(.*\‍)/', '', (string) $vatrate); // Remove code into vatrate.
2443 }
2444
2445 $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);
2446 $total_ht = $tabprice[0];
2447 $total_tva = $tabprice[1];
2448 $total_ttc = $tabprice[2];
2449 $pu_ht = $tabprice[3];
2450 $pu_tva = $tabprice[4];
2451 $pu_ttc = $tabprice[5];
2452 $total_localtax1 = $tabprice[9];
2453 $total_localtax2 = $tabprice[10];
2454
2455 // MultiCurrency
2456 $multicurrency_total_ht = $tabprice[16];
2457 $multicurrency_total_tva = $tabprice[17];
2458 $multicurrency_total_ttc = $tabprice[18];
2459 $pu_ht_devise = $tabprice[19];
2460
2461 if (empty($info_bits)) {
2462 $info_bits = 0;
2463 }
2464
2465 // Fetch current line from the database and then clone the object and set it in $oldline property
2466 $line = new SupplierInvoiceLine($this->db);
2467 $line->fetch($id);
2468 $line->fetch_optionals();
2469
2470 $staticline = clone $line;
2471
2472 if ($idproduct) {
2473 $product = new Product($this->db);
2474 $result = $product->fetch($idproduct);
2475 $product_type = $product->type;
2476 } else {
2477 $idproduct = $staticline->fk_product;
2478 $product_type = $type;
2479 }
2480
2481 $line->oldline = $staticline;
2482 $line->context = $this->context;
2483
2484 $line->description = $desc;
2485 $line->desc = $desc;
2486
2487 $line->qty = ($this->type == self::TYPE_CREDIT_NOTE ? abs((float) $qty) : (float) $qty); // For credit note, quantity is always positive and unit price negative
2488
2489 $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
2490 $line->pu_ht = $line->subprice; // deprecated
2491 $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
2492 $line->pu_ttc = $line->subprice_ttc; // deprecated
2493
2494 $line->remise_percent = $remise_percent;
2495 $line->ref_supplier = $ref_supplier;
2496
2497 $line->date_start = $date_start;
2498 $line->date_end = $date_end;
2499
2500 $line->vat_src_code = $vat_src_code;
2501 $line->tva_tx = $vatrate;
2502 $line->localtax1_tx = $txlocaltax1;
2503 $line->localtax2_tx = $txlocaltax2;
2504 $line->localtax1_type = empty($localtaxes_type[0]) ? 0 : $localtaxes_type[0];
2505 $line->localtax2_type = empty($localtaxes_type[2]) ? 0 : $localtaxes_type[2];
2506
2507 $line->total_ht = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $total_ht) : (float) $total_ht);
2508 $line->total_tva = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $total_tva) : (float) $total_tva);
2509 $line->total_localtax1 = (float) $total_localtax1;
2510 $line->total_localtax2 = (float) $total_localtax2;
2511 $line->total_ttc = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $total_ttc) : (float) $total_ttc);
2512
2513 $line->fk_product = $idproduct;
2514 $line->product_type = $product_type;
2515 $line->info_bits = $info_bits;
2516 $line->fk_unit = $fk_unit;
2517 $line->rang = $rang;
2518
2519 if (is_array($array_options) && count($array_options) > 0) {
2520 // We replace values in this->line->array_options only for entries defined into $array_options
2521 foreach ($array_options as $key => $value) {
2522 $line->array_options[$key] = $array_options[$key];
2523 }
2524 }
2525
2526 // Multicurrency
2527 $line->multicurrency_subprice = (float) $pu_ht_devise;
2528 $line->multicurrency_total_ht = (float) $multicurrency_total_ht;
2529 $line->multicurrency_total_tva = (float) $multicurrency_total_tva;
2530 $line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
2531
2532 $res = $line->update($notrigger);
2533
2534 if ($res < 1) {
2535 $this->setErrorsFromObject($line);
2536 } else {
2537 // Update total price into invoice record
2538 $res = $this->update_price(1, 'auto', 0, $this->thirdparty);
2539 }
2540
2541 return $res;
2542 }
2543
2551 public function deleteLine($rowid, $notrigger = 0)
2552 {
2553 if (!$rowid) {
2554 $rowid = $this->id;
2555 }
2556
2557 $this->db->begin();
2558
2559 // Free the discount linked to a line of invoice
2560 $sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
2561 $sql .= ' SET fk_invoice_supplier_line = NULL';
2562 $sql .= ' WHERE fk_invoice_supplier_line = '.((int) $rowid);
2563
2564 dol_syslog(get_class($this)."::deleteline", LOG_DEBUG);
2565 $result = $this->db->query($sql);
2566 if (!$result) {
2567 $this->error = $this->db->error();
2568 $this->db->rollback();
2569 return -2;
2570 }
2571
2572 $line = new SupplierInvoiceLine($this->db);
2573
2574 if ($line->fetch($rowid) < 1) {
2575 return -1;
2576 }
2577
2578 $res = $line->delete($notrigger);
2579
2580 if ($res < 1) {
2581 $this->errors[] = $line->error;
2582 $this->db->rollback();
2583 return -3;
2584 } else {
2585 $res = $this->update_price(1);
2586
2587 if ($res > 0) {
2588 $this->db->commit();
2589 return 1;
2590 } else {
2591 $this->db->rollback();
2592 $this->error = $this->db->lasterror();
2593 return -4;
2594 }
2595 }
2596 }
2597
2598
2605 public function info($id)
2606 {
2607 $sql = 'SELECT c.rowid, datec, tms as datem, ';
2608 $sql .= ' fk_user_author, fk_user_modif, fk_user_valid';
2609 $sql .= ' FROM '.MAIN_DB_PREFIX.'facture_fourn as c';
2610 $sql .= ' WHERE c.rowid = '.((int) $id);
2611
2612 $result = $this->db->query($sql);
2613 if ($result) {
2614 if ($this->db->num_rows($result)) {
2615 $obj = $this->db->fetch_object($result);
2616
2617 $this->id = $obj->rowid;
2618
2619 $this->user_creation_id = $obj->fk_user_author;
2620 $this->user_validation_id = $obj->fk_user_valid;
2621 $this->user_modification_id = $obj->fk_user_modif;
2622 $this->date_creation = $this->db->jdate($obj->datec);
2623 $this->date_modification = $this->db->jdate($obj->datem);
2624 //$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)
2625 }
2626 $this->db->free($result);
2627 } else {
2628 dol_print_error($this->db);
2629 }
2630 }
2631
2632 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2641 public function list_replacable_supplier_invoices($socid = 0)
2642 {
2643 // phpcs:enable
2644 global $conf;
2645
2646 $return = array();
2647
2648 $sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut,";
2649 $sql .= " ff.rowid as rowidnext";
2650 $sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn as f";
2651 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."paiementfourn_facturefourn as pf ON f.rowid = pf.fk_facturefourn";
2652 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facture_fourn as ff ON f.rowid = ff.fk_facture_source";
2653 $sql .= " WHERE (f.fk_statut = ".self::STATUS_VALIDATED." OR (f.fk_statut = ".self::STATUS_ABANDONED." AND f.close_code = '".self::CLOSECODE_ABANDONED."'))";
2654 $sql .= " AND f.entity = ".$conf->entity;
2655 $sql .= " AND f.paye = 0"; // Not closed completely
2656 $sql .= " AND pf.fk_paiementfourn IS NULL"; // No payment already done
2657 $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)
2658 if ($socid > 0) {
2659 $sql .= " AND f.fk_soc = ".((int) $socid);
2660 }
2661 $sql .= " ORDER BY f.ref";
2662
2663 dol_syslog(get_class($this)."::list_replacable_supplier_invoices", LOG_DEBUG);
2664 $resql = $this->db->query($sql);
2665 if ($resql) {
2666 while ($obj = $this->db->fetch_object($resql)) {
2667 $return[$obj->rowid] = array(
2668 'id' => $obj->rowid,
2669 'ref' => $obj->ref,
2670 'status' => $obj->fk_statut
2671 );
2672 }
2673 //print_r($return);
2674 return $return;
2675 } else {
2676 $this->error = $this->db->error();
2677 return -1;
2678 }
2679 }
2680
2681 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2690 public function list_qualified_avoir_supplier_invoices($socid = 0)
2691 {
2692 // phpcs:enable
2693 global $conf;
2694
2695 $return = array();
2696
2697 $sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut, f.type, f.subtype, f.paye as paid, pf.fk_paiementfourn";
2698 $sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn as f";
2699 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."paiementfourn_facturefourn as pf ON f.rowid = pf.fk_facturefourn";
2700 $sql .= " WHERE f.entity = ".$conf->entity;
2701 $sql .= " AND f.fk_statut in (".self::STATUS_VALIDATED.",".self::STATUS_CLOSED.")";
2702 $sql .= " AND NOT EXISTS (SELECT rowid from ".MAIN_DB_PREFIX."facture_fourn as ff WHERE f.rowid = ff.fk_facture_source";
2703 $sql .= " AND ff.type=".self::TYPE_REPLACEMENT.")";
2704 $sql .= " AND f.type != ".self::TYPE_CREDIT_NOTE; // Type non 2 si facture non avoir
2705 if ($socid > 0) {
2706 $sql .= " AND f.fk_soc = ".((int) $socid);
2707 }
2708 $sql .= " ORDER BY f.ref";
2709
2710 dol_syslog(get_class($this)."::list_qualified_avoir_supplier_invoices", LOG_DEBUG);
2711 $resql = $this->db->query($sql);
2712 if ($resql) {
2713 while ($obj = $this->db->fetch_object($resql)) {
2714 $qualified = 0;
2715 if ($obj->fk_statut == self::STATUS_VALIDATED) {
2716 $qualified = 1;
2717 }
2718 if ($obj->fk_statut == self::STATUS_CLOSED) {
2719 $qualified = 1;
2720 }
2721 if ($qualified) {
2722 $paymentornot = ($obj->fk_paiementfourn ? 1 : 0);
2723 $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);
2724 }
2725 }
2726
2727 return $return;
2728 } else {
2729 $this->error = $this->db->error();
2730 return -1;
2731 }
2732 }
2733
2734 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2741 public function load_board($user)
2742 {
2743 // phpcs:enable
2744 global $conf, $langs;
2745
2746 $sql = 'SELECT ff.rowid, ff.date_lim_reglement as datefin, ff.fk_statut as status, ff.total_ht, ff.total_ttc';
2747 $sql .= ' FROM '.MAIN_DB_PREFIX.'facture_fourn as ff';
2748 if (empty($user->socid) && !$user->hasRight("societe", "client", "voir")) {
2749 $sql .= " JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON ff.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
2750 }
2751 $sql .= ' WHERE ff.paye = 0';
2752 $sql .= " AND ff.fk_statut IN (".self::STATUS_VALIDATED.")";
2753 $sql .= " AND ff.entity = ".$conf->entity;
2754 if ($user->socid) {
2755 $sql .= ' AND ff.fk_soc = '.((int) $user->socid);
2756 }
2757
2758 $resql = $this->db->query($sql);
2759 if ($resql) {
2760 $langs->load("bills");
2761 $now = dol_now();
2762
2763 $response = new WorkboardResponse();
2764 $response->warning_delay = $conf->facture->fournisseur->warning_delay / 60 / 60 / 24;
2765 $response->label = $langs->trans("SupplierBillsToPay");
2766 $response->labelShort = $langs->trans("StatusToPay");
2767
2768 $response->url = DOL_URL_ROOT.'/fourn/facture/list.php?search_status=1&mainmenu=billing&leftmenu=suppliers_bills';
2769 $response->img = img_object($langs->trans("Bills"), "bill");
2770
2771 $facturestatic = new FactureFournisseur($this->db);
2772
2773 while ($obj = $this->db->fetch_object($resql)) {
2774 $facturestatic->date_echeance = $this->db->jdate($obj->datefin);
2775 $facturestatic->statut = $obj->status; // For backward compatibility
2776 $facturestatic->status = $obj->status;
2777
2778 $response->nbtodo++;
2779 $response->total += $obj->total_ht;
2780
2781 if ($facturestatic->hasDelay()) {
2782 $response->nbtodolate++;
2783 $response->url_late = DOL_URL_ROOT.'/fourn/facture/list.php?search_option=late&mainmenu=billing&leftmenu=suppliers_bills';
2784 }
2785 }
2786
2787 $this->db->free($resql);
2788 return $response;
2789 } else {
2790 dol_print_error($this->db);
2791 $this->error = $this->db->error();
2792 return -1;
2793 }
2794 }
2795
2803 public function getTooltipContentArray($params)
2804 {
2805 global $conf, $langs, $mysoc;
2806
2807 $langs->load('bills');
2808
2809 $datas = [];
2810 $moretitle = $params['moretitle'] ?? '';
2811
2812 $picto = $this->picto;
2813 if ($this->type == self::TYPE_REPLACEMENT) {
2814 $picto .= 'r'; // Replacement invoice
2815 }
2816 if ($this->type == self::TYPE_CREDIT_NOTE) {
2817 $picto .= 'a'; // Credit note
2818 }
2819 if ($this->type == self::TYPE_DEPOSIT) {
2820 $picto .= 'd'; // Deposit invoice
2821 }
2822
2823 $datas['picto'] = img_picto('', $picto).' <u class="paddingrightonly">'.$langs->trans("SupplierInvoice").'</u>';
2824 if ($this->type == self::TYPE_REPLACEMENT) {
2825 $datas['picto'] .= '<u class="paddingrightonly">'.$langs->transnoentitiesnoconv("InvoiceReplacement").'</u>';
2826 } elseif ($this->type == self::TYPE_CREDIT_NOTE) {
2827 $datas['picto'] .= '<u class="paddingrightonly">'.$langs->transnoentitiesnoconv("CreditNote").'</u>';
2828 } elseif ($this->type == self::TYPE_DEPOSIT) {
2829 $datas['picto'] .= '<u class="paddingrightonly">'.$langs->transnoentitiesnoconv("Deposit").'</u>';
2830 }
2831 if (isset($this->status)) {
2832 $alreadypaid = -1;
2833 if (isset($this->totalpaid)) {
2834 $alreadypaid = $this->totalpaid;
2835 }
2836
2837 $datas['picto'] .= ' '.$this->getLibStatut(5, $alreadypaid);
2838 }
2839 if ($moretitle) {
2840 $datas['picto'] .= ' - '.$moretitle;
2841 }
2842 if (!empty($this->ref)) {
2843 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
2844 }
2845 if (!empty($this->ref_supplier)) {
2846 $datas['refsupplier'] = '<br><b>'.$langs->trans('RefSupplier').':</b> '.$this->ref_supplier;
2847 }
2848 if (!empty($this->label)) {
2849 $datas['label'] = '<br><b>'.$langs->trans('Label').':</b> '.$this->label;
2850 }
2851 if (!empty($this->date)) {
2852 $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
2853 }
2854 if (!empty($this->date_echeance)) {
2855 $datas['date_echeance'] = '<br><b>'.$langs->trans('DateDue').':</b> '.dol_print_date($this->date_echeance, 'day');
2856 }
2857 if (!empty($this->total_ht)) {
2858 $datas['amountht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
2859 }
2860 if (!empty($this->total_tva)) {
2861 $datas['totaltva'] = '<br><b>'.$langs->trans('AmountVAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
2862 }
2863 if (!empty($this->total_localtax1) && $this->total_localtax1 != 0) {
2864 // We keep test != 0 because $this->total_localtax1 can be '0.00000000'
2865 $datas['amountlt1'] = '<br><b>'.$langs->transcountry('AmountLT1', $mysoc->country_code).':</b> '.price($this->total_localtax1, 0, $langs, 0, -1, -1, $conf->currency);
2866 }
2867 if (!empty($this->total_localtax2) && $this->total_localtax2 != 0) {
2868 $datas['amountlt2'] = '<br><b>'.$langs->transcountry('AmountLT2', $mysoc->country_code).':</b> '.price($this->total_localtax2, 0, $langs, 0, -1, -1, $conf->currency);
2869 }
2870 if (!empty($this->revenuestamp)) {
2871 $datas['amountrevenustamp'] = '<br><b>'.$langs->trans('RevenueStamp').':</b> '.price($this->revenuestamp, 0, $langs, 0, -1, -1, $conf->currency);
2872 }
2873 if (!empty($this->total_ttc)) {
2874 $datas['totalttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
2875 }
2876 return $datas;
2877 }
2878
2892 public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $moretitle = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = 0)
2893 {
2894 global $langs, $conf, $user, $hookmanager;
2895
2896 $result = '';
2897
2898 if ($option == 'withdraw') {
2899 $url = DOL_URL_ROOT.'/compta/facture/prelevement.php?facid='.$this->id.'&type=bank-transfer';
2900 } elseif ($option == 'document') {
2901 $url = DOL_URL_ROOT.'/fourn/facture/document.php?facid='.$this->id;
2902 } else {
2903 $url = DOL_URL_ROOT.'/fourn/facture/card.php?facid='.$this->id;
2904 }
2905
2906 if ($short) {
2907 return $url;
2908 }
2909
2910 if ($option !== 'nolink') {
2911 // Add param to save lastsearch_values or not
2912 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
2913 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
2914 $add_save_lastsearch_values = 1;
2915 }
2916 if ($add_save_lastsearch_values) {
2917 $url .= '&save_lastsearch_values=1';
2918 }
2919 }
2920
2921 $picto = $this->picto;
2922 if ($this->type == self::TYPE_REPLACEMENT) {
2923 $picto .= 'r'; // Replacement invoice
2924 }
2925 if ($this->type == self::TYPE_CREDIT_NOTE) {
2926 $picto .= 'a'; // Credit note
2927 }
2928 if ($this->type == self::TYPE_DEPOSIT) {
2929 $picto .= 'd'; // Deposit invoice
2930 }
2931
2932 $params = [
2933 'id' => $this->id,
2934 'objecttype' => $this->element,
2935 'option' => $option,
2936 'moretitle' => $moretitle,
2937 ];
2938 $classfortooltip = 'classfortooltip';
2939 $dataparams = '';
2940 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
2941 $classfortooltip = 'classforajaxtooltip';
2942 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
2943 $label = '';
2944 } else {
2945 $label = implode($this->getTooltipContentArray($params));
2946 }
2947
2948 $ref = $this->ref;
2949 if (empty($ref)) {
2950 $ref = $this->id;
2951 }
2952
2953 $linkclose = '';
2954 if (empty($notooltip)) {
2955 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2956 $label = $langs->trans("ShowSupplierInvoice");
2957 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
2958 }
2959 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
2960 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
2961 }
2962
2963 $linkstart = '<a href="'.$url.'"';
2964 $linkstart .= $linkclose.'>';
2965 $linkend = '</a>';
2966
2967 $result .= $linkstart;
2968 if ($withpicto) {
2969 $result .= img_object(($notooltip ? '' : $label), ($picto ? $picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'"'), 0, 0, $notooltip ? 0 : 1);
2970 }
2971 if ($withpicto != 2) {
2972 $result .= ($max ? dol_trunc($ref, $max) : $ref);
2973 }
2974 $result .= $linkend;
2975
2976 if ($addlinktonotes) {
2977 $txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
2978 if ($txttoshow) {
2979 $notetoshow = $langs->trans("ViewPrivateNote").':<br>'.dol_string_nohtmltag($txttoshow, 1);
2980 $result .= ' <span class="note inline-block">';
2981 $result .= '<a href="'.DOL_URL_ROOT.'/fourn/facture/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($notetoshow).'">';
2982 $result .= img_picto('', 'note');
2983 $result .= '</a>';
2984 $result .= '</span>';
2985 }
2986 }
2987 global $action;
2988 $hookmanager->initHooks(array($this->element . 'dao'));
2989 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
2990 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2991 if ($reshook > 0) {
2992 $result = $hookmanager->resPrint;
2993 } else {
2994 $result .= $hookmanager->resPrint;
2995 }
2996 return $result;
2997 }
2998
3009 public function setCategories($categories)
3010 {
3011 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
3012 return parent::setCategoriesCommon($categories, Categorie::TYPE_SUPPLIER_INVOICE);
3013 }
3014
3023 public function getNextNumRef($soc, $mode = 'next')
3024 {
3025 global $db, $langs, $conf;
3026 $langs->load("orders");
3027
3028 // Clean parameters (if not defined or using deprecated value)
3029 if (!getDolGlobalString('INVOICE_SUPPLIER_ADDON_NUMBER')) {
3030 $conf->global->INVOICE_SUPPLIER_ADDON_NUMBER = 'mod_facture_fournisseur_cactus';
3031 }
3032
3033 $mybool = false;
3034
3035 $file = getDolGlobalString('INVOICE_SUPPLIER_ADDON_NUMBER') . ".php";
3036 $classname = getDolGlobalString('INVOICE_SUPPLIER_ADDON_NUMBER');
3037
3038 // Include file with class
3039 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3040
3041 foreach ($dirmodels as $reldir) {
3042 $dir = dol_buildpath($reldir."core/modules/supplier_invoice/");
3043
3044 // Load file with numbering class (if found)
3045 $mybool = ((bool) @include_once $dir.$file) || $mybool;
3046 }
3047
3048 if (!$mybool) {
3049 dol_print_error(null, "Failed to include file ".$file);
3050 return '';
3051 }
3052
3053 $obj = new $classname();
3054 '@phan-var-force ModeleNumRefSuppliersInvoices $obj';
3055 $numref = "";
3056 $numref = $obj->getNextValue($soc, $this, $mode);
3057
3058 if ($numref != "") {
3059 return $numref;
3060 } else {
3061 $this->error = $obj->error;
3062 return -1;
3063 }
3064 }
3065
3066
3075 public function initAsSpecimen($option = '')
3076 {
3077 global $langs, $conf;
3078 include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
3079
3080 $now = dol_now();
3081
3082 // Load array of products prodids
3083 $num_prods = 0;
3084 $prodids = array();
3085
3086 $sql = "SELECT rowid";
3087 $sql .= " FROM ".MAIN_DB_PREFIX."product";
3088 $sql .= " WHERE entity IN (".getEntity('product').")";
3089 $sql .= $this->db->plimit(100);
3090
3091 $resql = $this->db->query($sql);
3092 if ($resql) {
3093 $num_prods = $this->db->num_rows($resql);
3094 $i = 0;
3095 while ($i < $num_prods) {
3096 $i++;
3097 $row = $this->db->fetch_row($resql);
3098 $prodids[$i] = $row[0];
3099 }
3100 }
3101
3102 // Initialise parameters
3103 $this->id = 0;
3104 $this->ref = 'SPECIMEN';
3105 $this->ref_supplier = 'SUPPLIER_REF_SPECIMEN';
3106 $this->specimen = 1;
3107 $this->socid = 1;
3108 $this->date = $now;
3109 $this->date_lim_reglement = $this->date + 3600 * 24 * 30;
3110 $this->cond_reglement_code = 'RECEP';
3111 $this->mode_reglement_code = 'CHQ';
3112
3113 $this->note_public = 'This is a comment (public)';
3114 $this->note_private = 'This is a comment (private)';
3115
3116 $this->multicurrency_tx = 1;
3117 $this->multicurrency_code = $conf->currency;
3118
3119 $xnbp = 0;
3120 if (empty($option) || $option != 'nolines') {
3121 // Lines
3122 $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)
3123 while ($xnbp < $nbp) {
3124 $line = new SupplierInvoiceLine($this->db);
3125 $line->desc = $langs->trans("Description")." ".$xnbp;
3126 $line->qty = 1;
3127 $line->subprice = 100;
3128 $line->price = 100;
3129 $line->tva_tx = 19.6;
3130 $line->localtax1_tx = 0;
3131 $line->localtax2_tx = 0;
3132 if ($xnbp == 2) {
3133 $line->total_ht = 50;
3134 $line->total_ttc = 59.8;
3135 $line->total_tva = 9.8;
3136 $line->remise_percent = 50;
3137 } else {
3138 $line->total_ht = 100;
3139 $line->total_ttc = 119.6;
3140 $line->total_tva = 19.6;
3141 $line->remise_percent = 0;
3142 }
3143
3144 if ($num_prods > 0) {
3145 $prodid = mt_rand(1, $num_prods);
3146 $line->fk_product = $prodids[$prodid];
3147 }
3148 $line->product_type = 0;
3149
3150 $this->lines[$xnbp] = $line;
3151
3152 $this->total_ht += $line->total_ht;
3153 $this->total_tva += $line->total_tva;
3154 $this->total_ttc += $line->total_ttc;
3155
3156 $xnbp++;
3157 }
3158 }
3159
3160 $this->total_ht = $xnbp * 100;
3161 $this->total_tva = $xnbp * 19.6;
3162 $this->total_ttc = $xnbp * 119.6;
3163
3164 return 1;
3165 }
3166
3172 public function loadStateBoard()
3173 {
3174 global $conf, $user;
3175
3176 $this->nb = array();
3177
3178 $clause = "WHERE";
3179
3180 $sql = "SELECT count(f.rowid) as nb";
3181 $sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn as f";
3182 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON f.fk_soc = s.rowid";
3183 if (empty($user->socid) && !$user->hasRight("societe", "client", "voir")) {
3184 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3185 $sql .= " WHERE sc.fk_user = ".((int) $user->id);
3186 $clause = "AND";
3187 }
3188 $sql .= " ".$clause." f.entity = ".((int) $conf->entity);
3189
3190 $resql = $this->db->query($sql);
3191 if ($resql) {
3192 while ($obj = $this->db->fetch_object($resql)) {
3193 $this->nb["supplier_invoices"] = $obj->nb;
3194 }
3195 $this->db->free($resql);
3196 return 1;
3197 } else {
3198 dol_print_error($this->db);
3199 $this->error = $this->db->error();
3200 return -1;
3201 }
3202 }
3203
3212 public function createFromClone(User $user, $fromid, $invertdetail = 0)
3213 {
3214 global $conf, $langs;
3215
3216 $error = 0;
3217
3218 $object = new FactureFournisseur($this->db);
3219
3220 $this->db->begin();
3221
3222 // Load source object
3223 $object->fetch($fromid);
3224 $object->id = 0;
3225 $object->statut = self::STATUS_DRAFT; // For backward compatibility
3226 $object->status = self::STATUS_DRAFT;
3227
3228 $object->fetch_thirdparty(); // We need it to recalculate VAT localtaxes according to main sale taxes and vendor
3229
3230 // Clear fields
3231 $object->ref_supplier = (empty($this->ref_supplier) ? $langs->trans("CopyOf").' '.$object->ref_supplier : $this->ref_supplier);
3232 $object->author = $user->id; // FIXME? user_validation_id is replacement for author
3233 $object->user_validation_id = 0; // FIXME? user_validation_id is replacement for author
3234 $object->fk_facture_source = 0;
3235 $object->date_creation = '';
3236 $object->date_validation = '';
3237 $object->date = (empty($this->date) ? dol_now() : $this->date);
3238 $object->ref_client = '';
3239 $object->close_code = '';
3240 $object->close_note = '';
3241 if (getDolGlobalInt('MAIN_DONT_KEEP_NOTE_ON_CLONING') == 1) {
3242 $object->note_private = '';
3243 $object->note_public = '';
3244 }
3245
3246 $object->date_echeance = $object->calculate_date_lim_reglement();
3247
3248 // Loop on each line of new invoice
3249 foreach ($object->lines as $i => $line) {
3250 if (isset($object->lines[$i]->info_bits) && ($object->lines[$i]->info_bits & 0x02) == 0x02) { // We do not clone line of discounts
3251 unset($object->lines[$i]);
3252 }
3253 }
3254
3255 // Create clone
3256 $object->context['createfromclone'] = 'createfromclone';
3257 $result = $object->create($user);
3258
3259 // Other options
3260 if ($result < 0) {
3261 $this->error = $object->error;
3262 $this->errors = $object->errors;
3263 $error++;
3264 }
3265
3266 if (!$error) {
3267 }
3268
3269 unset($object->context['createfromclone']);
3270
3271 // End
3272 if (!$error) {
3273 $this->db->commit();
3274 return $object->id;
3275 } else {
3276 $this->db->rollback();
3277 return -1;
3278 }
3279 }
3280
3292 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3293 {
3294 global $langs;
3295
3296 $langs->load("suppliers");
3297 $outputlangs->load("products");
3298
3299 // Set the model on the model name to use
3300 if (empty($modele)) {
3301 if (getDolGlobalString('INVOICE_SUPPLIER_ADDON_PDF')) {
3302 $modele = getDolGlobalString('INVOICE_SUPPLIER_ADDON_PDF');
3303 } else {
3304 $modele = ''; // No default value. For supplier invoice, we allow to disable all PDF generation
3305 }
3306 } elseif ($modele == 'auto') {
3307 $modele = 'canelle';
3308 }
3309
3310 if (empty($modele)) {
3311 return 0;
3312 } else {
3313 $modelpath = "core/modules/supplier_invoice/doc/";
3314
3315 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3316 }
3317 }
3318
3323 public function getRights()
3324 {
3325 global $user;
3326
3327 return $user->hasRight("fournisseur", "facture");
3328 }
3329
3338 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
3339 {
3340 $tables = array(
3341 'facture_fourn'
3342 );
3343
3344 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
3345 }
3346
3355 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
3356 {
3357 $tables = array(
3358 'facture_fourn_det'
3359 );
3360
3361 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
3362 }
3363
3369 public function hasDelay()
3370 {
3371 global $conf;
3372
3373 $now = dol_now();
3374
3375 if (!$this->date_echeance) {
3376 return false;
3377 }
3378
3379 $status = isset($this->status) ? $this->status : $this->statut;
3380
3381 return ($status == self::STATUS_VALIDATED) && ($this->date_echeance < ($now - $conf->facture->fournisseur->warning_delay));
3382 }
3383
3389 public function isCreditNoteUsed()
3390 {
3391 $isUsed = false;
3392
3393 $sql = "SELECT fk_invoice_supplier FROM ".MAIN_DB_PREFIX."societe_remise_except WHERE fk_invoice_supplier_source = ".((int) $this->id);
3394 $resql = $this->db->query($sql);
3395 if (!empty($resql)) {
3396 $obj = $this->db->fetch_object($resql);
3397 if (!empty($obj->fk_invoice_supplier)) {
3398 $isUsed = true;
3399 }
3400 }
3401
3402 return $isUsed;
3403 }
3411 public function getKanbanView($option = '', $arraydata = null)
3412 {
3413 global $langs;
3414
3415 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
3416
3417 $picto = $this->picto;
3418 if ($this->type == self::TYPE_REPLACEMENT) {
3419 $picto .= 'r'; // Replacement invoice
3420 }
3421 if ($this->type == self::TYPE_CREDIT_NOTE) {
3422 $picto .= 'a'; // Credit note
3423 }
3424 if ($this->type == self::TYPE_DEPOSIT) {
3425 $picto .= 'd'; // Deposit invoice
3426 }
3427
3428 $return = '<div class="box-flex-item box-flex-grow-zero">';
3429 $return .= '<div class="info-box info-box-sm">';
3430 $return .= '<span class="info-box-icon bg-infobox-action">';
3431 $return .= img_picto('', $picto);
3432 $return .= '</span>';
3433 $return .= '<div class="info-box-content">';
3434 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . $this->getNomUrl(1) . '</span>';
3435 if ($selected >= 0) {
3436 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
3437 }
3438 if (!empty($arraydata['thirdparty'])) {
3439 $return .= '<br><span class="info-box-label">'.$arraydata['thirdparty'].'</span>';
3440 }
3441 if (!empty($this->date)) {
3442 $return .= '<br><span class="info-box-label">'.dol_print_date($this->date, 'day').'</span>';
3443 }
3444 if (!empty($this->total_ht)) {
3445 $return .= ' &nbsp; <span class="info-box-label amount" title="'.dol_escape_htmltag($langs->trans("AmountHT")).'">'.price($this->total_ht);
3446 $return .= ' '.$langs->trans("HT");
3447 $return .= '</span>';
3448 }
3449 $alreadypaid = (empty($arraydata['alreadypaid']) ? 0 : $arraydata['alreadypaid']);
3450 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3, $alreadypaid).'</div>';
3451
3452 $return .= '</div>';
3453 $return .= '</div>';
3454 $return .= '</div>';
3455
3456 return $return;
3457 }
3458
3465 public function setVATReverseCharge($vatreversecharge)
3466 {
3467 if (!$this->table_element) {
3468 dol_syslog(get_class($this)."::setVATReverseCharge was called on object with property table_element not defined", LOG_ERR);
3469 return -1;
3470 }
3471
3472 dol_syslog(get_class($this).'::setVATReverseCharge('.$vatreversecharge.')');
3473
3474 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
3475 $sql .= " SET vat_reverse_charge = ".((int) $vatreversecharge);
3476 $sql .= " WHERE rowid=".((int) $this->id);
3477
3478 if ($this->db->query($sql)) {
3479 $this->vat_reverse_charge = ($vatreversecharge == 0) ? 0 : 1;
3480 return 1;
3481 } else {
3482 dol_syslog(get_class($this).'::setVATReverseCharge Error ', LOG_DEBUG);
3483 $this->error = $this->db->error();
3484 return 0;
3485 }
3486 }
3487
3499 public function sendEmailsRemindersOnSupplierInvoiceDueDate($nbdays = 0, $paymentmode = 'all', $template = '', $datetouse = 'duedate', $forcerecipient = '')
3500 {
3501 global $conf, $langs, $user;
3502
3503 $this->output = '';
3504 $this->error = '';
3505 $nbMailSend = 0;
3506
3507 $error = 0;
3508 $errorsMsg = array();
3509
3510 $langs->load('bills');
3511
3512 if (!isModEnabled(!getDolGlobalString('MAIN_USE_NEW_SUPPLIERMOD') ? 'fournisseur' : 'supplier_invoice')) { // Should not happen. If module disabled, cron job should not be visible.
3513 $this->output .= $langs->trans('ModuleNotEnabled', $langs->transnoentitiesnoconv('Suppliers'));
3514 return 0;
3515 }
3516 if (!in_array($datetouse, array('duedate', 'invoicedate'))) {
3517 $this->output .= 'Bad value for parameter datetouse. Must be "duedate" or "invoicedate"';
3518 return 0;
3519 }
3520
3521 require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
3522 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php';
3523 require_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
3524 $formmail = new FormMail($this->db);
3525
3526 $now = dol_now();
3527 $tmpidate = dol_get_first_hour(dol_time_plus_duree($now, $nbdays, 'd'), 'gmt');
3528
3529 $tmpinvoice = new FactureFournisseur($this->db);
3530
3531 dol_syslog(__METHOD__." start", LOG_INFO);
3532
3533 // Select all action comm reminder
3534 $sql = "SELECT rowid as id FROM ".MAIN_DB_PREFIX."facture_fourn as f";
3535 if (!empty($paymentmode) && $paymentmode != 'all') {
3536 $sql .= ", ".MAIN_DB_PREFIX."c_paiement as cp";
3537 }
3538 $sql .= " WHERE f.paye = 0"; // Only unpaid
3539 $sql .= " AND f.fk_statut = ".self::STATUS_VALIDATED; // Only validated status
3540 if ($datetouse == 'invoicedate') {
3541 $sql .= " AND f.datef = '".$this->db->idate($tmpidate, 'gmt')."'";
3542 } else {
3543 $sql .= " AND f.date_lim_reglement = '".$this->db->idate($tmpidate, 'gmt')."'";
3544 }
3545 $sql .= " AND f.entity IN (".getEntity('supplier_invoice', 0).")"; // One batch process only one company (no sharing)
3546 if (!empty($paymentmode) && $paymentmode != 'all') {
3547 $sql .= " AND f.fk_mode_reglement = cp.id AND cp.code = '".$this->db->escape($paymentmode)."'";
3548 }
3549 // TODO Add a filter to check there is no payment started yet
3550 if ($datetouse == 'invoicedate') {
3551 $sql .= $this->db->order("datef", "ASC");
3552 } else {
3553 $sql .= $this->db->order("date_lim_reglement", "ASC");
3554 }
3555
3556 $resql = $this->db->query($sql);
3557
3558 $stmpidate = dol_print_date($tmpidate, 'day', 'gmt');
3559 if ($datetouse == 'invoicedate') {
3560 $this->output .= $langs->transnoentitiesnoconv("SearchValidatedSupplierInvoicesWithDate", $stmpidate);
3561 } else {
3562 $this->output .= $langs->transnoentitiesnoconv("SearchUnpaidSupplierInvoicesWithDueDate", $stmpidate);
3563 }
3564 if (!empty($paymentmode) && $paymentmode != 'all') {
3565 $this->output .= ' ('.$langs->transnoentitiesnoconv("PaymentMode").' '.$paymentmode.')';
3566 }
3567 $this->output .= '<br>';
3568
3569 if ($resql) {
3570 while ($obj = $this->db->fetch_object($resql)) {
3571 if (!$error) {
3572 // Load event
3573 $res = $tmpinvoice->fetch($obj->id);
3574 if ($res > 0) {
3575 $tmpinvoice->fetch_thirdparty();
3576
3577 $outputlangs = new Translate('', $conf);
3578 if ($tmpinvoice->thirdparty->default_lang) {
3579 $outputlangs->setDefaultLang($tmpinvoice->thirdparty->default_lang);
3580 $outputlangs->loadLangs(array("main", "suppliers"));
3581 } else {
3582 $outputlangs = $langs;
3583 }
3584
3585 // Select email template according to language of recipient
3586 $templateId = 0;
3587 $templateLabel = '';
3588 if (empty($template) || $template == 'EmailTemplateCode') {
3589 $templateLabel = '(SendingReminderEmailOnUnpaidSupplierInvoice)';
3590 } else {
3591 if (is_numeric($template)) {
3592 $templateId = $template;
3593 } else {
3594 $templateLabel = $template;
3595 }
3596 }
3597
3598 $arraymessage = $formmail->getEMailTemplate($this->db, 'invoice_supplier_send', $user, $outputlangs, $templateId, 1, $templateLabel);
3599 if (is_numeric($arraymessage) && $arraymessage <= 0) {
3600 $langs->load("errors");
3601 $this->output .= $langs->trans('ErrorFailedToFindEmailTemplate', $template);
3602 return 0;
3603 }
3604
3605 // PREPARE EMAIL
3606 $errormesg = '';
3607
3608 // Make substitution in email content
3609 $substitutionarray = getCommonSubstitutionArray($outputlangs, 0, null, $tmpinvoice);
3610
3611 complete_substitutions_array($substitutionarray, $outputlangs, $tmpinvoice);
3612
3613 // Topic
3614 $sendTopic = make_substitutions(empty($arraymessage->topic) ? $outputlangs->transnoentitiesnoconv('InformationMessage') : $arraymessage->topic, $substitutionarray, $outputlangs, 1);
3615
3616 // Content
3617 $content = $outputlangs->transnoentitiesnoconv($arraymessage->content);
3618
3619 $sendContent = make_substitutions($content, $substitutionarray, $outputlangs, 1);
3620
3621 // Recipient
3622 $to = array();
3623 if ($forcerecipient) { // If a recipient was forced
3624 $to = array($forcerecipient);
3625 } else {
3626 $res = $tmpinvoice->fetch_thirdparty();
3627 $recipient = $tmpinvoice->thirdparty;
3628 if ($res > 0) {
3629 $tmparraycontact = $tmpinvoice->liste_contact(-1, 'internal', 0, 'SALESREPFOLL');
3630 if (is_array($tmparraycontact) && count($tmparraycontact) > 0) {
3631 foreach ($tmparraycontact as $data_email) {
3632 if (!empty($data_email['email'])) {
3633 $to[] = $data_email['email'];
3634 }
3635 }
3636 }
3637 if (empty($to) && !empty($recipient->email)) {
3638 $to[] = $recipient->email;
3639 }
3640 if (empty($to)) {
3641 $errormesg = "Failed to send remind to thirdparty id=".$tmpinvoice->socid.". No email defined for supplier invoice or customer.";
3642 $error++;
3643 }
3644 } else {
3645 $errormesg = "Failed to load recipient with thirdparty id=".$tmpinvoice->socid;
3646 $error++;
3647 }
3648 }
3649
3650 // Sender
3651 $from = getDolGlobalString('MAIN_MAIL_EMAIL_FROM');
3652 if (!empty($arraymessage->email_from)) { // If a sender is defined into template, we use it in priority
3653 $from = (string) $arraymessage->email_from;
3654 }
3655 if (empty($from)) {
3656 $errormesg = "Failed to get sender into global setup MAIN_MAIL_EMAIL_FROM";
3657 $error++;
3658 }
3659
3660 if (!$error && !empty($to)) {
3661 $this->db->begin();
3662
3663 $to = implode(',', $to);
3664 if (!empty($arraymessage->email_to)) { // If a recipient is defined into template, we add it
3665 $to = $to.','.$arraymessage->email_to;
3666 }
3667
3668 // Errors Recipient
3669 $errors_to = getDolGlobalString('MAIN_MAIL_ERRORS_TO');
3670
3671 $trackid = 'inv'.$tmpinvoice->id;
3672 $sendcontext = 'standard';
3673
3674 $email_tocc = '';
3675 if (!empty($arraymessage->email_tocc)) { // If a CC is defined into template, we use it
3676 $email_tocc = (string) $arraymessage->email_tocc;
3677 }
3678
3679 $email_tobcc = '';
3680 if (!empty($arraymessage->email_tobcc)) { // If a BCC is defined into template, we use it
3681 $email_tobcc = (string) $arraymessage->email_tobcc;
3682 }
3683
3684 // Mail Creation
3685 $cMailFile = new CMailFile($sendTopic, $to, $from, $sendContent, array(), array(), array(), $email_tocc, $email_tobcc, 0, 1, $errors_to, '', $trackid, '', $sendcontext, '');
3686
3687 // Sending Mail
3688 if ($cMailFile->sendfile()) {
3689 $nbMailSend++;
3690
3691 // Add a line into event table
3692 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
3693
3694 // Insert record of emails sent
3695 $actioncomm = new ActionComm($this->db);
3696
3697 $actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
3698 $actioncomm->socid = $tmpinvoice->thirdparty->id; // To link to a company
3699 $actioncomm->contact_id = 0;
3700
3701 $actioncomm->code = 'AC_EMAIL';
3702 $actioncomm->label = 'sendEmailsRemindersOnInvoiceDueDateOK (nbdays='.$nbdays.' paymentmode='.$paymentmode.' template='.$template.' datetouse='.$datetouse.' forcerecipient='.$forcerecipient.')';
3703 $actioncomm->note_private = $sendContent;
3704 $actioncomm->fk_project = $tmpinvoice->fk_project;
3705 $actioncomm->datep = dol_now();
3706 $actioncomm->datef = $actioncomm->datep;
3707 $actioncomm->percentage = -1; // Not applicable
3708 $actioncomm->authorid = $user->id; // User saving action
3709 $actioncomm->userownerid = $user->id; // Owner of action
3710 // Fields when action is an email (content should be added into note)
3711 $actioncomm->email_msgid = $cMailFile->msgid;
3712 $actioncomm->email_subject = $sendTopic;
3713 $actioncomm->email_from = $from;
3714 $actioncomm->email_sender = '';
3715 $actioncomm->email_to = $to;
3716 //$actioncomm->email_tocc = $sendtocc;
3717 //$actioncomm->email_tobcc = $sendtobcc;
3718 //$actioncomm->email_subject = $subject;
3719 $actioncomm->errors_to = $errors_to;
3720
3721 $actioncomm->elementtype = 'invoice_supplier';
3722 $actioncomm->elementid = $tmpinvoice->id;
3723 $actioncomm->fk_element = $tmpinvoice->id;
3724
3725 //$actioncomm->extraparams = $extraparams;
3726
3727 $actioncomm->create($user);
3728 } else {
3729 $errormesg = $cMailFile->error.' : '.$to;
3730 $error++;
3731
3732 // Add a line into event table
3733 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
3734
3735 // Insert record of emails sent
3736 $actioncomm = new ActionComm($this->db);
3737
3738 $actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
3739 $actioncomm->socid = $tmpinvoice->thirdparty->id; // To link to a company
3740 $actioncomm->contact_id = 0;
3741
3742 $actioncomm->code = 'AC_EMAIL';
3743 $actioncomm->label = 'sendEmailsRemindersOnInvoiceDueDateKO';
3744 $actioncomm->note_private = $errormesg;
3745 $actioncomm->fk_project = $tmpinvoice->fk_project;
3746 $actioncomm->datep = dol_now();
3747 $actioncomm->datef = $actioncomm->datep;
3748 $actioncomm->percentage = -1; // Not applicable
3749 $actioncomm->authorid = $user->id; // User saving action
3750 $actioncomm->userownerid = $user->id; // Owner of action
3751 // Fields when action is an email (content should be added into note)
3752 $actioncomm->email_msgid = $cMailFile->msgid;
3753 $actioncomm->email_from = $from;
3754 $actioncomm->email_sender = '';
3755 $actioncomm->email_to = $to;
3756 //$actioncomm->email_tocc = $sendtocc;
3757 //$actioncomm->email_tobcc = $sendtobcc;
3758 //$actioncomm->email_subject = $subject;
3759 $actioncomm->errors_to = $errors_to;
3760
3761 //$actioncomm->extraparams = $extraparams;
3762
3763 $actioncomm->create($user);
3764 }
3765
3766 $this->db->commit(); // We always commit
3767 }
3768
3769 if ($errormesg) {
3770 $errorsMsg[] = $errormesg;
3771 }
3772 } else {
3773 $errorsMsg[] = 'Failed to fetch record invoice with ID = '.$obj->id;
3774 $error++;
3775 }
3776 }
3777 }
3778 } else {
3779 $error++;
3780 }
3781
3782 if (!$error) {
3783 $this->output .= 'Nb of emails sent : '.$nbMailSend;
3784
3785 dol_syslog(__METHOD__." end - ".$this->output, LOG_INFO);
3786
3787 return 0;
3788 } else {
3789 $this->error = 'Nb of emails sent : '.$nbMailSend.', '.(empty($errorsMsg) ? $error : implode(', ', $errorsMsg));
3790
3791 dol_syslog(__METHOD__." end - ".$this->error, LOG_INFO);
3792
3793 return $error;
3794 }
3795 }
3796}
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