dolibarr 22.0.5
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
169 public $datec;
170
176 public $date_echeance;
177
182 public $amount = 0;
187 public $remise = 0;
188
193 public $tva;
194
195 // Warning: Do not set default value into property definition. it must stay null.
196 // For example to avoid to have substitution done when object is generic and not yet defined.
198 public $localtax1;
200 public $localtax2;
202 public $total_ht;
204 public $total_tva;
206 public $total_localtax1;
208 public $total_localtax2;
210 public $total_ttc;
211
217 public $note;
219 public $note_private;
221 public $note_public;
223 public $propalid;
224
228 public $fk_account; // default bank account
229
233 public $transport_mode_id;
234
238 public $vat_reverse_charge;
239
243 public $extraparams = array();
244
249 public $lines = array();
250
255 public $fournisseur;
256
258
261 public $fk_facture_source;
262
264 public $fac_rec;
266 public $fk_fac_rec_source;
267
268 public $fields = array(
269 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 10),
270 'ref' => array('type' => 'varchar(255)', 'label' => 'Ref', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'showoncombobox' => 1, 'position' => 15),
271 'ref_supplier' => array('type' => 'varchar(255)', 'label' => 'RefSupplier', 'enabled' => 1, 'visible' => -1, 'position' => 20),
272 'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 25, 'index' => 1),
273 'ref_ext' => array('type' => 'varchar(255)', 'label' => 'RefExt', 'enabled' => 1, 'visible' => 0, 'position' => 30),
274 'type' => array('type' => 'smallint(6)', 'label' => 'Type', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 35),
275 'subtype' => array('type' => 'smallint(6)', 'label' => 'InvoiceSubtype', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 36),
276 'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 'isModEnabled("societe")', 'visible' => -1, 'notnull' => 1, 'position' => 40),
277 'datec' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -1, 'position' => 45),
278 'datef' => array('type' => 'date', 'label' => 'Date', 'enabled' => 1, 'visible' => -1, 'position' => 50),
279 'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 55),
280 'libelle' => array('type' => 'varchar(255)', 'label' => 'Label', 'enabled' => 1, 'visible' => -1, 'position' => 60),
281 'paye' => array('type' => 'smallint(6)', 'label' => 'Paye', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 65),
282 'amount' => array('type' => 'double(24,8)', 'label' => 'Amount', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 70),
283 'remise' => array('type' => 'double(24,8)', 'label' => 'Discount', 'enabled' => 1, 'visible' => -1, 'position' => 75),
284 'close_code' => array('type' => 'varchar(16)', 'label' => 'CloseCode', 'enabled' => 1, 'visible' => -1, 'position' => 80),
285 'close_note' => array('type' => 'varchar(128)', 'label' => 'CloseNote', 'enabled' => 1, 'visible' => -1, 'position' => 85),
286 'tva' => array('type' => 'double(24,8)', 'label' => 'Tva', 'enabled' => 1, 'visible' => -1, 'position' => 90),
287 'localtax1' => array('type' => 'double(24,8)', 'label' => 'Localtax1', 'enabled' => 1, 'visible' => -1, 'position' => 95),
288 'localtax2' => array('type' => 'double(24,8)', 'label' => 'Localtax2', 'enabled' => 1, 'visible' => -1, 'position' => 100),
289 'total_ht' => array('type' => 'double(24,8)', 'label' => 'TotalHT', 'enabled' => 1, 'visible' => -1, 'position' => 105),
290 'total_tva' => array('type' => 'double(24,8)', 'label' => 'TotalVAT', 'enabled' => 1, 'visible' => -1, 'position' => 110),
291 'total_ttc' => array('type' => 'double(24,8)', 'label' => 'TotalTTC', 'enabled' => 1, 'visible' => -1, 'position' => 115),
292 'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => -1, 'position' => 125),
293 'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'position' => 130),
294 'fk_user_valid' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserValidation', 'enabled' => 1, 'visible' => -1, 'position' => 135),
295 'fk_facture_source' => array('type' => 'integer', 'label' => 'Fk facture source', 'enabled' => 1, 'visible' => -1, 'position' => 140),
296 'fk_projet' => array('type' => 'integer:Project:projet/class/project.class.php:1:fk_statut=1', 'label' => 'Project', 'enabled' => "isModEnabled('project')", 'visible' => -1, 'position' => 145),
297 'fk_account' => array('type' => 'integer', 'label' => 'Account', 'enabled' => 'isModEnabled("bank")', 'visible' => -1, 'position' => 150),
298 'fk_cond_reglement' => array('type' => 'integer', 'label' => 'PaymentTerm', 'enabled' => 1, 'visible' => -1, 'position' => 155),
299 'fk_mode_reglement' => array('type' => 'integer', 'label' => 'PaymentMode', 'enabled' => 1, 'visible' => -1, 'position' => 160),
300 'date_lim_reglement' => array('type' => 'date', 'label' => 'DateLimReglement', 'enabled' => 1, 'visible' => -1, 'position' => 165),
301 'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 170),
302 'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 175),
303 'model_pdf' => array('type' => 'varchar(255)', 'label' => 'ModelPdf', 'enabled' => 1, 'visible' => 0, 'position' => 180),
304 'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => -1, 'position' => 190),
305 'fk_incoterms' => array('type' => 'integer', 'label' => 'IncotermCode', 'enabled' => 1, 'visible' => -1, 'position' => 195),
306 'location_incoterms' => array('type' => 'varchar(255)', 'label' => 'IncotermLocation', 'enabled' => 1, 'visible' => -1, 'position' => 200),
307 'fk_multicurrency' => array('type' => 'integer', 'label' => 'MulticurrencyId', 'enabled' => 1, 'visible' => -1, 'position' => 205),
308 'multicurrency_code' => array('type' => 'varchar(255)', 'label' => 'MulticurrencyCode', 'enabled' => 1, 'visible' => -1, 'position' => 210),
309 'multicurrency_tx' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyRate', 'enabled' => 1, 'visible' => -1, 'position' => 215),
310 'multicurrency_total_ht' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyTotalHT', 'enabled' => 1, 'visible' => -1, 'position' => 220),
311 'multicurrency_total_tva' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyTotalVAT', 'enabled' => 1, 'visible' => -1, 'position' => 225),
312 'multicurrency_total_ttc' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyTotalTTC', 'enabled' => 1, 'visible' => -1, 'position' => 230),
313 'date_pointoftax' => array('type' => 'date', 'label' => 'Date pointoftax', 'enabled' => 1, 'visible' => -1, 'position' => 235),
314 'date_valid' => array('type' => 'date', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -1, 'position' => 240),
315 'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'Last main doc', 'enabled' => 1, 'visible' => -1, 'position' => 245),
316 'fk_statut' => array('type' => 'smallint(6)', 'label' => 'Status', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 500),
317 'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'position' => 900),
318 );
319
320
324 public $fk_user_valid;
325
329 const TYPE_STANDARD = 0;
330
335
340
344 const TYPE_DEPOSIT = 3;
345
349 const STATUS_DRAFT = 0;
350
355
363 const STATUS_CLOSED = 2;
364
373
374 const CLOSECODE_DISCOUNTVAT = 'discount_vat';
375 const CLOSECODE_BADCREDIT = 'badsupplier';
376 const CLOSECODE_ABANDONED = 'abandon';
377 const CLOSECODE_REPLACED = 'replaced';
378
384 public function __construct($db)
385 {
386 $this->db = $db;
387
388 $this->ismultientitymanaged = 1;
389 }
390
397 public function create($user)
398 {
399 global $langs, $conf, $hookmanager;
400
401 $error = 0;
402 $now = dol_now();
403
404 // Clean parameters
405 if (isset($this->ref_supplier)) {
406 $this->ref_supplier = trim($this->ref_supplier);
407 }
408 if (empty($this->type)) {
409 $this->type = self::TYPE_STANDARD;
410 }
411 if (empty($this->date)) {
412 $this->date = $now;
413 }
414
415 // Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
416 if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
417 list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date);
418 } else {
419 $this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
420 }
421 if (empty($this->fk_multicurrency)) {
422 $this->multicurrency_code = $conf->currency;
423 $this->fk_multicurrency = 0;
424 $this->multicurrency_tx = 1;
425 }
426 $this->entity = setEntity($this);
427
428 $this->db->begin();
429
430 // Defaults
431 $originaldatewhen = 0;
432 $nextdatewhen = 0;
433 $previousdaynextdatewhen = 0;
434 $_facrec = null;
435
436 // Create invoice from a template recurring invoice
437 if ($this->fac_rec > 0) {
438 $this->fk_fac_rec_source = $this->fac_rec;
439
440 require_once DOL_DOCUMENT_ROOT . '/fourn/class/fournisseur.facture-rec.class.php';
441 $_facrec = new FactureFournisseurRec($this->db);
442 $result = $_facrec->fetch($this->fac_rec);
443 $result = $_facrec->fetchObjectLinked(null, '', null, '', 'OR', 1, 'sourcetype', 0); // This load $_facrec->linkedObjectsIds
444
445 // Define some dates
446 if (!empty($_facrec->frequency)) {
447 $originaldatewhen = $_facrec->date_when;
448 $nextdatewhen = dol_time_plus_duree($originaldatewhen, $_facrec->frequency, $_facrec->unit_frequency);
449 $previousdaynextdatewhen = dol_time_plus_duree($nextdatewhen, -1, 'd');
450 $this->socid = $_facrec->socid;
451 }
452
453 $this->entity = $_facrec->entity; // Invoice created in same entity than template
454
455 // Fields coming from GUI
456 // @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
457 // set by posted page with $object->xxx = ... and this section should be removed.
458 $this->fk_project = GETPOSTINT('projectid') > 0 ? (GETPOSTINT('projectid')) : $_facrec->fk_project;
459 $this->note_public = GETPOST('note_public', 'restricthtml') ? GETPOST('note_public', 'restricthtml') : $_facrec->note_public;
460 $this->note_private = GETPOST('note_private', 'restricthtml') ? GETPOST('note_private', 'restricthtml') : $_facrec->note_private;
461 $this->model_pdf = GETPOST('model', 'alpha') ? GETPOST('model', 'alpha') : $_facrec->model_pdf;
462 $this->cond_reglement_id = GETPOSTINT('cond_reglement_id') > 0 ? (GETPOSTINT('cond_reglement_id')) : $_facrec->cond_reglement_id;
463 $this->mode_reglement_id = GETPOSTINT('mode_reglement_id') > 0 ? (GETPOSTINT('mode_reglement_id')) : $_facrec->mode_reglement_id;
464 $this->fk_account = GETPOST('fk_account') > 0 ? ((int) GETPOST('fk_account')) : $_facrec->fk_account;
465
466 // Set here to have this defined for substitution into notes, should be recalculated after adding lines to get same result
467 $this->total_ht = $_facrec->total_ht;
468 $this->total_ttc = $_facrec->total_ttc;
469
470 // Fields always coming from template
471 $this->fk_incoterms = $_facrec->fk_incoterms;
472 $this->location_incoterms = $_facrec->location_incoterms;
473
474 // Clean parameters
475 if (! $this->type) {
476 $this->type = self::TYPE_STANDARD;
477 }
478 $this->note_public = trim($this->note_public);
479 $this->note_private = trim($this->note_private);
480 $this->note_private = dol_concatdesc($this->note_private, $langs->trans("GeneratedFromRecurringInvoice", $_facrec->title));
481
482 $this->array_options = $_facrec->array_options;
483
484 if (! $this->mode_reglement_id) {
485 $this->mode_reglement_id = 0;
486 }
487 $this->status = self::STATUS_DRAFT;
488 $this->statut = self::STATUS_DRAFT; // deprecated
489
490 $this->linked_objects = $_facrec->linkedObjectsIds;
491 // We do not add link to template invoice or next invoice will be linked to all generated invoices
492 //$this->linked_objects['facturerec'][0] = $this->fac_rec;
493
494 $forceduedate = $this->calculate_date_lim_reglement();
495
496 // For recurring invoices, update date and number of last generation of recurring template invoice, before inserting new invoice
497 if ($_facrec->frequency > 0) {
498 $this->ref_supplier = trim($this->ref_supplier . '_' . ($_facrec->nb_gen_done + 1));
499 dol_syslog("This is a recurring invoice so we set date_last_gen and next date_when");
500 if (empty($_facrec->date_when)) {
501 $_facrec->date_when = $now;
502 }
503 $next_date = $_facrec->getNextDate(); // Calculate next date
504 $result = $_facrec->setValueFrom('date_last_gen', $now, '', 0, 'date', '', $user, '');
505 //$_facrec->setValueFrom('nb_gen_done', $_facrec->nb_gen_done + 1); // Not required, +1 already included into setNextDate when second param is 1.
506 $result = $_facrec->setNextDate($next_date, 1);
507 }
508
509 // Define lang of customer
510 $outputlangs = $langs;
511 $newlang = '';
512
513 if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang) && isset($this->thirdparty->default_lang)) {
514 $newlang = $this->thirdparty->default_lang; // for proposal, order, invoice, ...
515 }
516 if (getDolGlobalInt('MAIN_MULTILANGS') && empty($newlang) && property_exists($this, 'default_lang') && isset($this->default_lang)) { // @phan-suppress-current-line PhanUndeclaredProperty
517 $newlang = $this->default_lang; // for thirdparty @phan-suppress-current-line PhanUndeclaredProperty
518 }
519 if (!empty($newlang)) {
520 $outputlangs = new Translate("", $conf);
521 $outputlangs->setDefaultLang($newlang);
522 } // Array of possible substitutions (See also file mailing-send.php that should manage same substitutions)
523 $substitutionarray = getCommonSubstitutionArray($outputlangs, 0, null, $this);
524 $substitutionarray['__INVOICE_PREVIOUS_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%m');
525 $substitutionarray['__INVOICE_MONTH__'] = dol_print_date($this->date, '%m');
526 $substitutionarray['__INVOICE_NEXT_MONTH__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%m');
527 $substitutionarray['__INVOICE_PREVIOUS_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'm'), '%B');
528 $substitutionarray['__INVOICE_MONTH_TEXT__'] = dol_print_date($this->date, '%B');
529 $substitutionarray['__INVOICE_NEXT_MONTH_TEXT__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'm'), '%B');
530 $substitutionarray['__INVOICE_PREVIOUS_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, -1, 'y'), '%Y');
531 $substitutionarray['__INVOICE_YEAR__'] = dol_print_date($this->date, '%Y');
532 $substitutionarray['__INVOICE_NEXT_YEAR__'] = dol_print_date(dol_time_plus_duree($this->date, 1, 'y'), '%Y'); // Only for template invoice
533 $substitutionarray['__INVOICE_DATE_NEXT_INVOICE_BEFORE_GEN__'] = $originaldatewhen ? dol_print_date($originaldatewhen, 'dayhour') : '';
534 $substitutionarray['__INVOICE_DATE_NEXT_INVOICE_AFTER_GEN__'] = $nextdatewhen ? dol_print_date($nextdatewhen, 'dayhour') : '';
535 $substitutionarray['__INVOICE_PREVIOUS_DATE_NEXT_INVOICE_AFTER_GEN__'] = $previousdaynextdatewhen ? dol_print_date($previousdaynextdatewhen, 'dayhour') : '';
536 $substitutionarray['__INVOICE_COUNTER_CURRENT__'] = $_facrec->nb_gen_done;
537 $substitutionarray['__INVOICE_COUNTER_MAX__'] = $_facrec->nb_gen_max;
538
539 complete_substitutions_array($substitutionarray, $outputlangs);
540
541 $this->note_public = make_substitutions($this->note_public, $substitutionarray);
542 $this->note_private = make_substitutions($this->note_private, $substitutionarray);
543 }
544
545 // Define due date if not already defined
546 if (!empty($forceduedate)) {
547 $this->date_echeance = $forceduedate;
548 }
549
550 $sql = "INSERT INTO ".MAIN_DB_PREFIX."facture_fourn (";
551 $sql .= "ref";
552 $sql .= ", ref_supplier";
553 $sql .= ", ref_ext";
554 $sql .= ", entity";
555 $sql .= ", type";
556 $sql .= ", subtype";
557 $sql .= ", libelle";
558 $sql .= ", fk_soc";
559 $sql .= ", datec";
560 $sql .= ", datef";
561 $sql .= ", vat_reverse_charge";
562 $sql .= ", fk_projet";
563 $sql .= ", fk_cond_reglement";
564 $sql .= ", fk_mode_reglement";
565 $sql .= ", fk_account";
566 $sql .= ", note_private";
567 $sql .= ", note_public";
568 $sql .= ", fk_user_author";
569 $sql .= ", date_lim_reglement";
570 $sql .= ", fk_incoterms, location_incoterms";
571 $sql .= ", fk_multicurrency";
572 $sql .= ", multicurrency_code";
573 $sql .= ", multicurrency_tx";
574 $sql .= ", fk_facture_source";
575 $sql .= ", fk_fac_rec_source";
576 $sql .= ")";
577 $sql .= " VALUES (";
578 $sql .= "'(PROV)'";
579 $sql .= ", '".$this->db->escape($this->ref_supplier)."'";
580 $sql .= ", '".$this->db->escape($this->ref_ext)."'";
581 $sql .= ", ".((int) $this->entity);
582 $sql .= ", '".$this->db->escape((string) $this->type)."'";
583 $sql .= ", ".((int) $this->subtype);
584 $sql .= ", '".$this->db->escape(isset($this->label) ? $this->label : (isset($this->libelle) ? $this->libelle : ''))."'";
585 $sql .= ", ".((int) $this->socid);
586 $sql .= ", '".$this->db->idate($now)."'";
587 $sql .= ", '".$this->db->idate($this->date)."'";
588 $sql .= ", ".($this->vat_reverse_charge != '' ? ((int) $this->vat_reverse_charge) : 0);
589 $sql .= ", ".($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
590 $sql .= ", ".($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : "null");
591 $sql .= ", ".($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : "null");
592 $sql .= ", ".($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
593 $sql .= ", '".$this->db->escape($this->note_private)."'";
594 $sql .= ", '".$this->db->escape($this->note_public)."'";
595 $sql .= ", ".((int) $user->id).",";
596 $sql .= $this->date_echeance != '' ? "'".$this->db->idate($this->date_echeance)."'" : "null";
597 $sql .= ", ".(int) $this->fk_incoterms;
598 $sql .= ", '".$this->db->escape($this->location_incoterms)."'";
599 $sql .= ", ".(int) $this->fk_multicurrency;
600 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
601 $sql .= ", ".(float) $this->multicurrency_tx;
602 $sql .= ", ".($this->fk_facture_source ? ((int) $this->fk_facture_source) : "null");
603 $sql .= ", ".(isset($this->fk_fac_rec_source) ? $this->fk_fac_rec_source : "NULL");
604 $sql .= ")";
605
606 dol_syslog(get_class($this)."::create", LOG_DEBUG);
607 $resql = $this->db->query($sql);
608 if ($resql) {
609 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'facture_fourn');
610
611 // Update ref with new one
612 $this->ref = '(PROV'.$this->id.')';
613 $sql = 'UPDATE '.MAIN_DB_PREFIX."facture_fourn SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".((int) $this->id);
614
615 dol_syslog(get_class($this)."::create", LOG_DEBUG);
616 $resql = $this->db->query($sql);
617 if (!$resql) {
618 $error++;
619 }
620
621 if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
622 $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
623 }
624
625 // Add object linked
626 if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
627 foreach ($this->linked_objects as $origin => $tmp_origin_id) {
628 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, ...))
629 foreach ($tmp_origin_id as $origin_id) {
630 $ret = $this->add_object_linked($origin, $origin_id);
631 if (!$ret) {
632 dol_print_error($this->db);
633 $error++;
634 }
635 }
636 } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
637 $origin_id = $tmp_origin_id;
638 $ret = $this->add_object_linked($origin, $origin_id);
639 if (!$ret) {
640 dol_print_error($this->db);
641 $error++;
642 }
643 }
644 }
645 }
646
647 if (!$error && empty($this->fac_rec) && count($this->lines) && is_object($this->lines[0])) { // If this->lines is array of InvoiceLines (preferred mode)
648 dol_syslog("There is ".count($this->lines)." lines that are invoice lines objects");
649 foreach ($this->lines as $i => $val) {
650 $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'facture_fourn_det (fk_facture_fourn, special_code, fk_remise_except)';
651 $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').')';
652
653 $resql_insert = $this->db->query($sql);
654 if ($resql_insert) {
655 $idligne = $this->db->last_insert_id(MAIN_DB_PREFIX.'facture_fourn_det');
656
657 $res = $this->updateline(
658 $idligne,
659 $this->lines[$i]->desc ? $this->lines[$i]->desc : $this->lines[$i]->description,
660 $this->lines[$i]->subprice,
661 $this->lines[$i]->tva_tx.($this->lines[$i]->vat_src_code ? ' ('.$this->lines[$i]->vat_src_code.')' : ''),
662 $this->lines[$i]->localtax1_tx,
663 $this->lines[$i]->localtax2_tx,
664 $this->lines[$i]->qty,
665 $this->lines[$i]->fk_product,
666 'HT',
667 (!empty($this->lines[$i]->info_bits) ? $this->lines[$i]->info_bits : ''),
668 $this->lines[$i]->product_type,
669 $this->lines[$i]->remise_percent,
670 0,
671 $this->lines[$i]->date_start,
672 $this->lines[$i]->date_end,
673 $this->lines[$i]->array_options,
674 $this->lines[$i]->fk_unit,
675 $this->lines[$i]->multicurrency_subprice,
676 $this->lines[$i]->ref_supplier
677 );
678 } else {
679 $this->error = $this->db->lasterror();
680 $this->db->rollback();
681 return -5;
682 }
683 }
684 } elseif (!$error && empty($this->fac_rec)) { // If this->lines is an array of invoice line arrays
685 dol_syslog("There is ".count($this->lines)." lines that are array lines");
686 foreach ($this->lines as $i => $val) {
687 $line = $this->lines[$i];
688
689 // Test and convert into object this->lines[$i]. When coming from REST API, we may still have an array
690 //if (! is_object($line)) $line=json_decode(json_encode($line), false); // convert recursively array into object.
691 if (!is_object($line)) {
692 $line = (object) $line;
693 }
694
695 $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'facture_fourn_det (fk_facture_fourn, special_code, fk_remise_except)';
696 $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').')';
697
698 $resql_insert = $this->db->query($sql);
699 if ($resql_insert) {
700 $idligne = $this->db->last_insert_id(MAIN_DB_PREFIX.'facture_fourn_det');
701
702 $this->updateline(
703 $idligne,
704 $line->desc ? $line->desc : $line->description,
705 $line->subprice,
706 $line->tva_tx,
707 $line->localtax1_tx,
708 $line->localtax2_tx,
709 $line->qty,
710 $line->fk_product,
711 'HT',
712 (!empty($line->info_bits) ? $line->info_bits : ''),
713 $line->product_type,
714 $line->remise_percent,
715 0,
716 $line->date_start,
717 $line->date_end,
718 $line->array_options,
719 $line->fk_unit,
720 $line->multicurrency_subprice,
721 $line->ref_supplier
722 );
723 } else {
724 $this->error = $this->db->lasterror();
725 $this->db->rollback();
726 return -5;
727 }
728 }
729 }
730
731 /*
732 * Insert lines of template invoices
733 */
734 if (! $error && $this->fac_rec > 0 && $_facrec instanceof FactureFournisseurRec) {
735 foreach ($_facrec->lines as $i => $val) {
736 $product_type = $_facrec->lines[$i]->product_type;
737 if ($_facrec->lines[$i]->fk_product) {
738 $prod = new Product($this->db);
739 $res = $prod->fetch($_facrec->lines[$i]->fk_product);
740 }
741
742 // For line from template invoice, we use data from template invoice
743 /*
744 $tva_tx = get_default_tva($mysoc,$soc,$prod->id);
745 $tva_npr = get_default_npr($mysoc,$soc,$prod->id);
746 if (empty($tva_tx)) $tva_npr=0;
747 $localtax1_tx=get_localtax($tva_tx,1,$soc,$mysoc,$tva_npr);
748 $localtax2_tx=get_localtax($tva_tx,2,$soc,$mysoc,$tva_npr);
749 */
750 $tva_tx = $_facrec->lines[$i]->tva_tx . ($_facrec->lines[$i]->vat_src_code ? '(' . $_facrec->lines[$i]->vat_src_code . ')' : '');
751 $tva_npr = $_facrec->lines[$i]->info_bits;
752 if (empty($tva_tx)) {
753 $tva_npr = 0;
754 }
755 $localtax1_tx = $_facrec->lines[$i]->localtax1_tx;
756 $localtax2_tx = $_facrec->lines[$i]->localtax2_tx;
757
758 // $fk_product_fournisseur_price not used and does not exist on line
759 // $fk_product_fournisseur_price = empty($_facrec->lines[$i]->fk_product_fournisseur_price) ? null : $_facrec->lines[$i]->fk_product_fournisseur_price;
760 $buyprice = empty($_facrec->lines[$i]->buyprice) ? 0 : $_facrec->lines[$i]->buyprice;
761
762 // If buyprice not defined from template invoice, we try to guess the best value
763 if (! $buyprice && $_facrec->lines[$i]->fk_product > 0) {
764 require_once DOL_DOCUMENT_ROOT . '/fourn/class/fournisseur.product.class.php';
765 $producttmp = new ProductFournisseur($this->db);
766 $producttmp->fetch($_facrec->lines[$i]->fk_product);
767
768 // If margin module defined on costprice, we try the costprice
769 // If not defined or if module margin defined and pmp and stock module enabled, we try pmp price
770 // else we get the best supplier price
771 if (getDolGlobalString('MARGIN_TYPE') == 'costprice' && !empty($producttmp->cost_price)) {
772 $buyprice = $producttmp->cost_price;
773 } elseif (isModEnabled('stock') && (getDolGlobalString('MARGIN_TYPE') == 'costprice' || getDolGlobalString('MARGIN_TYPE') == 'pmp') && !empty($producttmp->pmp)) {
774 $buyprice = $producttmp->pmp;
775 } else {
776 if ($producttmp->find_min_price_product_fournisseur($_facrec->lines[$i]->fk_product) > 0) {
777 if ($producttmp->product_fourn_price_id > 0) {
778 $buyprice = price2num($producttmp->fourn_unitprice * (1 - $producttmp->fourn_remise_percent / 100) + $producttmp->fourn_remise, 'MU');
779 }
780 }
781 }
782 }
783
784 $result_insert = $this->addline(
785 $_facrec->lines[$i]->desc ? $_facrec->lines[$i]->desc : $_facrec->lines[$i]->description,
786 $_facrec->lines[$i]->pu_ht,
787 (float) $tva_tx,
788 $localtax1_tx,
789 $localtax2_tx,
790 $_facrec->lines[$i]->qty,
791 $_facrec->lines[$i]->fk_product,
792 $_facrec->lines[$i]->remise_percent,
793 ($_facrec->lines[$i]->date_start == 1 && $this->date) ? $this->date : '',
794 ($_facrec->lines[$i]->date_end == 1 && $previousdaynextdatewhen) ? $previousdaynextdatewhen : '',
795 0,
796 $_facrec->lines[$i]->info_bits,
797 'HT',
798 $product_type,
799 $_facrec->lines[$i]->rang,
800 0,
801 $_facrec->lines[$i]->array_options,
802 $_facrec->lines[$i]->fk_unit,
803 0,
804 0,
805 $_facrec->lines[$i]->ref_supplier,
806 $_facrec->lines[$i]->special_code,
807 0,
808 0
809 );
810 if ($result_insert < 0) {
811 $error++;
812 $this->error = $this->db->error();
813 break;
814 }
815 }
816 }
817
818
819 // Update total price
820 $result = $this->update_price(1);
821 if ($result > 0) {
822 // Actions on extra fields
823 if (!$error) {
824 $result = $this->insertExtraFields(); // This also set $this->error or $this->errors if errors are found
825 if ($result < 0) {
826 $error++;
827 }
828 }
829
830 if (!$error) {
831 // Call trigger
832 $result = $this->call_trigger('BILL_SUPPLIER_CREATE', $user);
833 if ($result < 0) {
834 $error++;
835 }
836 // End call triggers
837 }
838
839 if (!$error) {
840 $this->db->commit();
841 return $this->id;
842 } else {
843 $this->db->rollback();
844 return -4;
845 }
846 } else {
847 $this->error = $langs->trans('FailedToUpdatePrice');
848 $this->db->rollback();
849 return -3;
850 }
851 } else {
852 if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
853 $this->error = $langs->trans('ErrorRefAlreadyExists');
854 $this->db->rollback();
855 return -1;
856 } else {
857 $this->error = $this->db->lasterror();
858 $this->db->rollback();
859 return -2;
860 }
861 }
862 }
863
872 public function fetch($id = 0, $ref = '', $ref_ext = '')
873 {
874 if (empty($id) && empty($ref) && empty($ref_ext)) {
875 return -1;
876 }
877
878 $sql = "SELECT";
879 $sql .= " t.rowid,";
880 $sql .= " t.ref,";
881 $sql .= " t.ref_supplier,";
882 $sql .= " t.ref_ext,";
883 $sql .= " t.entity,";
884 $sql .= " t.type,";
885 $sql .= " t.subtype,";
886 $sql .= " t.fk_soc,";
887 $sql .= " t.datec,";
888 $sql .= " t.datef,";
889 $sql .= " t.tms,";
890 $sql .= " t.libelle as label,";
891 $sql .= " t.paye,";
892 $sql .= " t.close_code,";
893 $sql .= " t.close_note,";
894 $sql .= " t.tva,";
895 $sql .= " t.localtax1,";
896 $sql .= " t.localtax2,";
897 $sql .= " t.total_ht,";
898 $sql .= " t.total_tva,";
899 $sql .= " t.total_ttc,";
900 $sql .= " t.fk_statut as status,";
901 $sql .= " t.fk_user_author,";
902 $sql .= " t.fk_user_valid,";
903 $sql .= " t.fk_facture_source,";
904 $sql .= " t.vat_reverse_charge,";
905 $sql .= " t.fk_fac_rec_source,";
906 $sql .= " t.fk_projet as fk_project,";
907 $sql .= " t.fk_cond_reglement,";
908 $sql .= " t.fk_account,";
909 $sql .= " t.fk_mode_reglement,";
910 $sql .= " t.date_lim_reglement,";
911 $sql .= " t.note_private,";
912 $sql .= " t.note_public,";
913 $sql .= " t.model_pdf,";
914 $sql .= " t.last_main_doc,";
915 $sql .= " t.import_key,";
916 $sql .= " t.extraparams,";
917 $sql .= " cr.code as cond_reglement_code, cr.libelle as cond_reglement_label, cr.libelle_facture as cond_reglement_doc,";
918 $sql .= " p.code as mode_reglement_code, p.libelle as mode_reglement_label,";
919 $sql .= ' s.nom as socnom, s.rowid as socid,';
920 $sql .= ' t.fk_incoterms, t.location_incoterms,';
921 $sql .= " i.libelle as label_incoterms,";
922 $sql .= ' t.fk_transport_mode,';
923 $sql .= ' t.fk_multicurrency, t.multicurrency_code, t.multicurrency_tx, t.multicurrency_total_ht, t.multicurrency_total_tva, t.multicurrency_total_ttc';
924 $sql .= ' FROM '.MAIN_DB_PREFIX.'facture_fourn as t';
925 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON (t.fk_soc = s.rowid)";
926 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_payment_term as cr ON t.fk_cond_reglement = cr.rowid";
927 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_paiement as p ON t.fk_mode_reglement = p.id";
928 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON t.fk_incoterms = i.rowid';
929 if ($id) {
930 $sql .= " WHERE t.rowid = ".((int) $id);
931 } else {
932 $sql .= ' WHERE t.entity IN ('.getEntity('supplier_invoice').')'; // Don't use entity if you use rowid
933 if ($ref) {
934 $sql .= " AND t.ref = '".$this->db->escape($ref)."'";
935 }
936 if ($ref_ext) {
937 $sql .= " AND t.ref_ext = '".$this->db->escape($ref_ext)."'";
938 }
939 }
940
941 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
942 $resql = $this->db->query($sql);
943 if ($resql) {
944 if ($this->db->num_rows($resql)) {
945 $obj = $this->db->fetch_object($resql);
946
947 $this->id = $obj->rowid;
948 $this->ref = $obj->ref ? $obj->ref : $obj->rowid; // We take rowid if ref is empty for backward compatibility
949
950 $this->ref_supplier = $obj->ref_supplier;
951 $this->ref_ext = $obj->ref_ext;
952 $this->entity = $obj->entity;
953 $this->type = empty($obj->type) ? self::TYPE_STANDARD : $obj->type;
954 $this->subtype = (int) $obj->subtype;
955 $this->socid = $obj->fk_soc;
956 $this->datec = $this->db->jdate($obj->datec);
957 $this->date = $this->db->jdate($obj->datef);
958 //$this->datep = $this->db->jdate($obj->datef);
959 $this->tms = $this->db->jdate($obj->tms);
960 $this->libelle = $obj->label; // deprecated
961 $this->label = $obj->label;
962 $this->paye = $obj->paye;
963 $this->paid = $obj->paye;
964 $this->close_code = $obj->close_code;
965 $this->close_note = $obj->close_note;
966 $this->total_localtax1 = $obj->localtax1;
967 $this->total_localtax2 = $obj->localtax2;
968 $this->total_ht = $obj->total_ht;
969 $this->total_tva = $obj->total_tva;
970 $this->total_ttc = $obj->total_ttc;
971 $this->status = $obj->status;
972 $this->statut = $obj->status; // For backward compatibility
973 $this->fk_statut = $obj->status; // For backward compatibility
974 $this->user_creation_id = $obj->fk_user_author;
975 $this->author = $obj->fk_user_author; // deprecated
976 $this->user_validation_id = $obj->fk_user_valid;
977 $this->fk_facture_source = $obj->fk_facture_source;
978 $this->vat_reverse_charge = empty($obj->vat_reverse_charge) ? 0 : 1;
979 $this->fk_fac_rec_source = $obj->fk_fac_rec_source;
980 $this->fk_project = $obj->fk_project;
981 $this->cond_reglement_id = $obj->fk_cond_reglement;
982 $this->cond_reglement_code = $obj->cond_reglement_code;
983 $this->cond_reglement = $obj->cond_reglement_label; // deprecated
984 $this->cond_reglement_label = $obj->cond_reglement_label;
985 $this->cond_reglement_doc = $obj->cond_reglement_doc;
986 $this->fk_account = $obj->fk_account;
987 $this->mode_reglement_id = $obj->fk_mode_reglement;
988 $this->mode_reglement_code = $obj->mode_reglement_code;
989 $this->mode_reglement = $obj->mode_reglement_label;
990 $this->date_echeance = $this->db->jdate($obj->date_lim_reglement);
991 $this->note = $obj->note_private; // deprecated
992 $this->note_private = $obj->note_private;
993 $this->note_public = $obj->note_public;
994 $this->model_pdf = $obj->model_pdf;
995 $this->last_main_doc = $obj->last_main_doc;
996 $this->import_key = $obj->import_key;
997
998 //Incoterms
999 $this->fk_incoterms = $obj->fk_incoterms;
1000 $this->location_incoterms = $obj->location_incoterms;
1001 $this->label_incoterms = $obj->label_incoterms;
1002 $this->transport_mode_id = $obj->fk_transport_mode;
1003
1004 // Multicurrency
1005 $this->fk_multicurrency = $obj->fk_multicurrency;
1006 $this->multicurrency_code = $obj->multicurrency_code;
1007 $this->multicurrency_tx = $obj->multicurrency_tx;
1008 $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
1009 $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
1010 $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1011
1012 $this->extraparams = isset($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
1013
1014 $this->socid = $obj->socid;
1015
1016 $this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
1017
1018 // Retrieve all extrafield
1019 // fetch optionals attributes and labels
1020 $this->fetch_optionals();
1021
1022 $result = $this->fetch_lines();
1023 if ($result < 0) {
1024 $this->error = $this->db->lasterror();
1025 return -3;
1026 }
1027 } else {
1028 $this->error = 'Bill with id '.$id.' not found';
1029 dol_syslog(get_class($this).'::fetch '.$this->error);
1030 return 0;
1031 }
1032
1033 $this->db->free($resql);
1034 return 1;
1035 } else {
1036 $this->error = "Error ".$this->db->lasterror();
1037 return -1;
1038 }
1039 }
1040
1041
1042 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1048 public function fetch_lines()
1049 {
1050 // phpcs:enable
1051 $this->lines = array();
1052
1053 $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';
1054 $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';
1055 $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';
1056 $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';
1057 $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';
1058 $sql .= ' FROM '.MAIN_DB_PREFIX.'facture_fourn_det as f';
1059 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON f.fk_product = p.rowid';
1060 $sql .= ' WHERE fk_facture_fourn='.((int) $this->id);
1061 $sql .= ' ORDER BY f.rang, f.rowid';
1062
1063 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1064
1065 $resql_rows = $this->db->query($sql);
1066 if ($resql_rows) {
1067 $num_rows = $this->db->num_rows($resql_rows);
1068 if ($num_rows) {
1069 $i = 0;
1070 while ($i < $num_rows) {
1071 $obj = $this->db->fetch_object($resql_rows);
1072
1073 $line = new SupplierInvoiceLine($this->db);
1074
1075 $line->id = $obj->rowid;
1076 $line->rowid = $obj->rowid;
1077
1078 $line->description = $obj->line_desc;
1079 $line->desc = $obj->line_desc;
1080 $line->date_start = $this->db->jdate($obj->date_start);
1081 $line->date_end = $this->db->jdate($obj->date_end);
1082
1083 $line->product_ref = $obj->product_ref;
1084 $line->ref = $obj->product_ref;
1085 $line->ref_supplier = $obj->ref_supplier;
1086 $line->libelle = $obj->label;
1087 $line->label = $obj->label;
1088 $line->product_barcode = $obj->product_barcode;
1089 $line->product_desc = $obj->product_desc;
1090 $line->subprice = $obj->pu_ht;
1091 $line->pu_ht = $obj->pu_ht;
1092 $line->pu_ttc = $obj->pu_ttc;
1093 $line->vat_src_code = $obj->vat_src_code;
1094 $line->tva_tx = $obj->tva_tx;
1095 $line->localtax1_tx = $obj->localtax1_tx;
1096 $line->localtax2_tx = $obj->localtax2_tx;
1097 $line->localtax1_type = $obj->localtax1_type;
1098 $line->localtax2_type = $obj->localtax2_type;
1099 $line->qty = $obj->qty;
1100 $line->remise_percent = $obj->remise_percent;
1101 $line->fk_remise_except = $obj->fk_remise_except;
1102 //$line->tva = $obj->total_tva; // deprecated
1103 $line->total_ht = $obj->total_ht;
1104 $line->total_ttc = $obj->total_ttc;
1105 $line->total_tva = $obj->total_tva;
1106 $line->total_localtax1 = $obj->total_localtax1;
1107 $line->total_localtax2 = $obj->total_localtax2;
1108 $line->fk_facture_fourn = $obj->fk_facture_fourn;
1109 $line->fk_product = $obj->fk_product;
1110 $line->product_type = $obj->product_type;
1111 $line->product_label = $obj->label;
1112 $line->info_bits = $obj->info_bits;
1113 $line->fk_parent_line = $obj->fk_parent_line;
1114 $line->special_code = $obj->special_code;
1115 $line->rang = $obj->rang;
1116 $line->fk_unit = $obj->fk_unit;
1117
1118 $line->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
1119
1120 // Accountancy
1121 $line->fk_code_ventilation = $obj->fk_code_ventilation;
1122
1123 // Multicurrency
1124 $line->fk_multicurrency = $obj->fk_multicurrency;
1125 $line->multicurrency_code = $obj->multicurrency_code;
1126 $line->multicurrency_subprice = $obj->multicurrency_subprice;
1127 $line->multicurrency_total_ht = $obj->multicurrency_total_ht;
1128 $line->multicurrency_total_tva = $obj->multicurrency_total_tva;
1129 $line->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1130
1131 // Extra fields
1132 $line->fetch_optionals();
1133
1134 $this->lines[$i] = $line;
1135
1136 $i++;
1137 }
1138 }
1139 $this->db->free($resql_rows);
1140 return 1;
1141 } else {
1142 $this->error = $this->db->error();
1143 dol_syslog(get_class($this)."::fetch_lines - No lines:{$this->error} Error:{$this->error}", LOG_DEBUG);
1144 return -3;
1145 }
1146 }
1147
1148
1156 public function update($user = null, $notrigger = 0)
1157 {
1158 global $langs;
1159 $error = 0;
1160
1161 // Clean parameters
1162 if (empty($this->type)) {
1163 $this->type = self::TYPE_STANDARD;
1164 }
1165 if (isset($this->ref)) {
1166 $this->ref = trim($this->ref);
1167 }
1168 if (isset($this->ref_supplier)) {
1169 $this->ref_supplier = trim($this->ref_supplier);
1170 }
1171 if (isset($this->ref_ext)) {
1172 $this->ref_ext = trim($this->ref_ext);
1173 }
1174 if (isset($this->entity)) {
1175 $this->entity = (int) $this->entity;
1176 }
1177 if (isset($this->type)) {
1178 $this->type = (int) $this->type;
1179 }
1180 if (isset($this->subtype)) {
1181 $this->subtype = (int) $this->subtype;
1182 }
1183 if (isset($this->socid)) {
1184 $this->socid = (int) $this->socid;
1185 }
1186 if (isset($this->label)) {
1187 $this->label = trim($this->label);
1188 }
1189 if (isset($this->paid)) {
1190 $this->paid = (int) (bool) $this->paye;
1191 $this->paye = $this->paid;
1192 } elseif (isset($this->paye)) {
1193 $this->paid = (int) (bool) $this->paye;
1194 $this->paye = $this->paid;
1195 }
1196 if (isset($this->close_code)) {
1197 $this->close_code = trim($this->close_code);
1198 }
1199 if (isset($this->close_note)) {
1200 $this->close_note = trim($this->close_note);
1201 }
1202 if (empty($this->total_ht)) {
1203 $this->total_ht = 0;
1204 }
1205 if (empty($this->total_tva)) {
1206 $this->total_tva = 0;
1207 }
1208 if (empty($this->total_localtax1)) {
1209 $this->total_localtax1 = 0;
1210 }
1211 if (empty($this->total_localtax2)) {
1212 $this->total_localtax2 = 0;
1213 }
1214 if (isset($this->total_ttc)) {
1215 $this->total_ttc = (float) $this->total_ttc;
1216 }
1217 if (isset($this->status)) {
1218 $this->status = (int) $this->status;
1219 $this->statut = $this->status;
1220 } elseif (isset($this->statut)) {
1221 $this->status = (int) $this->statut;
1222 $this->statut = $this->status;
1223 }
1224 if (isset($this->author)) { // TODO: user_creation_id?
1225 $this->author = (int) $this->author;
1226 }
1227 if (isset($this->fk_user_valid)) {
1228 $this->fk_user_valid = (int) $this->fk_user_valid;
1229 }
1230 if (isset($this->fk_facture_source)) {
1231 $this->fk_facture_source = (int) $this->fk_facture_source;
1232 }
1233 if (isset($this->fk_project)) {
1234 if (empty($this->fk_project)) {
1235 $this->fk_project = 0;
1236 } else {
1237 $this->fk_project = (int) $this->fk_project;
1238 }
1239 }
1240 if (isset($this->mode_reglement_id)) {
1241 $this->mode_reglement_id = (int) $this->mode_reglement_id;
1242 }
1243 if (isset($this->cond_reglement_id)) {
1244 $this->cond_reglement_id = (int) $this->cond_reglement_id;
1245 }
1246 if (isset($this->note_private)) {
1247 $this->note_private = trim($this->note_private);
1248 $this->note = $this->note_private;
1249 }
1250 if (isset($this->note_public)) {
1251 $this->note_public = trim($this->note_public);
1252 }
1253 if (isset($this->model_pdf)) {
1254 $this->model_pdf = trim($this->model_pdf);
1255 }
1256 if (isset($this->import_key)) {
1257 $this->import_key = trim($this->import_key);
1258 }
1259
1260
1261 // Check parameters
1262 // Put here code to add control on parameters values
1263
1264 // Update request
1265 $sql = "UPDATE ".MAIN_DB_PREFIX."facture_fourn SET";
1266 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1267 $sql .= " ref_supplier=".(isset($this->ref_supplier) ? "'".$this->db->escape($this->ref_supplier)."'" : "null").",";
1268 $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1269 $sql .= " entity=".(isset($this->entity) ? ((int) $this->entity) : "null").",";
1270 $sql .= " type=".(isset($this->type) ? ((int) $this->type) : "null").",";
1271 $sql .= " subtype=".((int) $this->subtype).",";
1272 $sql .= " fk_soc=".(isset($this->socid) ? ((int) $this->socid) : "null").",";
1273 $sql .= " datec=".(dol_strlen((string) $this->datec) != 0 ? "'".$this->db->idate($this->datec)."'" : 'null').",";
1274 $sql .= " datef=".(dol_strlen((string) $this->date) != 0 ? "'".$this->db->idate($this->date)."'" : 'null').",";
1275 if (dol_strlen((string) $this->tms) != 0) {
1276 $sql .= " tms=".(dol_strlen((string) $this->tms) != 0 ? "'".$this->db->idate($this->tms)."'" : 'null').",";
1277 }
1278 $sql .= " libelle=".(isset($this->label) ? "'".$this->db->escape($this->label)."'" : "null").",";
1279 $sql .= " paye=".(isset($this->paid) ? ((int) $this->paid) : "0").",";
1280 $sql .= " close_code=".(isset($this->close_code) ? "'".$this->db->escape($this->close_code)."'" : "null").",";
1281 $sql .= " close_note=".(isset($this->close_note) ? "'".$this->db->escape($this->close_note)."'" : "null").",";
1282 $sql .= " localtax1=".(isset($this->total_localtax1) ? ((float) $this->total_localtax1) : "null").",";
1283 $sql .= " localtax2=".(isset($this->total_localtax2) ? ((float) $this->total_localtax2) : "null").",";
1284 $sql .= " total_ht=".(isset($this->total_ht) ? ((float) $this->total_ht) : "null").",";
1285 $sql .= " total_tva=".(isset($this->total_tva) ? ((float) $this->total_tva) : "null").",";
1286 $sql .= " total_ttc=".(isset($this->total_ttc) ? ((float) $this->total_ttc) : "null").",";
1287 $sql .= " fk_statut=".(isset($this->status) ? ((int) $this->status) : (isset($this->statut) ? ((int) $this->statut) : "null")).",";
1288 $sql .= " fk_user_author=".(isset($this->author) ? ((int) $this->author) : "null").",";
1289 $sql .= " fk_user_valid=".(isset($this->fk_user_valid) ? ((int) $this->fk_user_valid) : "null").",";
1290 $sql .= " fk_facture_source=".($this->fk_facture_source ? ((int) $this->fk_facture_source) : "null").",";
1291 $sql .= " vat_reverse_charge = ".($this->vat_reverse_charge != '' ? ((int) $this->vat_reverse_charge) : 0).",";
1292 $sql .= " fk_projet=".(!empty($this->fk_project) ? ((int) $this->fk_project) : "null").",";
1293 $sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? ((int) $this->mode_reglement_id) : "null").",";
1294 $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? ((int) $this->cond_reglement_id) : "null").",";
1295 $sql .= " date_lim_reglement=".(dol_strlen((string) $this->date_echeance) != 0 ? "'".$this->db->idate($this->date_echeance)."'" : 'null').",";
1296 $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1297 $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1298 $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1299 $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null");
1300 $sql .= " WHERE rowid=".((int) $this->id);
1301
1302 $this->db->begin();
1303
1304 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1305 $resql = $this->db->query($sql);
1306
1307 if (!$resql) {
1308 $error++;
1309
1310 if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1311 $this->errors[] = $langs->trans('ErrorRefAlreadyExists');
1312 } else {
1313 $this->errors[] = "Error ".$this->db->lasterror();
1314 }
1315 }
1316
1317 if (!$error) {
1318 $result = $this->insertExtraFields();
1319 if ($result < 0) {
1320 $error++;
1321 }
1322 }
1323
1324 if (!$error) {
1325 if (!$notrigger) {
1326 // Call trigger
1327 $result = $this->call_trigger('BILL_SUPPLIER_MODIFY', $user);
1328 if ($result < 0) {
1329 $error++;
1330 }
1331 // End call triggers
1332 }
1333 }
1334
1335 // Commit or rollback
1336 if ($error) {
1337 foreach ($this->errors as $errmsg) {
1338 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1339 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1340 }
1341 $this->db->rollback();
1342 return -1 * $error;
1343 } else {
1344 $this->db->commit();
1345 return 1;
1346 }
1347 }
1348
1349 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1356 public function insert_discount($idremise)
1357 {
1358 // phpcs:enable
1359 global $conf, $langs;
1360
1361 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1362 include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
1363
1364 $this->db->begin();
1365
1366 $remise = new DiscountAbsolute($this->db);
1367 $result = $remise->fetch($idremise);
1368
1369 if ($result > 0) {
1370 if ($remise->fk_invoice_supplier) { // Protection against multiple submission
1371 $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
1372 $this->db->rollback();
1373 return -5;
1374 }
1375
1376 $facligne = new SupplierInvoiceLine($this->db);
1377 $facligne->fk_facture_fourn = $this->id;
1378 $facligne->fk_remise_except = $remise->id;
1379 $facligne->desc = $remise->description; // Description ligne
1380 $facligne->vat_src_code = $remise->vat_src_code;
1381 $facligne->tva_tx = $remise->tva_tx;
1382 $facligne->subprice = -(float) $remise->amount_ht;
1383 $facligne->fk_product = 0; // Id produit predefini
1384 $facligne->product_type = 0;
1385 $facligne->qty = 1;
1386 $facligne->remise_percent = 0;
1387 $facligne->rang = -1;
1388 $facligne->info_bits = 2;
1389
1390 if (getDolGlobalString('MAIN_ADD_LINE_AT_POSITION')) {
1391 $facligne->rang = 1;
1392 $linecount = count($this->lines);
1393 for ($ii = 1; $ii <= $linecount; $ii++) {
1394 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
1395 }
1396 }
1397
1398 // Get buy/cost price of invoice that is source of discount
1399 if ($remise->fk_invoice_supplier_source > 0) {
1400 $srcinvoice = new FactureFournisseur($this->db);
1401 $srcinvoice->fetch($remise->fk_invoice_supplier_source);
1402 $totalcostpriceofinvoice = 0;
1403 include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmargin.class.php'; // TODO Move this into commonobject
1404 $formmargin = new FormMargin($this->db);
1405 $arraytmp = $formmargin->getMarginInfosArray($srcinvoice, false);
1406 $facligne->pa_ht = $arraytmp['pa_total'];
1407 }
1408
1409 $facligne->total_ht = -(float) $remise->amount_ht;
1410 $facligne->total_tva = -(float) $remise->amount_tva;
1411 $facligne->total_ttc = -(float) $remise->amount_ttc;
1412
1413 $facligne->multicurrency_subprice = -$remise->multicurrency_subprice;
1414 $facligne->multicurrency_total_ht = -$remise->multicurrency_total_ht;
1415 $facligne->multicurrency_total_tva = -$remise->multicurrency_total_tva;
1416 $facligne->multicurrency_total_ttc = -$remise->multicurrency_total_ttc;
1417
1418 $lineid = $facligne->insert();
1419 if ($lineid > 0) {
1420 $result = $this->update_price(1);
1421 if ($result > 0) {
1422 // Create link between discount and invoice line
1423 $result = $remise->link_to_invoice($lineid, 0);
1424 if ($result < 0) {
1425 $this->error = $remise->error;
1426 $this->db->rollback();
1427 return -4;
1428 }
1429
1430 $this->db->commit();
1431 return 1;
1432 } else {
1433 $this->error = $facligne->error;
1434 $this->db->rollback();
1435 return -1;
1436 }
1437 } else {
1438 $this->error = $facligne->error;
1439 $this->db->rollback();
1440 return -2;
1441 }
1442 } else {
1443 $this->db->rollback();
1444 return -3;
1445 }
1446 }
1447
1448
1456 public function delete(User $user, $notrigger = 0)
1457 {
1458 global $conf;
1459
1460 $rowid = $this->id;
1461
1462 dol_syslog("FactureFournisseur::delete rowid=".$rowid, LOG_DEBUG);
1463
1464 // TODO Test if there is at least on payment. If yes, refuse to delete.
1465
1466 $error = 0;
1467 $this->db->begin();
1468
1469 if (!$error && !$notrigger) {
1470 // Call trigger
1471 $result = $this->call_trigger('BILL_SUPPLIER_DELETE', $user);
1472 if ($result < 0) {
1473 $this->db->rollback();
1474 return -1;
1475 }
1476 // Fin appel triggers
1477 }
1478
1479 if (!$error) {
1480 // If invoice was converted into a discount not yet consumed, we remove discount
1481 $sql = 'DELETE FROM '.MAIN_DB_PREFIX.'societe_remise_except';
1482 $sql .= ' WHERE fk_invoice_supplier_source = '.((int) $rowid);
1483 $sql .= ' AND fk_invoice_supplier_line IS NULL';
1484 $resql = $this->db->query($sql);
1485
1486 // If invoice has consumned discounts
1487 $this->fetch_lines();
1488 $list_rowid_det = array();
1489 foreach ($this->lines as $key => $invoiceline) {
1490 $list_rowid_det[] = $invoiceline->id;
1491 }
1492
1493 // Consumned discounts are freed
1494 if (count($list_rowid_det)) {
1495 $sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
1496 $sql .= ' SET fk_invoice_supplier = NULL, fk_invoice_supplier_line = NULL';
1497 $sql .= ' WHERE fk_invoice_supplier_line IN ('.$this->db->sanitize(implode(',', $list_rowid_det)).')';
1498
1499 dol_syslog(get_class($this)."::delete", LOG_DEBUG);
1500 if (!$this->db->query($sql)) {
1501 $error++;
1502 }
1503 }
1504 }
1505
1506 if (!$error) {
1507 $main = MAIN_DB_PREFIX.'facture_fourn_det';
1508 $ef = $main."_extrafields";
1509 $sqlef = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM ".$main." WHERE fk_facture_fourn = ".((int) $rowid).")";
1510 $resqlef = $this->db->query($sqlef);
1511 $sql = 'DELETE FROM '.MAIN_DB_PREFIX.'facture_fourn_det WHERE fk_facture_fourn = '.((int) $rowid);
1512 dol_syslog(get_class($this)."::delete", LOG_DEBUG);
1513 $resql = $this->db->query($sql);
1514 if ($resqlef && $resql) {
1515 $sql = 'DELETE FROM '.MAIN_DB_PREFIX.'facture_fourn WHERE rowid = '.((int) $rowid);
1516 dol_syslog(get_class($this)."::delete", LOG_DEBUG);
1517 $resql2 = $this->db->query($sql);
1518 if (!$resql2) {
1519 $error++;
1520 }
1521 } else {
1522 $error++;
1523 }
1524 }
1525
1526 if (!$error) {
1527 // Delete linked object
1528 $res = $this->deleteObjectLinked();
1529 if ($res < 0) {
1530 $error++;
1531 }
1532 }
1533
1534 if (!$error) {
1535 // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
1536 $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1537 $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1538
1539 // We remove directory
1540 if ($conf->fournisseur->facture->dir_output) {
1541 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1542
1543 $ref = dol_sanitizeFileName($this->ref);
1544 $dir = $conf->fournisseur->facture->dir_output.'/'.get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier').$ref;
1545 $file = $dir."/".$ref.".pdf";
1546 if (file_exists($file)) {
1547 if (!dol_delete_file($file, 0, 0, 0, $this)) { // For triggers
1548 $this->error = 'ErrorFailToDeleteFile';
1549 $error++;
1550 }
1551 }
1552 if (file_exists($dir)) {
1553 $res = @dol_delete_dir_recursive($dir);
1554
1555 if (!$res) {
1556 $this->error = 'ErrorFailToDeleteDir';
1557 $error++;
1558 }
1559 }
1560 }
1561 }
1562
1563 // Remove linked categories.
1564 if (!$error) {
1565 $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_invoice";
1566 $sql .= " WHERE fk_invoice = ".((int) $this->id);
1567
1568 $result = $this->db->query($sql);
1569 if (!$result) {
1570 $error++;
1571 $this->errors[] = $this->db->lasterror();
1572 }
1573 }
1574
1575 // Remove extrafields
1576 if (!$error) {
1577 $result = $this->deleteExtraFields();
1578 if ($result < 0) {
1579 $error++;
1580 dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
1581 }
1582 }
1583
1584 if (!$error) {
1585 dol_syslog(get_class($this)."::delete $this->id by $user->id", LOG_DEBUG);
1586 $this->db->commit();
1587 return 1;
1588 } else {
1589 $this->error = $this->db->lasterror();
1590 $this->db->rollback();
1591 return -$error;
1592 }
1593 }
1594
1595
1596 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1607 public function set_paid($user, $close_code = '', $close_note = '')
1608 {
1609 // phpcs:enable
1610 dol_syslog(get_class($this)."::set_paid is deprecated, use setPaid instead", LOG_NOTICE);
1611 return $this->setPaid($user, $close_code, $close_note);
1612 }
1613
1622 public function setPaid($user, $close_code = '', $close_note = '')
1623 {
1624 $error = 0;
1625
1626 if ($this->paid != 1) {
1627 $this->db->begin();
1628
1629 $now = dol_now();
1630
1631 dol_syslog("FactureFournisseur::setPaid", LOG_DEBUG);
1632
1633 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture_fourn SET';
1634 $sql .= ' fk_statut = '.self::STATUS_CLOSED;
1635 if (!$close_code) {
1636 $sql .= ', paye=1';
1637 }
1638 if ($close_code) {
1639 $sql .= ", close_code='".$this->db->escape($close_code)."'";
1640 }
1641 if ($close_note) {
1642 $sql .= ", close_note='".$this->db->escape($close_note)."'";
1643 }
1644 $sql .= ', fk_user_closing = '.((int) $user->id);
1645 $sql .= ", date_closing = '".$this->db->idate($now)."'";
1646 $sql .= ' WHERE rowid = '.((int) $this->id);
1647
1648 $resql = $this->db->query($sql);
1649 if ($resql) {
1650 // Call trigger
1651 $result = $this->call_trigger('BILL_SUPPLIER_PAYED', $user);
1652 if ($result < 0) {
1653 $error++;
1654 }
1655 // End call triggers
1656 } else {
1657 $error++;
1658 $this->error = $this->db->error();
1659 dol_print_error($this->db);
1660 }
1661
1662 if (!$error) {
1663 $this->db->commit();
1664 return 1;
1665 } else {
1666 $this->db->rollback();
1667 return -1;
1668 }
1669 } else {
1670 return 0;
1671 }
1672 }
1673
1674 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1685 public function set_unpaid($user)
1686 {
1687 // phpcs:enable
1688 dol_syslog(get_class($this)."::set_unpaid is deprecated, use setUnpaid instead", LOG_NOTICE);
1689 return $this->setUnpaid($user);
1690 }
1691
1700 public function setUnpaid($user)
1701 {
1702 $error = 0;
1703
1704 $this->db->begin();
1705
1706 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture_fourn';
1707 $sql .= ' SET paye=0, fk_statut='.self::STATUS_VALIDATED.', close_code=null, close_note=null,';
1708 $sql .= ' date_closing=null,';
1709 $sql .= ' fk_user_closing=null';
1710 $sql .= ' WHERE rowid = '.((int) $this->id);
1711
1712 dol_syslog(get_class($this)."::set_unpaid", LOG_DEBUG);
1713 $resql = $this->db->query($sql);
1714 if ($resql) {
1715 // Call trigger
1716 $result = $this->call_trigger('BILL_SUPPLIER_UNPAYED', $user);
1717 if ($result < 0) {
1718 $error++;
1719 }
1720 // End call triggers
1721 } else {
1722 $error++;
1723 $this->error = $this->db->error();
1724 dol_print_error($this->db);
1725 }
1726
1727 if (!$error) {
1728 $this->db->commit();
1729 return 1;
1730 } else {
1731 $this->db->rollback();
1732 return -1;
1733 }
1734 }
1735
1746 public function setCanceled($user, $close_code = '', $close_note = '')
1747 {
1748 dol_syslog(get_class($this)."::setCanceled rowid=".((int) $this->id), LOG_DEBUG);
1749
1750 $this->db->begin();
1751
1752 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture_fourn SET';
1753 $sql .= ' fk_statut='.self::STATUS_ABANDONED;
1754 if ($close_code) {
1755 $sql .= ", close_code='".$this->db->escape($close_code)."'";
1756 }
1757 if ($close_note) {
1758 $sql .= ", close_note='".$this->db->escape($close_note)."'";
1759 }
1760 $sql .= " WHERE rowid = ".((int) $this->id);
1761
1762 $resql = $this->db->query($sql);
1763 if ($resql) {
1764 // Bound discounts are deducted from the invoice
1765 // as they have not been used since the invoice is abandoned.
1766 $sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
1767 $sql .= ' SET fk_invoice_supplier = NULL';
1768 $sql .= ' WHERE fk_invoice_supplier = '.((int) $this->id);
1769
1770 $resql = $this->db->query($sql);
1771 if ($resql) {
1772 // Call trigger
1773 $result = $this->call_trigger('BILL_SUPPLIER_CANCEL', $user);
1774 if ($result < 0) {
1775 $this->db->rollback();
1776 return -1;
1777 }
1778 // End call triggers
1779
1780 $this->db->commit();
1781 return 1;
1782 } else {
1783 $this->error = $this->db->error()." sql=".$sql;
1784 $this->db->rollback();
1785 return -1;
1786 }
1787 } else {
1788 $this->error = $this->db->error()." sql=".$sql;
1789 $this->db->rollback();
1790 return -2;
1791 }
1792 }
1793
1803 public function validate($user, $force_number = '', $idwarehouse = 0, $notrigger = 0)
1804 {
1805 global $mysoc, $conf, $langs;
1806
1807 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1808
1809 $now = dol_now();
1810
1811 $error = 0;
1812 dol_syslog(get_class($this).'::validate user='.$user->id.', force_number='.$force_number.', idwarehouse='.$idwarehouse);
1813
1814 // Force to have object complete for checks
1815 $this->fetch_thirdparty();
1816 $this->fetch_lines();
1817
1818 // Check parameters
1819 if ($this->status > self::STATUS_DRAFT) { // This is to avoid to validate twice (avoid errors on logs and stock management)
1820 dol_syslog(get_class($this)."::validate no draft status", LOG_WARNING);
1821 return 0;
1822 }
1823 if (preg_match('/^'.preg_quote($langs->trans("CopyOf").' ').'/', $this->ref_supplier)) {
1824 $langs->load("errors");
1825 $this->error = $langs->trans("ErrorFieldFormat", $langs->transnoentities("RefSupplier")).'. '.$langs->trans('RemoveString', $langs->transnoentitiesnoconv("CopyOf"));
1826 return -1;
1827 }
1828 if (count($this->lines) <= 0) {
1829 $langs->load("errors");
1830 $this->error = $langs->trans("ErrorObjectMustHaveLinesToBeValidated", $this->ref);
1831 return -1;
1832 }
1833
1834 // Check for mandatory fields in thirdparty (defined into setup)
1835 if (!empty($this->thirdparty) && is_object($this->thirdparty)) {
1836 $array_to_check = array('IDPROF1', 'IDPROF2', 'IDPROF3', 'IDPROF4', 'IDPROF5', 'IDPROF6', 'EMAIL', 'ACCOUNTANCY_CODE_SUPPLIER');
1837 foreach ($array_to_check as $key) {
1838 $keymin = strtolower($key);
1839 if ($keymin == 'accountancy_code_supplier') {
1840 $keymin = 'code_compta_fournisseur';
1841 }
1842 if (!property_exists($this->thirdparty, $keymin)) {
1843 continue;
1844 }
1845 $vallabel = $this->thirdparty->$keymin;
1846
1847 $i = (int) preg_replace('/[^0-9]/', '', $key);
1848 if ($i > 0) {
1849 if ($this->thirdparty->isACompany()) {
1850 // Check for mandatory prof id (but only if country is other than ours)
1851 if ($mysoc->country_id > 0 && $this->thirdparty->country_id == $mysoc->country_id) {
1852 $idprof_mandatory = 'SOCIETE_'.$key.'_INVOICE_MANDATORY';
1853 if (!$vallabel && getDolGlobalString($idprof_mandatory)) {
1854 $langs->load("errors");
1855 $this->error = $langs->trans('ErrorProdIdIsMandatory', $langs->transcountry('ProfId'.$i, $this->thirdparty->country_code)).' ('.$langs->trans("ForbiddenBySetupRules").') ['.$langs->trans('Company').' : '.$this->thirdparty->name.']';
1856 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
1857 return -1;
1858 }
1859 }
1860 }
1861 } else {
1862 if ($key == 'EMAIL') {
1863 // Check for mandatory
1864 if (getDolGlobalString('SOCIETE_EMAIL_INVOICE_MANDATORY') && !isValidEmail($this->thirdparty->email)) {
1865 $langs->load("errors");
1866 $this->error = $langs->trans("ErrorBadEMail", $this->thirdparty->email).' ('.$langs->trans("ForbiddenBySetupRules").') ['.$langs->trans('Company').' : '.$this->thirdparty->name.']';
1867 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
1868 return -1;
1869 }
1870 } elseif ($key == 'ACCOUNTANCY_CODE_SUPPLIER') {
1871 // Check for mandatory
1872 if (getDolGlobalString('SOCIETE_ACCOUNTANCY_CODE_SUPPLIER_INVOICE_MANDATORY') && empty($this->thirdparty->code_compta_fournisseur)) {
1873 $langs->load("errors");
1874 $this->error = $langs->trans("ErrorAccountancyCodeSupplierIsMandatory", $this->thirdparty->name).' ('.$langs->trans("ForbiddenBySetupRules").')';
1875 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
1876 return -1;
1877 }
1878 }
1879 }
1880 }
1881 }
1882
1883 $this->db->begin();
1884
1885 // Define new ref
1886 if ($force_number) {
1887 $num = $force_number;
1888 } elseif (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
1889 $num = $this->getNextNumRef($this->thirdparty);
1890 } else {
1891 $num = $this->ref;
1892 }
1893 $this->newref = dol_sanitizeFileName($num);
1894
1895 $sql = "UPDATE ".MAIN_DB_PREFIX."facture_fourn";
1896 $sql .= " SET ref='".$this->db->escape($num)."', fk_statut = 1, fk_user_valid = ".((int) $user->id).", date_valid = '".$this->db->idate($now)."'";
1897 $sql .= " WHERE rowid = ".((int) $this->id);
1898
1899 dol_syslog(get_class($this)."::validate", LOG_DEBUG);
1900 $resql = $this->db->query($sql);
1901 if ($resql) {
1902 // Si on incrémente le produit principal et ses composants à la validation de facture fournisseur
1903 if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL')) {
1904 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
1905 $langs->load("agenda");
1906
1907 $cpt = count($this->lines);
1908 for ($i = 0; $i < $cpt; $i++) {
1909 if ($this->lines[$i]->fk_product > 0) {
1910 $mouvP = new MouvementStock($this->db);
1911 $mouvP->origin = &$this;
1912 $mouvP->setOrigin($this->element, $this->id);
1913 // We increase stock for product
1914 $up_ht_disc = $this->lines[$i]->subprice;
1915 if (!empty($this->lines[$i]->remise_percent) && !getDolGlobalString('STOCK_EXCLUDE_DISCOUNT_FOR_PMP')) {
1916 $up_ht_disc = price2num($up_ht_disc * (100 - $this->lines[$i]->remise_percent) / 100, 'MU');
1917 }
1919 $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $up_ht_disc, $langs->trans("InvoiceValidatedInDolibarr", $num));
1920 } else {
1921 $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $up_ht_disc, $langs->trans("InvoiceValidatedInDolibarr", $num));
1922 }
1923 if ($result < 0) {
1924 $this->error = $mouvP->error;
1925 if (count($mouvP->errors)) {
1926 $this->errors = $mouvP->errors;
1927 }
1928 return -2;
1929 }
1930 }
1931 }
1932 }
1933
1934 // Triggers call
1935 if (!$error && empty($notrigger)) {
1936 // Call trigger
1937 $result = $this->call_trigger('BILL_SUPPLIER_VALIDATE', $user);
1938 if ($result < 0) {
1939 $error++;
1940 }
1941 // End call triggers
1942 }
1943
1944 if (!$error) {
1945 $this->oldref = $this->ref;
1946
1947 // Rename directory if dir was a temporary ref
1948 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
1949 // Now we rename also files into index
1950 $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)."'";
1951 $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;
1952 $resql = $this->db->query($sql);
1953 if (!$resql) {
1954 $error++;
1955 $this->error = $this->db->lasterror();
1956 }
1957 $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)."'";
1958 $sql .= " WHERE filepath = 'fournisseur/facture/".get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier').$this->db->escape($this->ref)."' and entity = ".$conf->entity;
1959 $resql = $this->db->query($sql);
1960 if (!$resql) {
1961 $error++;
1962 $this->error = $this->db->lasterror();
1963 }
1964
1965 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
1966 $oldref = dol_sanitizeFileName($this->ref);
1967 $dirsource = $conf->fournisseur->facture->dir_output.'/'.get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier').$oldref;
1968 $dirdest = $conf->fournisseur->facture->dir_output.'/'.get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier').$this->newref;
1969 if (!$error && file_exists($dirsource)) {
1970 dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
1971
1972 if (@rename($dirsource, $dirdest)) {
1973 dol_syslog("Rename ok");
1974 // Rename docs starting with $oldref with $this->newref
1975 $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, '/'));
1976 foreach ($listoffiles as $fileentry) {
1977 $dirsource = $fileentry['name'];
1978 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $this->newref, $dirsource);
1979 $dirsource = $fileentry['path'].'/'.$dirsource;
1980 $dirdest = $fileentry['path'].'/'.$dirdest;
1981 @rename($dirsource, $dirdest);
1982 }
1983 }
1984 }
1985 }
1986 }
1987
1988 // Set new ref and define current status
1989 if (!$error) {
1990 $this->ref = $this->newref;
1991 $this->statut = self::STATUS_VALIDATED;
1993 //$this->date_validation=$now; this is stored into log table
1994 }
1995
1996 if (!$error) {
1997 $this->db->commit();
1998 return 1;
1999 } else {
2000 $this->db->rollback();
2001 return -1;
2002 }
2003 } else {
2004 $this->error = $this->db->error();
2005 $this->db->rollback();
2006 return -1;
2007 }
2008 }
2009
2018 public function setDraft($user, $idwarehouse = -1, $notrigger = 0)
2019 {
2020 // phpcs:enable
2021 global $conf, $langs;
2022
2023 $error = 0;
2024
2025 if ($this->status == self::STATUS_DRAFT) {
2026 dol_syslog(__METHOD__." already draft status", LOG_WARNING);
2027 return 0;
2028 }
2029
2030 dol_syslog(__METHOD__, LOG_DEBUG);
2031
2032 $this->db->begin();
2033
2034 $sql = "UPDATE ".MAIN_DB_PREFIX."facture_fourn";
2035 $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2036 $sql .= " WHERE rowid = ".((int) $this->id);
2037
2038 $result = $this->db->query($sql);
2039 if ($result) {
2040 if (!$error) {
2041 $this->oldcopy = clone $this;
2042 }
2043
2044 // Si on incremente le produit principal et ses composants a la validation de facture fournisseur, on decremente
2045 if ($result >= 0 && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL')) {
2046 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2047 $langs->load("agenda");
2048
2049 $cpt = count($this->lines);
2050 for ($i = 0; $i < $cpt; $i++) {
2051 if ($this->lines[$i]->fk_product > 0) {
2052 $mouvP = new MouvementStock($this->db);
2053 $mouvP->origin = &$this;
2054 $mouvP->setOrigin($this->element, $this->id);
2055 // We increase stock for product
2057 $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceBackToDraftInDolibarr", $this->ref));
2058 } else {
2059 $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceBackToDraftInDolibarr", $this->ref));
2060 }
2061 }
2062 }
2063 }
2064 // Triggers call
2065 if (!$error && empty($notrigger)) {
2066 // Call trigger
2067 $result = $this->call_trigger('BILL_SUPPLIER_UNVALIDATE', $user);
2068 if ($result < 0) {
2069 $error++;
2070 }
2071 // End call triggers
2072 }
2073 if ($error == 0) {
2074 $this->db->commit();
2075 return 1;
2076 } else {
2077 $this->db->rollback();
2078 return -1;
2079 }
2080 } else {
2081 $this->error = $this->db->error();
2082 $this->db->rollback();
2083 return -1;
2084 }
2085 }
2086
2087
2121 public function addline($desc, $pu, $txtva, $txlocaltax1, $txlocaltax2, $qty, $fk_product = 0, $remise_percent = 0, $date_start = 0, $date_end = 0, $fk_code_ventilation = 0, $info_bits = 0, $price_base_type = 'HT', $type = 0, $rang = -1, $notrigger = 0, $array_options = [], $fk_unit = null, $origin_id = 0, $pu_devise = 0, $ref_supplier = '', $special_code = 0, $fk_parent_line = 0, $fk_remise_except = 0)
2122 {
2123 global $langs, $mysoc;
2124
2125 dol_syslog(get_class($this)."::addline $desc,$pu,$qty,$txtva,$fk_product,$remise_percent,$date_start,$date_end,$fk_code_ventilation,$info_bits,$price_base_type,$type,$fk_unit,fk_remise_except=$fk_remise_except", LOG_DEBUG);
2126 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2127
2128 if ($this->status == self::STATUS_DRAFT) {
2129 // Clean parameters
2130 if (empty($remise_percent)) {
2131 $remise_percent = 0;
2132 }
2133 if (empty($qty)) {
2134 $qty = 0;
2135 }
2136 if (empty($info_bits)) {
2137 $info_bits = 0;
2138 }
2139 if (empty($rang)) {
2140 $rang = 0;
2141 }
2142 if (empty($fk_code_ventilation)) {
2143 $fk_code_ventilation = 0;
2144 }
2145 if (empty($txtva)) {
2146 $txtva = 0;
2147 }
2148 if (empty($txlocaltax1)) {
2149 $txlocaltax1 = 0;
2150 }
2151 if (empty($txlocaltax2)) {
2152 $txlocaltax2 = 0;
2153 }
2154
2155 $remise_percent = price2num($remise_percent);
2156 $qty = price2num($qty);
2157 $pu = price2num($pu);
2158 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
2159 $txtva = price2num($txtva); // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
2160 }
2161 $txlocaltax1 = price2num($txlocaltax1);
2162 $txlocaltax2 = price2num($txlocaltax2);
2163
2164 if ($date_start && $date_end && $date_start > $date_end) {
2165 $langs->load("errors");
2166 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
2167 return -1;
2168 }
2169
2170 $this->db->begin();
2171
2172 if ($fk_product > 0) {
2173 if (getDolGlobalInt('SUPPLIER_INVOICE_WITH_PREDEFINED_PRICES_ONLY') == 1) { // Not the common case
2174 // Check quantity is enough
2175 dol_syslog(get_class($this)."::addline we check supplier prices fk_product=".$fk_product." qty=".$qty." ref_supplier=".$ref_supplier);
2176 $prod = new ProductFournisseur($this->db);
2177 if ($prod->fetch($fk_product) > 0) {
2178 $product_type = $prod->type;
2179 $label = $prod->label;
2180 $fk_prod_fourn_price = 0;
2181
2182 // We use 'none' instead of $ref_supplier, because $ref_supplier may not exists anymore. So we will take the first supplier price ok.
2183 // If we want a dedicated supplier price, we must provide $fk_prod_fourn_price.
2184 $result = $prod->get_buyprice($fk_prod_fourn_price, (float) $qty, $fk_product, 'none', ($this->fk_soc ? $this->fk_soc : $this->socid)); // Search on couple $fk_prod_fourn_price/$qty first, then on triplet $qty/$fk_product/$ref_supplier/$this->fk_soc
2185 if ($result > 0) {
2186 if (empty($pu)) {
2187 $pu = $prod->fourn_pu; // Unit price supplier price set by get_buyprice
2188 }
2189 $ref_supplier = $prod->ref_supplier; // Ref supplier price set by get_buyprice
2190 // is remise percent not keyed but present for the product we add it
2191 if ($remise_percent == 0 && $prod->remise_percent != 0) {
2192 $remise_percent = $prod->remise_percent;
2193 }
2194 }
2195 if ($result == 0) { // If result == 0, we failed to found the supplier reference price
2196 $langs->load("errors");
2197 $this->error = "Ref ".$prod->ref." ".$langs->trans("ErrorQtyTooLowForThisSupplier");
2198 $this->db->rollback();
2199 dol_syslog(get_class($this)."::addline we did not found supplier price, so we can't guess unit price");
2200 //$pu = $prod->fourn_pu; // We do not overwrite unit price
2201 //$ref = $prod->ref_fourn; // We do not overwrite ref supplier price
2202 return -1;
2203 }
2204 if ($result == -1) {
2205 $langs->load("errors");
2206 $this->error = "Ref ".$prod->ref." ".$langs->trans("ErrorQtyTooLowForThisSupplier");
2207 $this->db->rollback();
2208 dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_DEBUG);
2209 return -1;
2210 }
2211 if ($result < -1) {
2212 $this->error = $prod->error;
2213 $this->db->rollback();
2214 dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_ERR);
2215 return -1;
2216 }
2217 } else {
2218 $this->error = $prod->error;
2219 $this->db->rollback();
2220 return -1;
2221 }
2222 }
2223 } else {
2224 $product_type = $type;
2225 }
2226
2227 if (isModEnabled("multicurrency") && $pu_devise > 0) {
2228 $pu = 0;
2229 }
2230
2231 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
2232
2233 // Clean vat code
2234 $reg = array();
2235 $vat_src_code = '';
2236 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
2237 $vat_src_code = $reg[1];
2238 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
2239 }
2240
2241 // Calcul du total TTC et de la TVA pour la ligne a partir de
2242 // qty, pu, remise_percent et txtva
2243 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2244 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2245
2246 $tabprice = calcul_price_total((float) $qty, $pu, $remise_percent, $txtva, (float) $txlocaltax1, (float) $txlocaltax2, 0, $price_base_type, $info_bits, $type, $this->thirdparty, $localtaxes_type, 100, $this->multicurrency_tx, $pu_devise);
2247 $total_ht = $tabprice[0];
2248 $total_tva = $tabprice[1];
2249 $total_ttc = $tabprice[2];
2250 $total_localtax1 = $tabprice[9];
2251 $total_localtax2 = $tabprice[10];
2252 $pu_ht = $tabprice[3];
2253
2254 // MultiCurrency
2255 $multicurrency_total_ht = $tabprice[16];
2256 $multicurrency_total_tva = $tabprice[17];
2257 $multicurrency_total_ttc = $tabprice[18];
2258 $pu_ht_devise = $tabprice[19];
2259
2260 // Check parameters
2261 if ($type < 0) {
2262 return -1;
2263 }
2264
2265 if ($rang < 0) {
2266 $rangmax = $this->line_max();
2267 $rang = $rangmax + 1;
2268 }
2269
2270 // Insert line
2271 $supplierinvoiceline = new SupplierInvoiceLine($this->db);
2272
2273 $supplierinvoiceline->context = $this->context;
2274
2275 $supplierinvoiceline->fk_facture_fourn = $this->id;
2276 //$supplierinvoiceline->label=$label; // deprecated
2277 $supplierinvoiceline->desc = $desc;
2278 $supplierinvoiceline->ref_supplier = $ref_supplier;
2279
2280 $supplierinvoiceline->qty = ($this->type == self::TYPE_CREDIT_NOTE ? abs((float) $qty) : (float) $qty); // For credit note, quantity is always positive and unit price negative
2281 $supplierinvoiceline->subprice = ($this->type == self::TYPE_CREDIT_NOTE ? -abs((float) $pu_ht) : (float) $pu_ht); // For credit note, unit price always negative, always positive otherwise
2282
2283 $supplierinvoiceline->vat_src_code = $vat_src_code;
2284 $supplierinvoiceline->tva_tx = $txtva;
2285 $supplierinvoiceline->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
2286 $supplierinvoiceline->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
2287 $supplierinvoiceline->localtax1_type = empty($localtaxes_type[0]) ? 0 : $localtaxes_type[0];
2288 $supplierinvoiceline->localtax2_type = empty($localtaxes_type[2]) ? 0 : $localtaxes_type[2];
2289
2290 $supplierinvoiceline->total_ht = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $total_ht) : (float) $total_ht); // For credit note and if qty is negative, total is negative
2291 $supplierinvoiceline->total_tva = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $total_tva) : (float) $total_tva); // For credit note and if qty is negative, total is negative
2292 $supplierinvoiceline->total_localtax1 = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $total_localtax1) : (float) $total_localtax1); // For credit note and if qty is negative, total is negative
2293 $supplierinvoiceline->total_localtax2 = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $total_localtax2) : (float) $total_localtax2); // For credit note and if qty is negative, total is negative
2294 $supplierinvoiceline->total_ttc = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $total_ttc) : (float) $total_ttc); // For credit note and if qty is negative, total is negative
2295
2296 $supplierinvoiceline->fk_product = $fk_product;
2297 $supplierinvoiceline->product_type = $type;
2298 $supplierinvoiceline->remise_percent = $remise_percent;
2299 $supplierinvoiceline->date_start = $date_start;
2300 $supplierinvoiceline->date_end = $date_end;
2301 $supplierinvoiceline->fk_code_ventilation = $fk_code_ventilation;
2302 $supplierinvoiceline->rang = $rang;
2303 $supplierinvoiceline->info_bits = $info_bits;
2304 $supplierinvoiceline->fk_remise_except = $fk_remise_except;
2305
2306
2307 $supplierinvoiceline->special_code = (int) $special_code;
2308 $supplierinvoiceline->fk_parent_line = $fk_parent_line;
2309 $supplierinvoiceline->origin = $this->origin;
2310 $supplierinvoiceline->origin_id = $origin_id;
2311 $supplierinvoiceline->fk_unit = $fk_unit;
2312
2313 // Multicurrency
2314 $supplierinvoiceline->fk_multicurrency = $this->fk_multicurrency;
2315 $supplierinvoiceline->multicurrency_code = $this->multicurrency_code;
2316 $supplierinvoiceline->multicurrency_subprice = ($this->type == self::TYPE_CREDIT_NOTE ? -abs((float) $pu_ht_devise) : (float) $pu_ht_devise); // For credit note, unit price always negative, always positive otherwise
2317
2318 $supplierinvoiceline->multicurrency_total_ht = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $multicurrency_total_ht) : (float) $multicurrency_total_ht); // For credit note and if qty is negative, total is negative
2319 $supplierinvoiceline->multicurrency_total_tva = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $multicurrency_total_tva) : (float) $multicurrency_total_tva); // For credit note and if qty is negative, total is negative
2320 $supplierinvoiceline->multicurrency_total_ttc = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $multicurrency_total_ttc) : (float) $multicurrency_total_ttc); // For credit note and if qty is negative, total is negative
2321
2322 if (is_array($array_options) && count($array_options) > 0) {
2323 $supplierinvoiceline->array_options = $array_options;
2324 }
2325
2326 $result = $supplierinvoiceline->insert($notrigger);
2327 if ($result > 0) {
2328 // Update denormalized fields at the order level
2329 $result = $this->update_price(1, 'auto', 0, $this->thirdparty); // The addline method is designed to add line from user input so total calculation with update_price must be done using 'auto' mode.
2330
2331 if ($result > 0) {
2332 if (!isset($this->context['createfromclone'])) {
2333 if (!empty($fk_parent_line)) {
2334 // Always reorder if child line
2335 $this->line_order(true, 'DESC');
2336 } elseif ($rang > 0 && $rang <= count($this->lines)) {
2337 // Update all rank of all other lines starting from the same $ranktouse
2338 $linecount = count($this->lines);
2339 for ($ii = $rang; $ii <= $linecount; $ii++) {
2340 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
2341 }
2342 }
2343
2344 $this->lines[] = $supplierinvoiceline;
2345 }
2346
2347 $this->db->commit();
2348 return $supplierinvoiceline->id;
2349 } else {
2350 $this->error = $this->db->error();
2351 $this->db->rollback();
2352 return -1;
2353 }
2354 } else {
2355 $this->error = $supplierinvoiceline->error;
2356 $this->errors = $supplierinvoiceline->errors;
2357 $this->db->rollback();
2358 return -2;
2359 }
2360 } else {
2361 return 0;
2362 }
2363 }
2364
2390 public function updateline($id, $desc, $pu, $vatrate, $txlocaltax1 = 0, $txlocaltax2 = 0, $qty = 1, $idproduct = 0, $price_base_type = 'HT', $info_bits = 0, $type = 0, $remise_percent = 0, $notrigger = 0, $date_start = '', $date_end = '', $array_options = [], $fk_unit = null, $pu_devise = 0, $ref_supplier = '', $rang = 0)
2391 {
2392 global $mysoc, $langs;
2393
2394 dol_syslog(get_class($this)."::updateline $id,$desc,$pu,$vatrate,$qty,$idproduct,$price_base_type,$info_bits,$type,$remise_percent,$notrigger,$date_start,$date_end,$fk_unit,$pu_devise,$ref_supplier", LOG_DEBUG);
2395 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2396
2397 $pu = price2num($pu);
2398 $qty = price2num($qty);
2399 $remise_percent = (float) price2num($remise_percent);
2400 $pu_devise = price2num($pu_devise);
2401
2402 // Check parameters
2403 //if (! is_numeric($pu) || ! is_numeric($qty)) return -1;
2404 if ($type < 0) {
2405 return -1;
2406 }
2407
2408 if ($date_start && $date_end && $date_start > $date_end) {
2409 $langs->load("errors");
2410 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
2411 return -1;
2412 }
2413
2414 // Clean parameters
2415 if (empty($vatrate)) {
2416 $vatrate = 0;
2417 }
2418 if (empty($txlocaltax1)) {
2419 $txlocaltax1 = 0;
2420 }
2421 if (empty($txlocaltax2)) {
2422 $txlocaltax2 = 0;
2423 }
2424
2425 $txlocaltax1 = (float) price2num($txlocaltax1);
2426 $txlocaltax2 = (float) price2num($txlocaltax2);
2427
2428 // Calcul du total TTC et de la TVA pour la ligne a partir de
2429 // qty, pu, remise_percent et txtva
2430 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2431 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2432
2433 $localtaxes_type = getLocalTaxesFromRate($vatrate, 0, $mysoc, $this->thirdparty);
2434
2435 $reg = array();
2436
2437 // Clean vat code
2438 $vat_src_code = '';
2439 if (preg_match('/\‍((.*)\‍)/', (string) $vatrate, $reg)) {
2440 $vat_src_code = $reg[1];
2441 $vatrate = preg_replace('/\s*\‍(.*\‍)/', '', (string) $vatrate); // Remove code into vatrate.
2442 }
2443
2444 $tabprice = calcul_price_total((float) $qty, (float) $pu, $remise_percent, $vatrate, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $this->thirdparty, $localtaxes_type, 100, $this->multicurrency_tx, (float) $pu_devise);
2445 $total_ht = $tabprice[0];
2446 $total_tva = $tabprice[1];
2447 $total_ttc = $tabprice[2];
2448 $pu_ht = $tabprice[3];
2449 $pu_tva = $tabprice[4];
2450 $pu_ttc = $tabprice[5];
2451 $total_localtax1 = $tabprice[9];
2452 $total_localtax2 = $tabprice[10];
2453
2454 // MultiCurrency
2455 $multicurrency_total_ht = $tabprice[16];
2456 $multicurrency_total_tva = $tabprice[17];
2457 $multicurrency_total_ttc = $tabprice[18];
2458 $pu_ht_devise = $tabprice[19];
2459
2460 if (empty($info_bits)) {
2461 $info_bits = 0;
2462 }
2463
2464 // Fetch current line from the database and then clone the object and set it in $oldline property
2465 $line = new SupplierInvoiceLine($this->db);
2466 $line->fetch($id);
2467 $line->fetch_optionals();
2468
2469 $staticline = clone $line;
2470
2471 if ($idproduct) {
2472 $product = new Product($this->db);
2473 $result = $product->fetch($idproduct);
2474 $product_type = $product->type;
2475 } else {
2476 $idproduct = $staticline->fk_product;
2477 $product_type = $type;
2478 }
2479
2480 $line->oldline = $staticline;
2481 $line->context = $this->context;
2482
2483 $line->description = $desc;
2484 $line->desc = $desc;
2485
2486 $line->qty = ($this->type == self::TYPE_CREDIT_NOTE ? abs((float) $qty) : (float) $qty); // For credit note, quantity is always positive and unit price negative
2487 $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
2488 $line->pu_ht = $line->subprice; // deprecated
2489 $line->pu_ttc = ($this->type == self::TYPE_CREDIT_NOTE ? -abs((float) $pu_ttc) : (float) $pu_ttc); // For credit note, unit price always negative, always positive otherwise
2490
2491 $line->remise_percent = $remise_percent;
2492 $line->ref_supplier = $ref_supplier;
2493
2494 $line->date_start = $date_start;
2495 $line->date_end = $date_end;
2496
2497 $line->vat_src_code = $vat_src_code;
2498 $line->tva_tx = $vatrate;
2499 $line->localtax1_tx = $txlocaltax1;
2500 $line->localtax2_tx = $txlocaltax2;
2501 $line->localtax1_type = empty($localtaxes_type[0]) ? 0 : $localtaxes_type[0];
2502 $line->localtax2_type = empty($localtaxes_type[2]) ? 0 : $localtaxes_type[2];
2503
2504 $line->total_ht = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $total_ht) : (float) $total_ht);
2505 $line->total_tva = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $total_tva) : (float) $total_tva);
2506 $line->total_localtax1 = (float) $total_localtax1;
2507 $line->total_localtax2 = (float) $total_localtax2;
2508 $line->total_ttc = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $total_ttc) : (float) $total_ttc);
2509
2510 $line->fk_product = $idproduct;
2511 $line->product_type = $product_type;
2512 $line->info_bits = $info_bits;
2513 $line->fk_unit = $fk_unit;
2514 $line->rang = $rang;
2515
2516 if (is_array($array_options) && count($array_options) > 0) {
2517 // We replace values in this->line->array_options only for entries defined into $array_options
2518 foreach ($array_options as $key => $value) {
2519 $line->array_options[$key] = $array_options[$key];
2520 }
2521 }
2522
2523 // Multicurrency
2524 $line->multicurrency_subprice = (float) $pu_ht_devise;
2525 $line->multicurrency_total_ht = (float) $multicurrency_total_ht;
2526 $line->multicurrency_total_tva = (float) $multicurrency_total_tva;
2527 $line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
2528
2529 $res = $line->update($notrigger);
2530
2531 if ($res < 1) {
2532 $this->errors[] = $line->error;
2533 $this->errors = array_merge($this->errors, $line->errors);
2534 } else {
2535 // Update total price into invoice record
2536 $res = $this->update_price(1, 'auto', 0, $this->thirdparty);
2537 }
2538
2539 return $res;
2540 }
2541
2549 public function deleteLine($rowid, $notrigger = 0)
2550 {
2551 if (!$rowid) {
2552 $rowid = $this->id;
2553 }
2554
2555 $this->db->begin();
2556
2557 // Free the discount linked to a line of invoice
2558 $sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
2559 $sql .= ' SET fk_invoice_supplier_line = NULL';
2560 $sql .= ' WHERE fk_invoice_supplier_line = '.((int) $rowid);
2561
2562 dol_syslog(get_class($this)."::deleteline", LOG_DEBUG);
2563 $result = $this->db->query($sql);
2564 if (!$result) {
2565 $this->error = $this->db->error();
2566 $this->db->rollback();
2567 return -2;
2568 }
2569
2570 $line = new SupplierInvoiceLine($this->db);
2571
2572 if ($line->fetch($rowid) < 1) {
2573 return -1;
2574 }
2575
2576 $res = $line->delete($notrigger);
2577
2578 if ($res < 1) {
2579 $this->errors[] = $line->error;
2580 $this->db->rollback();
2581 return -3;
2582 } else {
2583 $res = $this->update_price(1);
2584
2585 if ($res > 0) {
2586 $this->db->commit();
2587 return 1;
2588 } else {
2589 $this->db->rollback();
2590 $this->error = $this->db->lasterror();
2591 return -4;
2592 }
2593 }
2594 }
2595
2596
2603 public function info($id)
2604 {
2605 $sql = 'SELECT c.rowid, datec, tms as datem, ';
2606 $sql .= ' fk_user_author, fk_user_modif, fk_user_valid';
2607 $sql .= ' FROM '.MAIN_DB_PREFIX.'facture_fourn as c';
2608 $sql .= ' WHERE c.rowid = '.((int) $id);
2609
2610 $result = $this->db->query($sql);
2611 if ($result) {
2612 if ($this->db->num_rows($result)) {
2613 $obj = $this->db->fetch_object($result);
2614
2615 $this->id = $obj->rowid;
2616
2617 $this->user_creation_id = $obj->fk_user_author;
2618 $this->user_validation_id = $obj->fk_user_valid;
2619 $this->user_modification_id = $obj->fk_user_modif;
2620 $this->date_creation = $this->db->jdate($obj->datec);
2621 $this->date_modification = $this->db->jdate($obj->datem);
2622 //$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)
2623 }
2624 $this->db->free($result);
2625 } else {
2626 dol_print_error($this->db);
2627 }
2628 }
2629
2630 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2639 public function list_replacable_supplier_invoices($socid = 0)
2640 {
2641 // phpcs:enable
2642 global $conf;
2643
2644 $return = array();
2645
2646 $sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut,";
2647 $sql .= " ff.rowid as rowidnext";
2648 $sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn as f";
2649 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."paiementfourn_facturefourn as pf ON f.rowid = pf.fk_facturefourn";
2650 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facture_fourn as ff ON f.rowid = ff.fk_facture_source";
2651 $sql .= " WHERE (f.fk_statut = ".self::STATUS_VALIDATED." OR (f.fk_statut = ".self::STATUS_ABANDONED." AND f.close_code = '".self::CLOSECODE_ABANDONED."'))";
2652 $sql .= " AND f.entity = ".$conf->entity;
2653 $sql .= " AND f.paye = 0"; // Pas classee payee completement
2654 $sql .= " AND pf.fk_paiementfourn IS NULL"; // Aucun paiement deja fait
2655 $sql .= " AND ff.fk_statut IS NULL"; // Renvoi vrai si pas facture de replacement
2656 if ($socid > 0) {
2657 $sql .= " AND f.fk_soc = ".((int) $socid);
2658 }
2659 $sql .= " ORDER BY f.ref";
2660
2661 dol_syslog(get_class($this)."::list_replacable_supplier_invoices", LOG_DEBUG);
2662 $resql = $this->db->query($sql);
2663 if ($resql) {
2664 while ($obj = $this->db->fetch_object($resql)) {
2665 $return[$obj->rowid] = array(
2666 'id' => $obj->rowid,
2667 'ref' => $obj->ref,
2668 'status' => $obj->fk_statut
2669 );
2670 }
2671 //print_r($return);
2672 return $return;
2673 } else {
2674 $this->error = $this->db->error();
2675 return -1;
2676 }
2677 }
2678
2679 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2689 public function list_qualified_avoir_supplier_invoices($socid = 0)
2690 {
2691 // phpcs:enable
2692 global $conf;
2693
2694 $return = array();
2695
2696 $sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut, f.type, f.subtype, f.paye, pf.fk_paiementfourn";
2697 $sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn as f";
2698 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."paiementfourn_facturefourn as pf ON f.rowid = pf.fk_facturefourn";
2699 $sql .= " WHERE f.entity = ".$conf->entity;
2700 $sql .= " AND f.fk_statut in (".self::STATUS_VALIDATED.",".self::STATUS_CLOSED.")";
2701 $sql .= " AND NOT EXISTS (SELECT rowid from ".MAIN_DB_PREFIX."facture_fourn as ff WHERE f.rowid = ff.fk_facture_source";
2702 $sql .= " AND ff.type=".self::TYPE_REPLACEMENT.")";
2703 $sql .= " AND f.type != ".self::TYPE_CREDIT_NOTE; // Type non 2 si facture non avoir
2704 if ($socid > 0) {
2705 $sql .= " AND f.fk_soc = ".((int) $socid);
2706 }
2707 $sql .= " ORDER BY f.ref";
2708
2709 dol_syslog(get_class($this)."::list_qualified_avoir_supplier_invoices", LOG_DEBUG);
2710 $resql = $this->db->query($sql);
2711 if ($resql) {
2712 while ($obj = $this->db->fetch_object($resql)) {
2713 $qualified = 0;
2714 if ($obj->fk_statut == self::STATUS_VALIDATED) {
2715 $qualified = 1;
2716 }
2717 if ($obj->fk_statut == self::STATUS_CLOSED) {
2718 $qualified = 1;
2719 }
2720 if ($qualified) {
2721 $paymentornot = ($obj->fk_paiementfourn ? 1 : 0);
2722 $return[$obj->rowid] = array('ref' => (string) $obj->ref, 'status' => (int) $obj->fk_statut, 'type' => (int) $obj->type, 'paye' => (int) $obj->paye, 'paymentornot' => (int) $paymentornot);
2723 }
2724 }
2725
2726 return $return;
2727 } else {
2728 $this->error = $this->db->error();
2729 return -1;
2730 }
2731 }
2732
2733 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2740 public function load_board($user)
2741 {
2742 // phpcs:enable
2743 global $conf, $langs;
2744
2745 $sql = 'SELECT ff.rowid, ff.date_lim_reglement as datefin, ff.fk_statut as status, ff.total_ht, ff.total_ttc';
2746 $sql .= ' FROM '.MAIN_DB_PREFIX.'facture_fourn as ff';
2747 if (empty($user->socid) && !$user->hasRight("societe", "client", "voir")) {
2748 $sql .= " JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON ff.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
2749 }
2750 $sql .= ' WHERE ff.paye = 0';
2751 $sql .= " AND ff.fk_statut IN (".self::STATUS_VALIDATED.")";
2752 $sql .= " AND ff.entity = ".$conf->entity;
2753 if ($user->socid) {
2754 $sql .= ' AND ff.fk_soc = '.((int) $user->socid);
2755 }
2756
2757 $resql = $this->db->query($sql);
2758 if ($resql) {
2759 $langs->load("bills");
2760 $now = dol_now();
2761
2762 $response = new WorkboardResponse();
2763 $response->warning_delay = $conf->facture->fournisseur->warning_delay / 60 / 60 / 24;
2764 $response->label = $langs->trans("SupplierBillsToPay");
2765 $response->labelShort = $langs->trans("StatusToPay");
2766
2767 $response->url = DOL_URL_ROOT.'/fourn/facture/list.php?search_status=1&mainmenu=billing&leftmenu=suppliers_bills';
2768 $response->img = img_object($langs->trans("Bills"), "bill");
2769
2770 $facturestatic = new FactureFournisseur($this->db);
2771
2772 while ($obj = $this->db->fetch_object($resql)) {
2773 $facturestatic->date_echeance = $this->db->jdate($obj->datefin);
2774 $facturestatic->statut = $obj->status; // For backward compatibility
2775 $facturestatic->status = $obj->status;
2776
2777 $response->nbtodo++;
2778 $response->total += $obj->total_ht;
2779
2780 if ($facturestatic->hasDelay()) {
2781 $response->nbtodolate++;
2782 $response->url_late = DOL_URL_ROOT.'/fourn/facture/list.php?search_option=late&mainmenu=billing&leftmenu=suppliers_bills';
2783 }
2784 }
2785
2786 $this->db->free($resql);
2787 return $response;
2788 } else {
2789 dol_print_error($this->db);
2790 $this->error = $this->db->error();
2791 return -1;
2792 }
2793 }
2794
2802 public function getTooltipContentArray($params)
2803 {
2804 global $conf, $langs, $mysoc;
2805
2806 $langs->load('bills');
2807
2808 $datas = [];
2809 $moretitle = $params['moretitle'] ?? '';
2810
2811 $picto = $this->picto;
2812 if ($this->type == self::TYPE_REPLACEMENT) {
2813 $picto .= 'r'; // Replacement invoice
2814 }
2815 if ($this->type == self::TYPE_CREDIT_NOTE) {
2816 $picto .= 'a'; // Credit note
2817 }
2818 if ($this->type == self::TYPE_DEPOSIT) {
2819 $picto .= 'd'; // Deposit invoice
2820 }
2821
2822 $datas['picto'] = img_picto('', $picto).' <u class="paddingrightonly">'.$langs->trans("SupplierInvoice").'</u>';
2823 if ($this->type == self::TYPE_REPLACEMENT) {
2824 $datas['picto'] .= '<u class="paddingrightonly">'.$langs->transnoentitiesnoconv("InvoiceReplacement").'</u>';
2825 } elseif ($this->type == self::TYPE_CREDIT_NOTE) {
2826 $datas['picto'] .= '<u class="paddingrightonly">'.$langs->transnoentitiesnoconv("CreditNote").'</u>';
2827 } elseif ($this->type == self::TYPE_DEPOSIT) {
2828 $datas['picto'] .= '<u class="paddingrightonly">'.$langs->transnoentitiesnoconv("Deposit").'</u>';
2829 }
2830 if (isset($this->status)) {
2831 $alreadypaid = -1;
2832 if (isset($this->totalpaid)) {
2833 $alreadypaid = $this->totalpaid;
2834 }
2835
2836 $datas['picto'] .= ' '.$this->getLibStatut(5, $alreadypaid);
2837 }
2838 if ($moretitle) {
2839 $datas['picto'] .= ' - '.$moretitle;
2840 }
2841 if (!empty($this->ref)) {
2842 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
2843 }
2844 if (!empty($this->ref_supplier)) {
2845 $datas['refsupplier'] = '<br><b>'.$langs->trans('RefSupplier').':</b> '.$this->ref_supplier;
2846 }
2847 if (!empty($this->label)) {
2848 $datas['label'] = '<br><b>'.$langs->trans('Label').':</b> '.$this->label;
2849 }
2850 if (!empty($this->date)) {
2851 $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
2852 }
2853 if (!empty($this->date_echeance)) {
2854 $datas['date_echeance'] = '<br><b>'.$langs->trans('DateDue').':</b> '.dol_print_date($this->date_echeance, 'day');
2855 }
2856 if (!empty($this->total_ht)) {
2857 $datas['amountht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
2858 }
2859 if (!empty($this->total_tva)) {
2860 $datas['totaltva'] = '<br><b>'.$langs->trans('AmountVAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
2861 }
2862 if (!empty($this->total_localtax1) && $this->total_localtax1 != 0) {
2863 // We keep test != 0 because $this->total_localtax1 can be '0.00000000'
2864 $datas['amountlt1'] = '<br><b>'.$langs->transcountry('AmountLT1', $mysoc->country_code).':</b> '.price($this->total_localtax1, 0, $langs, 0, -1, -1, $conf->currency);
2865 }
2866 if (!empty($this->total_localtax2) && $this->total_localtax2 != 0) {
2867 $datas['amountlt2'] = '<br><b>'.$langs->transcountry('AmountLT2', $mysoc->country_code).':</b> '.price($this->total_localtax2, 0, $langs, 0, -1, -1, $conf->currency);
2868 }
2869 if (!empty($this->revenuestamp)) {
2870 $datas['amountrevenustamp'] = '<br><b>'.$langs->trans('RevenueStamp').':</b> '.price($this->revenuestamp, 0, $langs, 0, -1, -1, $conf->currency);
2871 }
2872 if (!empty($this->total_ttc)) {
2873 $datas['totalttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
2874 }
2875 return $datas;
2876 }
2877
2891 public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $moretitle = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = 0)
2892 {
2893 global $langs, $conf, $user, $hookmanager;
2894
2895 $result = '';
2896
2897 if ($option == 'withdraw') {
2898 $url = DOL_URL_ROOT.'/compta/facture/prelevement.php?facid='.$this->id.'&type=bank-transfer';
2899 } elseif ($option == 'document') {
2900 $url = DOL_URL_ROOT.'/fourn/facture/document.php?facid='.$this->id;
2901 } else {
2902 $url = DOL_URL_ROOT.'/fourn/facture/card.php?facid='.$this->id;
2903 }
2904
2905 if ($short) {
2906 return $url;
2907 }
2908
2909 if ($option !== 'nolink') {
2910 // Add param to save lastsearch_values or not
2911 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
2912 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
2913 $add_save_lastsearch_values = 1;
2914 }
2915 if ($add_save_lastsearch_values) {
2916 $url .= '&save_lastsearch_values=1';
2917 }
2918 }
2919
2920 $picto = $this->picto;
2921 if ($this->type == self::TYPE_REPLACEMENT) {
2922 $picto .= 'r'; // Replacement invoice
2923 }
2924 if ($this->type == self::TYPE_CREDIT_NOTE) {
2925 $picto .= 'a'; // Credit note
2926 }
2927 if ($this->type == self::TYPE_DEPOSIT) {
2928 $picto .= 'd'; // Deposit invoice
2929 }
2930
2931 $params = [
2932 'id' => $this->id,
2933 'objecttype' => $this->element,
2934 'option' => $option,
2935 'moretitle' => $moretitle,
2936 ];
2937 $classfortooltip = 'classfortooltip';
2938 $dataparams = '';
2939 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
2940 $classfortooltip = 'classforajaxtooltip';
2941 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
2942 $label = '';
2943 } else {
2944 $label = implode($this->getTooltipContentArray($params));
2945 }
2946
2947 $ref = $this->ref;
2948 if (empty($ref)) {
2949 $ref = $this->id;
2950 }
2951
2952 $linkclose = '';
2953 if (empty($notooltip)) {
2954 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2955 $label = $langs->trans("ShowSupplierInvoice");
2956 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
2957 }
2958 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
2959 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
2960 }
2961
2962 $linkstart = '<a href="'.$url.'"';
2963 $linkstart .= $linkclose.'>';
2964 $linkend = '</a>';
2965
2966 $result .= $linkstart;
2967 if ($withpicto) {
2968 $result .= img_object(($notooltip ? '' : $label), ($picto ? $picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'"'), 0, 0, $notooltip ? 0 : 1);
2969 }
2970 if ($withpicto != 2) {
2971 $result .= ($max ? dol_trunc($ref, $max) : $ref);
2972 }
2973 $result .= $linkend;
2974
2975 if ($addlinktonotes) {
2976 $txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
2977 if ($txttoshow) {
2978 $notetoshow = $langs->trans("ViewPrivateNote").':<br>'.dol_string_nohtmltag($txttoshow, 1);
2979 $result .= ' <span class="note inline-block">';
2980 $result .= '<a href="'.DOL_URL_ROOT.'/fourn/facture/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($notetoshow).'">';
2981 $result .= img_picto('', 'note');
2982 $result .= '</a>';
2983 $result .= '</span>';
2984 }
2985 }
2986 global $action;
2987 $hookmanager->initHooks(array($this->element . 'dao'));
2988 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
2989 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2990 if ($reshook > 0) {
2991 $result = $hookmanager->resPrint;
2992 } else {
2993 $result .= $hookmanager->resPrint;
2994 }
2995 return $result;
2996 }
2997
3008 public function setCategories($categories)
3009 {
3010 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
3011 return parent::setCategoriesCommon($categories, Categorie::TYPE_SUPPLIER_INVOICE);
3012 }
3013
3022 public function getNextNumRef($soc, $mode = 'next')
3023 {
3024 global $db, $langs, $conf;
3025 $langs->load("orders");
3026
3027 // Clean parameters (if not defined or using deprecated value)
3028 if (!getDolGlobalString('INVOICE_SUPPLIER_ADDON_NUMBER')) {
3029 $conf->global->INVOICE_SUPPLIER_ADDON_NUMBER = 'mod_facture_fournisseur_cactus';
3030 }
3031
3032 $mybool = false;
3033
3034 $file = getDolGlobalString('INVOICE_SUPPLIER_ADDON_NUMBER') . ".php";
3035 $classname = getDolGlobalString('INVOICE_SUPPLIER_ADDON_NUMBER');
3036
3037 // Include file with class
3038 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3039
3040 foreach ($dirmodels as $reldir) {
3041 $dir = dol_buildpath($reldir."core/modules/supplier_invoice/");
3042
3043 // Load file with numbering class (if found)
3044 $mybool = ((bool) @include_once $dir.$file) || $mybool;
3045 }
3046
3047 if (!$mybool) {
3048 dol_print_error(null, "Failed to include file ".$file);
3049 return '';
3050 }
3051
3052 $obj = new $classname();
3053 '@phan-var-force ModeleNumRefSuppliersInvoices $obj';
3054 $numref = "";
3055 $numref = $obj->getNextValue($soc, $this, $mode);
3056
3057 if ($numref != "") {
3058 return $numref;
3059 } else {
3060 $this->error = $obj->error;
3061 return -1;
3062 }
3063 }
3064
3065
3074 public function initAsSpecimen($option = '')
3075 {
3076 global $langs, $conf;
3077 include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
3078
3079 $now = dol_now();
3080
3081 // Load array of products prodids
3082 $num_prods = 0;
3083 $prodids = array();
3084
3085 $sql = "SELECT rowid";
3086 $sql .= " FROM ".MAIN_DB_PREFIX."product";
3087 $sql .= " WHERE entity IN (".getEntity('product').")";
3088 $sql .= $this->db->plimit(100);
3089
3090 $resql = $this->db->query($sql);
3091 if ($resql) {
3092 $num_prods = $this->db->num_rows($resql);
3093 $i = 0;
3094 while ($i < $num_prods) {
3095 $i++;
3096 $row = $this->db->fetch_row($resql);
3097 $prodids[$i] = $row[0];
3098 }
3099 }
3100
3101 // Initialise parameters
3102 $this->id = 0;
3103 $this->ref = 'SPECIMEN';
3104 $this->ref_supplier = 'SUPPLIER_REF_SPECIMEN';
3105 $this->specimen = 1;
3106 $this->socid = 1;
3107 $this->date = $now;
3108 $this->date_lim_reglement = $this->date + 3600 * 24 * 30;
3109 $this->cond_reglement_code = 'RECEP';
3110 $this->mode_reglement_code = 'CHQ';
3111
3112 $this->note_public = 'This is a comment (public)';
3113 $this->note_private = 'This is a comment (private)';
3114
3115 $this->multicurrency_tx = 1;
3116 $this->multicurrency_code = $conf->currency;
3117
3118 $xnbp = 0;
3119 if (empty($option) || $option != 'nolines') {
3120 // Lines
3121 $nbp = min(1000, GETPOSTINT('nblines') ? GETPOSTINT('nblines') : 5); // We can force the nb of lines to test from command line (but not more than 1000)
3122 while ($xnbp < $nbp) {
3123 $line = new SupplierInvoiceLine($this->db);
3124 $line->desc = $langs->trans("Description")." ".$xnbp;
3125 $line->qty = 1;
3126 $line->subprice = 100;
3127 $line->pu_ht = $line->subprice; // the canelle template use pu_ht and not subprice
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">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl(1) : $this->ref).'</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 (property_exists($this, 'date')) {
3442 $return .= '<br><span class="info-box-label">'.dol_print_date($this->date, 'day').'</span>';
3443 }
3444 if (property_exists($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 if (method_exists($this, 'getLibStatut')) {
3450 $alreadypaid = (empty($arraydata['alreadypaid']) ? 0 : $arraydata['alreadypaid']);
3451 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3, $alreadypaid).'</div>';
3452 }
3453 $return .= '</div>';
3454 $return .= '</div>';
3455 $return .= '</div>';
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(empty($conf->global->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 = $conf->global->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( $user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
Definition card.php:67
$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.
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.
call_trigger($triggerName, $user)
Call trigger based on this instance.
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
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:660
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:63
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_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.
dol_now($mode='auto')
Return date for now.
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_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
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_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.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0)
Clean a string to use it as a file name.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
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...
global $conf
The following vars must be defined: $type2label $form $conf, $lang, The following vars may also be de...
Definition member.php:79
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(preg_match('/(crypted|dolcrypt):/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
Definition repair.php:158