dolibarr 21.0.3
fournisseur.facture.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2002-2004 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2004-2012 Laurent Destailleur <eldy@users.sourceforge.net>
4 * Copyright (C) 2004 Christophe Combelles <ccomb@free.fr>
5 * Copyright (C) 2005 Marc Barilley <marc@ocebo.com>
6 * Copyright (C) 2005-2012 Regis Houssin <regis.houssin@inodbox.com>
7 * Copyright (C) 2010-2023 Juanjo Menent <jmenent@simnandez.es>
8 * Copyright (C) 2013-2019 Philippe Grand <philippe.grand@atoo-net.com>
9 * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
10 * Copyright (C) 2014-2016 Marcos García <marcosgdf@gmail.com>
11 * Copyright (C) 2015 Bahfir Abbes <bafbes@gmail.com>
12 * Copyright (C) 2015-2022 Ferran Marcet <fmarcet@2byte.es>
13 * Copyright (C) 2016-2023 Alexandre Spangaro <aspangaro@open-dsi.fr>
14 * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
15 * Copyright (C) 2018-2024 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 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($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->db->escape($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 $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 // Retrieve all extrafield
1017 // fetch optionals attributes and labels
1018 $this->fetch_optionals();
1019
1020 $result = $this->fetch_lines();
1021 if ($result < 0) {
1022 $this->error = $this->db->lasterror();
1023 return -3;
1024 }
1025 } else {
1026 $this->error = 'Bill with id '.$id.' not found';
1027 dol_syslog(get_class($this).'::fetch '.$this->error);
1028 return 0;
1029 }
1030
1031 $this->db->free($resql);
1032 return 1;
1033 } else {
1034 $this->error = "Error ".$this->db->lasterror();
1035 return -1;
1036 }
1037 }
1038
1039
1040 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1046 public function fetch_lines()
1047 {
1048 // phpcs:enable
1049 $this->lines = array();
1050
1051 $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';
1052 $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';
1053 $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';
1054 $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';
1055 $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';
1056 $sql .= ' FROM '.MAIN_DB_PREFIX.'facture_fourn_det as f';
1057 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON f.fk_product = p.rowid';
1058 $sql .= ' WHERE fk_facture_fourn='.((int) $this->id);
1059 $sql .= ' ORDER BY f.rang, f.rowid';
1060
1061 dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
1062
1063 $resql_rows = $this->db->query($sql);
1064 if ($resql_rows) {
1065 $num_rows = $this->db->num_rows($resql_rows);
1066 if ($num_rows) {
1067 $i = 0;
1068 while ($i < $num_rows) {
1069 $obj = $this->db->fetch_object($resql_rows);
1070
1071 $line = new SupplierInvoiceLine($this->db);
1072
1073 $line->id = $obj->rowid;
1074 $line->rowid = $obj->rowid;
1075
1076 $line->description = $obj->line_desc;
1077 $line->desc = $obj->line_desc;
1078 $line->date_start = $this->db->jdate($obj->date_start);
1079 $line->date_end = $this->db->jdate($obj->date_end);
1080
1081 $line->product_ref = $obj->product_ref;
1082 $line->ref = $obj->product_ref;
1083 $line->ref_supplier = $obj->ref_supplier;
1084 $line->libelle = $obj->label;
1085 $line->label = $obj->label;
1086 $line->product_barcode = $obj->product_barcode;
1087 $line->product_desc = $obj->product_desc;
1088 $line->subprice = $obj->pu_ht;
1089 $line->pu_ht = $obj->pu_ht;
1090 $line->pu_ttc = $obj->pu_ttc;
1091 $line->vat_src_code = $obj->vat_src_code;
1092 $line->tva_tx = $obj->tva_tx;
1093 $line->localtax1_tx = $obj->localtax1_tx;
1094 $line->localtax2_tx = $obj->localtax2_tx;
1095 $line->localtax1_type = $obj->localtax1_type;
1096 $line->localtax2_type = $obj->localtax2_type;
1097 $line->qty = $obj->qty;
1098 $line->remise_percent = $obj->remise_percent;
1099 $line->fk_remise_except = $obj->fk_remise_except;
1100 //$line->tva = $obj->total_tva; // deprecated
1101 $line->total_ht = $obj->total_ht;
1102 $line->total_ttc = $obj->total_ttc;
1103 $line->total_tva = $obj->total_tva;
1104 $line->total_localtax1 = $obj->total_localtax1;
1105 $line->total_localtax2 = $obj->total_localtax2;
1106 $line->fk_facture_fourn = $obj->fk_facture_fourn;
1107 $line->fk_product = $obj->fk_product;
1108 $line->product_type = $obj->product_type;
1109 $line->product_label = $obj->label;
1110 $line->info_bits = $obj->info_bits;
1111 $line->fk_parent_line = $obj->fk_parent_line;
1112 $line->special_code = $obj->special_code;
1113 $line->rang = $obj->rang;
1114 $line->fk_unit = $obj->fk_unit;
1115
1116 // Accountancy
1117 $line->fk_accounting_account = $obj->fk_code_ventilation;
1118
1119 // Multicurrency
1120 $line->fk_multicurrency = $obj->fk_multicurrency;
1121 $line->multicurrency_code = $obj->multicurrency_code;
1122 $line->multicurrency_subprice = $obj->multicurrency_subprice;
1123 $line->multicurrency_total_ht = $obj->multicurrency_total_ht;
1124 $line->multicurrency_total_tva = $obj->multicurrency_total_tva;
1125 $line->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1126
1127 // Extra fields
1128 $line->fetch_optionals();
1129
1130 $this->lines[$i] = $line;
1131
1132 $i++;
1133 }
1134 }
1135 $this->db->free($resql_rows);
1136 return 1;
1137 } else {
1138 $this->error = $this->db->error();
1139 dol_syslog(get_class($this)."::fetch_lines - No lines:{$this->error} Error:{$this->error}", LOG_DEBUG);
1140 return -3;
1141 }
1142 }
1143
1144
1152 public function update($user = null, $notrigger = 0)
1153 {
1154 global $langs;
1155 $error = 0;
1156
1157 // Clean parameters
1158 if (empty($this->type)) {
1159 $this->type = self::TYPE_STANDARD;
1160 }
1161 if (isset($this->ref)) {
1162 $this->ref = trim($this->ref);
1163 }
1164 if (isset($this->ref_supplier)) {
1165 $this->ref_supplier = trim($this->ref_supplier);
1166 }
1167 if (isset($this->ref_ext)) {
1168 $this->ref_ext = trim($this->ref_ext);
1169 }
1170 if (isset($this->entity)) {
1171 $this->entity = (int) $this->entity;
1172 }
1173 if (isset($this->type)) {
1174 $this->type = (int) $this->type;
1175 }
1176 if (isset($this->subtype)) {
1177 $this->subtype = (int) $this->subtype;
1178 }
1179 if (isset($this->socid)) {
1180 $this->socid = (int) $this->socid;
1181 }
1182 if (isset($this->label)) {
1183 $this->label = trim($this->label);
1184 }
1185 if (isset($this->paid)) {
1186 $this->paid = (int) (bool) $this->paye;
1187 $this->paye = $this->paid;
1188 } elseif (isset($this->paye)) {
1189 $this->paid = (int) (bool) $this->paye;
1190 $this->paye = $this->paid;
1191 }
1192 if (isset($this->close_code)) {
1193 $this->close_code = trim($this->close_code);
1194 }
1195 if (isset($this->close_note)) {
1196 $this->close_note = trim($this->close_note);
1197 }
1198 if (empty($this->total_ht)) {
1199 $this->total_ht = 0;
1200 }
1201 if (empty($this->total_tva)) {
1202 $this->total_tva = 0;
1203 }
1204 if (empty($this->total_localtax1)) {
1205 $this->total_localtax1 = 0;
1206 }
1207 if (empty($this->total_localtax2)) {
1208 $this->total_localtax2 = 0;
1209 }
1210 if (isset($this->total_ttc)) {
1211 $this->total_ttc = (float) $this->total_ttc;
1212 }
1213 if (isset($this->status)) {
1214 $this->status = (int) $this->status;
1215 $this->statut = $this->status;
1216 } elseif (isset($this->statut)) {
1217 $this->status = (int) $this->statut;
1218 $this->statut = $this->status;
1219 }
1220 if (isset($this->author)) { // TODO: user_creation_id?
1221 $this->author = (int) $this->author;
1222 }
1223 if (isset($this->fk_user_valid)) {
1224 $this->fk_user_valid = (int) $this->fk_user_valid;
1225 }
1226 if (isset($this->fk_facture_source)) {
1227 $this->fk_facture_source = (int) $this->fk_facture_source;
1228 }
1229 if (isset($this->fk_project)) {
1230 if (empty($this->fk_project)) {
1231 $this->fk_project = 0;
1232 } else {
1233 $this->fk_project = (int) $this->fk_project;
1234 }
1235 }
1236 if (isset($this->cond_reglement_id)) {
1237 $this->cond_reglement_id = (int) $this->cond_reglement_id;
1238 }
1239 if (isset($this->note_private)) {
1240 $this->note_private = trim($this->note_private);
1241 $this->note = $this->note_private;
1242 }
1243 if (isset($this->note_public)) {
1244 $this->note_public = trim($this->note_public);
1245 }
1246 if (isset($this->model_pdf)) {
1247 $this->model_pdf = trim($this->model_pdf);
1248 }
1249 if (isset($this->import_key)) {
1250 $this->import_key = trim($this->import_key);
1251 }
1252
1253
1254 // Check parameters
1255 // Put here code to add control on parameters values
1256
1257 // Update request
1258 $sql = "UPDATE ".MAIN_DB_PREFIX."facture_fourn SET";
1259 $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
1260 $sql .= " ref_supplier=".(isset($this->ref_supplier) ? "'".$this->db->escape($this->ref_supplier)."'" : "null").",";
1261 $sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
1262 $sql .= " entity=".(isset($this->entity) ? ((int) $this->entity) : "null").",";
1263 $sql .= " type=".(isset($this->type) ? ((int) $this->type) : "null").",";
1264 $sql .= " subtype=".((int) $this->subtype).",";
1265 $sql .= " fk_soc=".(isset($this->socid) ? ((int) $this->socid) : "null").",";
1266 $sql .= " datec=".(dol_strlen((string) $this->datec) != 0 ? "'".$this->db->idate($this->datec)."'" : 'null').",";
1267 $sql .= " datef=".(dol_strlen((string) $this->date) != 0 ? "'".$this->db->idate($this->date)."'" : 'null').",";
1268 if (dol_strlen((string) $this->tms) != 0) {
1269 $sql .= " tms=".(dol_strlen((string) $this->tms) != 0 ? "'".$this->db->idate($this->tms)."'" : 'null').",";
1270 }
1271 $sql .= " libelle=".(isset($this->label) ? "'".$this->db->escape($this->label)."'" : "null").",";
1272 $sql .= " paye=".(isset($this->paid) ? ((int) $this->paid) : "0").",";
1273 $sql .= " close_code=".(isset($this->close_code) ? "'".$this->db->escape($this->close_code)."'" : "null").",";
1274 $sql .= " close_note=".(isset($this->close_note) ? "'".$this->db->escape($this->close_note)."'" : "null").",";
1275 $sql .= " localtax1=".(isset($this->total_localtax1) ? ((float) $this->total_localtax1) : "null").",";
1276 $sql .= " localtax2=".(isset($this->total_localtax2) ? ((float) $this->total_localtax2) : "null").",";
1277 $sql .= " total_ht=".(isset($this->total_ht) ? ((float) $this->total_ht) : "null").",";
1278 $sql .= " total_tva=".(isset($this->total_tva) ? ((float) $this->total_tva) : "null").",";
1279 $sql .= " total_ttc=".(isset($this->total_ttc) ? ((float) $this->total_ttc) : "null").",";
1280 $sql .= " fk_statut=".(isset($this->status) ? ((int) $this->status) : (isset($this->statut) ? ((int) $this->statut) : "null")).",";
1281 $sql .= " fk_user_author=".(isset($this->author) ? ((int) $this->author) : "null").",";
1282 $sql .= " fk_user_valid=".(isset($this->fk_user_valid) ? ((int) $this->fk_user_valid) : "null").",";
1283 $sql .= " fk_facture_source=".($this->fk_facture_source ? ((int) $this->fk_facture_source) : "null").",";
1284 $sql .= " vat_reverse_charge = ".($this->vat_reverse_charge != '' ? ((int) $this->db->escape($this->vat_reverse_charge)) : 0).",";
1285 $sql .= " fk_projet=".(!empty($this->fk_project) ? ((int) $this->fk_project) : "null").",";
1286 $sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? ((int) $this->cond_reglement_id) : "null").",";
1287 $sql .= " date_lim_reglement=".(dol_strlen((string) $this->date_echeance) != 0 ? "'".$this->db->idate($this->date_echeance)."'" : 'null').",";
1288 $sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
1289 $sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
1290 $sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
1291 $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null");
1292 $sql .= " WHERE rowid=".((int) $this->id);
1293
1294 $this->db->begin();
1295
1296 dol_syslog(get_class($this)."::update", LOG_DEBUG);
1297 $resql = $this->db->query($sql);
1298
1299 if (!$resql) {
1300 $error++;
1301
1302 if ($this->db->errno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1303 $this->errors[] = $langs->trans('ErrorRefAlreadyExists');
1304 } else {
1305 $this->errors[] = "Error ".$this->db->lasterror();
1306 }
1307 }
1308
1309 if (!$error) {
1310 $result = $this->insertExtraFields();
1311 if ($result < 0) {
1312 $error++;
1313 }
1314 }
1315
1316 if (!$error) {
1317 if (!$notrigger) {
1318 // Call trigger
1319 $result = $this->call_trigger('BILL_SUPPLIER_MODIFY', $user);
1320 if ($result < 0) {
1321 $error++;
1322 }
1323 // End call triggers
1324 }
1325 }
1326
1327 // Commit or rollback
1328 if ($error) {
1329 foreach ($this->errors as $errmsg) {
1330 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1331 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1332 }
1333 $this->db->rollback();
1334 return -1 * $error;
1335 } else {
1336 $this->db->commit();
1337 return 1;
1338 }
1339 }
1340
1341 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1348 public function insert_discount($idremise)
1349 {
1350 // phpcs:enable
1351 global $conf, $langs;
1352
1353 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1354 include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
1355
1356 $this->db->begin();
1357
1358 $remise = new DiscountAbsolute($this->db);
1359 $result = $remise->fetch($idremise);
1360
1361 if ($result > 0) {
1362 if ($remise->fk_invoice_supplier) { // Protection against multiple submission
1363 $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
1364 $this->db->rollback();
1365 return -5;
1366 }
1367
1368 $facligne = new SupplierInvoiceLine($this->db);
1369 $facligne->fk_facture_fourn = $this->id;
1370 $facligne->fk_remise_except = $remise->id;
1371 $facligne->desc = $remise->description; // Description ligne
1372 $facligne->vat_src_code = $remise->vat_src_code;
1373 $facligne->tva_tx = $remise->tva_tx;
1374 $facligne->subprice = -(float) $remise->amount_ht;
1375 $facligne->fk_product = 0; // Id produit predefini
1376 $facligne->product_type = 0;
1377 $facligne->qty = 1;
1378 $facligne->remise_percent = 0;
1379 $facligne->rang = -1;
1380 $facligne->info_bits = 2;
1381
1382 if (getDolGlobalString('MAIN_ADD_LINE_AT_POSITION')) {
1383 $facligne->rang = 1;
1384 $linecount = count($this->lines);
1385 for ($ii = 1; $ii <= $linecount; $ii++) {
1386 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
1387 }
1388 }
1389
1390 // Get buy/cost price of invoice that is source of discount
1391 if ($remise->fk_invoice_supplier_source > 0) {
1392 $srcinvoice = new FactureFournisseur($this->db);
1393 $srcinvoice->fetch($remise->fk_invoice_supplier_source);
1394 $totalcostpriceofinvoice = 0;
1395 include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmargin.class.php'; // TODO Move this into commonobject
1396 $formmargin = new FormMargin($this->db);
1397 $arraytmp = $formmargin->getMarginInfosArray($srcinvoice, false);
1398 $facligne->pa_ht = $arraytmp['pa_total'];
1399 }
1400
1401 $facligne->total_ht = -(float) $remise->amount_ht;
1402 $facligne->total_tva = -(float) $remise->amount_tva;
1403 $facligne->total_ttc = -(float) $remise->amount_ttc;
1404
1405 $facligne->multicurrency_subprice = -$remise->multicurrency_subprice;
1406 $facligne->multicurrency_total_ht = -$remise->multicurrency_total_ht;
1407 $facligne->multicurrency_total_tva = -$remise->multicurrency_total_tva;
1408 $facligne->multicurrency_total_ttc = -$remise->multicurrency_total_ttc;
1409
1410 $lineid = $facligne->insert();
1411 if ($lineid > 0) {
1412 $result = $this->update_price(1);
1413 if ($result > 0) {
1414 // Create link between discount and invoice line
1415 $result = $remise->link_to_invoice($lineid, 0);
1416 if ($result < 0) {
1417 $this->error = $remise->error;
1418 $this->db->rollback();
1419 return -4;
1420 }
1421
1422 $this->db->commit();
1423 return 1;
1424 } else {
1425 $this->error = $facligne->error;
1426 $this->db->rollback();
1427 return -1;
1428 }
1429 } else {
1430 $this->error = $facligne->error;
1431 $this->db->rollback();
1432 return -2;
1433 }
1434 } else {
1435 $this->db->rollback();
1436 return -3;
1437 }
1438 }
1439
1440
1448 public function delete(User $user, $notrigger = 0)
1449 {
1450 global $conf;
1451
1452 $rowid = $this->id;
1453
1454 dol_syslog("FactureFournisseur::delete rowid=".$rowid, LOG_DEBUG);
1455
1456 // TODO Test if there is at least on payment. If yes, refuse to delete.
1457
1458 $error = 0;
1459 $this->db->begin();
1460
1461 if (!$error && !$notrigger) {
1462 // Call trigger
1463 $result = $this->call_trigger('BILL_SUPPLIER_DELETE', $user);
1464 if ($result < 0) {
1465 $this->db->rollback();
1466 return -1;
1467 }
1468 // Fin appel triggers
1469 }
1470
1471 if (!$error) {
1472 // If invoice was converted into a discount not yet consumed, we remove discount
1473 $sql = 'DELETE FROM '.MAIN_DB_PREFIX.'societe_remise_except';
1474 $sql .= ' WHERE fk_invoice_supplier_source = '.((int) $rowid);
1475 $sql .= ' AND fk_invoice_supplier_line IS NULL';
1476 $resql = $this->db->query($sql);
1477
1478 // If invoice has consumned discounts
1479 $this->fetch_lines();
1480 $list_rowid_det = array();
1481 foreach ($this->lines as $key => $invoiceline) {
1482 $list_rowid_det[] = $invoiceline->id;
1483 }
1484
1485 // Consumned discounts are freed
1486 if (count($list_rowid_det)) {
1487 $sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
1488 $sql .= ' SET fk_invoice_supplier = NULL, fk_invoice_supplier_line = NULL';
1489 $sql .= ' WHERE fk_invoice_supplier_line IN ('.$this->db->sanitize(implode(',', $list_rowid_det)).')';
1490
1491 dol_syslog(get_class($this)."::delete", LOG_DEBUG);
1492 if (!$this->db->query($sql)) {
1493 $error++;
1494 }
1495 }
1496 }
1497
1498 if (!$error) {
1499 $main = MAIN_DB_PREFIX.'facture_fourn_det';
1500 $ef = $main."_extrafields";
1501 $sqlef = "DELETE FROM $ef WHERE fk_object IN (SELECT rowid FROM ".$main." WHERE fk_facture_fourn = ".((int) $rowid).")";
1502 $resqlef = $this->db->query($sqlef);
1503 $sql = 'DELETE FROM '.MAIN_DB_PREFIX.'facture_fourn_det WHERE fk_facture_fourn = '.((int) $rowid);
1504 dol_syslog(get_class($this)."::delete", LOG_DEBUG);
1505 $resql = $this->db->query($sql);
1506 if ($resqlef && $resql) {
1507 $sql = 'DELETE FROM '.MAIN_DB_PREFIX.'facture_fourn WHERE rowid = '.((int) $rowid);
1508 dol_syslog(get_class($this)."::delete", LOG_DEBUG);
1509 $resql2 = $this->db->query($sql);
1510 if (!$resql2) {
1511 $error++;
1512 }
1513 } else {
1514 $error++;
1515 }
1516 }
1517
1518 if (!$error) {
1519 // Delete linked object
1520 $res = $this->deleteObjectLinked();
1521 if ($res < 0) {
1522 $error++;
1523 }
1524 }
1525
1526 if (!$error) {
1527 // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
1528 $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
1529 $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
1530
1531 // We remove directory
1532 if ($conf->fournisseur->facture->dir_output) {
1533 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1534
1535 $ref = dol_sanitizeFileName($this->ref);
1536 $dir = $conf->fournisseur->facture->dir_output.'/'.get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier').$ref;
1537 $file = $dir."/".$ref.".pdf";
1538 if (file_exists($file)) {
1539 if (!dol_delete_file($file, 0, 0, 0, $this)) { // For triggers
1540 $this->error = 'ErrorFailToDeleteFile';
1541 $error++;
1542 }
1543 }
1544 if (file_exists($dir)) {
1545 $res = @dol_delete_dir_recursive($dir);
1546
1547 if (!$res) {
1548 $this->error = 'ErrorFailToDeleteDir';
1549 $error++;
1550 }
1551 }
1552 }
1553 }
1554
1555 // Remove extrafields
1556 if (!$error) {
1557 $result = $this->deleteExtraFields();
1558 if ($result < 0) {
1559 $error++;
1560 dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
1561 }
1562 }
1563
1564 if (!$error) {
1565 dol_syslog(get_class($this)."::delete $this->id by $user->id", LOG_DEBUG);
1566 $this->db->commit();
1567 return 1;
1568 } else {
1569 $this->error = $this->db->lasterror();
1570 $this->db->rollback();
1571 return -$error;
1572 }
1573 }
1574
1575
1576 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1587 public function set_paid($user, $close_code = '', $close_note = '')
1588 {
1589 // phpcs:enable
1590 dol_syslog(get_class($this)."::set_paid is deprecated, use setPaid instead", LOG_NOTICE);
1591 return $this->setPaid($user, $close_code, $close_note);
1592 }
1593
1602 public function setPaid($user, $close_code = '', $close_note = '')
1603 {
1604 $error = 0;
1605
1606 if ($this->paid != 1) {
1607 $this->db->begin();
1608
1609 $now = dol_now();
1610
1611 dol_syslog("FactureFournisseur::setPaid", LOG_DEBUG);
1612
1613 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture_fourn SET';
1614 $sql .= ' fk_statut = '.self::STATUS_CLOSED;
1615 if (!$close_code) {
1616 $sql .= ', paye=1';
1617 }
1618 if ($close_code) {
1619 $sql .= ", close_code='".$this->db->escape($close_code)."'";
1620 }
1621 if ($close_note) {
1622 $sql .= ", close_note='".$this->db->escape($close_note)."'";
1623 }
1624 $sql .= ', fk_user_closing = '.((int) $user->id);
1625 $sql .= ", date_closing = '".$this->db->idate($now)."'";
1626 $sql .= ' WHERE rowid = '.((int) $this->id);
1627
1628 $resql = $this->db->query($sql);
1629 if ($resql) {
1630 // Call trigger
1631 $result = $this->call_trigger('BILL_SUPPLIER_PAYED', $user);
1632 if ($result < 0) {
1633 $error++;
1634 }
1635 // End call triggers
1636 } else {
1637 $error++;
1638 $this->error = $this->db->error();
1639 dol_print_error($this->db);
1640 }
1641
1642 if (!$error) {
1643 $this->db->commit();
1644 return 1;
1645 } else {
1646 $this->db->rollback();
1647 return -1;
1648 }
1649 } else {
1650 return 0;
1651 }
1652 }
1653
1654 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1665 public function set_unpaid($user)
1666 {
1667 // phpcs:enable
1668 dol_syslog(get_class($this)."::set_unpaid is deprecated, use setUnpaid instead", LOG_NOTICE);
1669 return $this->setUnpaid($user);
1670 }
1671
1680 public function setUnpaid($user)
1681 {
1682 $error = 0;
1683
1684 $this->db->begin();
1685
1686 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture_fourn';
1687 $sql .= ' SET paye=0, fk_statut='.self::STATUS_VALIDATED.', close_code=null, close_note=null,';
1688 $sql .= ' date_closing=null,';
1689 $sql .= ' fk_user_closing=null';
1690 $sql .= ' WHERE rowid = '.((int) $this->id);
1691
1692 dol_syslog(get_class($this)."::set_unpaid", LOG_DEBUG);
1693 $resql = $this->db->query($sql);
1694 if ($resql) {
1695 // Call trigger
1696 $result = $this->call_trigger('BILL_SUPPLIER_UNPAYED', $user);
1697 if ($result < 0) {
1698 $error++;
1699 }
1700 // End call triggers
1701 } else {
1702 $error++;
1703 $this->error = $this->db->error();
1704 dol_print_error($this->db);
1705 }
1706
1707 if (!$error) {
1708 $this->db->commit();
1709 return 1;
1710 } else {
1711 $this->db->rollback();
1712 return -1;
1713 }
1714 }
1715
1726 public function setCanceled($user, $close_code = '', $close_note = '')
1727 {
1728 dol_syslog(get_class($this)."::setCanceled rowid=".((int) $this->id), LOG_DEBUG);
1729
1730 $this->db->begin();
1731
1732 $sql = 'UPDATE '.MAIN_DB_PREFIX.'facture_fourn SET';
1733 $sql .= ' fk_statut='.self::STATUS_ABANDONED;
1734 if ($close_code) {
1735 $sql .= ", close_code='".$this->db->escape($close_code)."'";
1736 }
1737 if ($close_note) {
1738 $sql .= ", close_note='".$this->db->escape($close_note)."'";
1739 }
1740 $sql .= " WHERE rowid = ".((int) $this->id);
1741
1742 $resql = $this->db->query($sql);
1743 if ($resql) {
1744 // Bound discounts are deducted from the invoice
1745 // as they have not been used since the invoice is abandoned.
1746 $sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
1747 $sql .= ' SET fk_invoice_supplier = NULL';
1748 $sql .= ' WHERE fk_invoice_supplier = '.((int) $this->id);
1749
1750 $resql = $this->db->query($sql);
1751 if ($resql) {
1752 // Call trigger
1753 $result = $this->call_trigger('BILL_SUPPLIER_CANCEL', $user);
1754 if ($result < 0) {
1755 $this->db->rollback();
1756 return -1;
1757 }
1758 // End call triggers
1759
1760 $this->db->commit();
1761 return 1;
1762 } else {
1763 $this->error = $this->db->error()." sql=".$sql;
1764 $this->db->rollback();
1765 return -1;
1766 }
1767 } else {
1768 $this->error = $this->db->error()." sql=".$sql;
1769 $this->db->rollback();
1770 return -2;
1771 }
1772 }
1773
1783 public function validate($user, $force_number = '', $idwarehouse = 0, $notrigger = 0)
1784 {
1785 global $mysoc, $conf, $langs;
1786
1787 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1788
1789 $now = dol_now();
1790
1791 $error = 0;
1792 dol_syslog(get_class($this).'::validate user='.$user->id.', force_number='.$force_number.', idwarehouse='.$idwarehouse);
1793
1794 // Force to have object complete for checks
1795 $this->fetch_thirdparty();
1796 $this->fetch_lines();
1797
1798 // Check parameters
1799 if ($this->status > self::STATUS_DRAFT) { // This is to avoid to validate twice (avoid errors on logs and stock management)
1800 dol_syslog(get_class($this)."::validate no draft status", LOG_WARNING);
1801 return 0;
1802 }
1803 if (preg_match('/^'.preg_quote($langs->trans("CopyOf").' ').'/', $this->ref_supplier)) {
1804 $langs->load("errors");
1805 $this->error = $langs->trans("ErrorFieldFormat", $langs->transnoentities("RefSupplier")).'. '.$langs->trans('RemoveString', $langs->transnoentitiesnoconv("CopyOf"));
1806 return -1;
1807 }
1808 if (count($this->lines) <= 0) {
1809 $langs->load("errors");
1810 $this->error = $langs->trans("ErrorObjectMustHaveLinesToBeValidated", $this->ref);
1811 return -1;
1812 }
1813
1814 // Check for mandatory fields in thirdparty (defined into setup)
1815 if (!empty($this->thirdparty) && is_object($this->thirdparty)) {
1816 $array_to_check = array('IDPROF1', 'IDPROF2', 'IDPROF3', 'IDPROF4', 'IDPROF5', 'IDPROF6', 'EMAIL', 'ACCOUNTANCY_CODE_SUPPLIER');
1817 foreach ($array_to_check as $key) {
1818 $keymin = strtolower($key);
1819 if ($keymin == 'accountancy_code_supplier') {
1820 $keymin = 'code_compta_fournisseur';
1821 }
1822 if (!property_exists($this->thirdparty, $keymin)) {
1823 continue;
1824 }
1825 $vallabel = $this->thirdparty->$keymin;
1826
1827 $i = (int) preg_replace('/[^0-9]/', '', $key);
1828 if ($i > 0) {
1829 if ($this->thirdparty->isACompany()) {
1830 // Check for mandatory prof id (but only if country is other than ours)
1831 if ($mysoc->country_id > 0 && $this->thirdparty->country_id == $mysoc->country_id) {
1832 $idprof_mandatory = 'SOCIETE_'.$key.'_INVOICE_MANDATORY';
1833 if (!$vallabel && getDolGlobalString($idprof_mandatory)) {
1834 $langs->load("errors");
1835 $this->error = $langs->trans('ErrorProdIdIsMandatory', $langs->transcountry('ProfId'.$i, $this->thirdparty->country_code)).' ('.$langs->trans("ForbiddenBySetupRules").') ['.$langs->trans('Company').' : '.$this->thirdparty->name.']';
1836 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
1837 return -1;
1838 }
1839 }
1840 }
1841 } else {
1842 if ($key == 'EMAIL') {
1843 // Check for mandatory
1844 if (getDolGlobalString('SOCIETE_EMAIL_INVOICE_MANDATORY') && !isValidEmail($this->thirdparty->email)) {
1845 $langs->load("errors");
1846 $this->error = $langs->trans("ErrorBadEMail", $this->thirdparty->email).' ('.$langs->trans("ForbiddenBySetupRules").') ['.$langs->trans('Company').' : '.$this->thirdparty->name.']';
1847 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
1848 return -1;
1849 }
1850 } elseif ($key == 'ACCOUNTANCY_CODE_SUPPLIER') {
1851 // Check for mandatory
1852 if (getDolGlobalString('SOCIETE_ACCOUNTANCY_CODE_SUPPLIER_INVOICE_MANDATORY') && empty($this->thirdparty->code_compta_fournisseur)) {
1853 $langs->load("errors");
1854 $this->error = $langs->trans("ErrorAccountancyCodeSupplierIsMandatory", $this->thirdparty->name).' ('.$langs->trans("ForbiddenBySetupRules").')';
1855 dol_syslog(__METHOD__.' '.$this->error, LOG_ERR);
1856 return -1;
1857 }
1858 }
1859 }
1860 }
1861 }
1862
1863 $this->db->begin();
1864
1865 // Define new ref
1866 if ($force_number) {
1867 $num = $force_number;
1868 } elseif (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
1869 $num = $this->getNextNumRef($this->thirdparty);
1870 } else {
1871 $num = $this->ref;
1872 }
1873 $this->newref = dol_sanitizeFileName($num);
1874
1875 $sql = "UPDATE ".MAIN_DB_PREFIX."facture_fourn";
1876 $sql .= " SET ref='".$this->db->escape($num)."', fk_statut = 1, fk_user_valid = ".((int) $user->id).", date_valid = '".$this->db->idate($now)."'";
1877 $sql .= " WHERE rowid = ".((int) $this->id);
1878
1879 dol_syslog(get_class($this)."::validate", LOG_DEBUG);
1880 $resql = $this->db->query($sql);
1881 if ($resql) {
1882 // Si on incrémente le produit principal et ses composants à la validation de facture fournisseur
1883 if (!$error && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL')) {
1884 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
1885 $langs->load("agenda");
1886
1887 $cpt = count($this->lines);
1888 for ($i = 0; $i < $cpt; $i++) {
1889 if ($this->lines[$i]->fk_product > 0) {
1890 $mouvP = new MouvementStock($this->db);
1891 $mouvP->origin = &$this;
1892 $mouvP->setOrigin($this->element, $this->id);
1893 // We increase stock for product
1894 $up_ht_disc = $this->lines[$i]->subprice;
1895 if (!empty($this->lines[$i]->remise_percent) && !getDolGlobalString('STOCK_EXCLUDE_DISCOUNT_FOR_PMP')) {
1896 $up_ht_disc = price2num($up_ht_disc * (100 - $this->lines[$i]->remise_percent) / 100, 'MU');
1897 }
1899 $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $up_ht_disc, $langs->trans("InvoiceValidatedInDolibarr", $num));
1900 } else {
1901 $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $up_ht_disc, $langs->trans("InvoiceValidatedInDolibarr", $num));
1902 }
1903 if ($result < 0) {
1904 $this->error = $mouvP->error;
1905 if (count($mouvP->errors)) {
1906 $this->errors = $mouvP->errors;
1907 }
1908 return -2;
1909 }
1910 }
1911 }
1912 }
1913
1914 // Triggers call
1915 if (!$error && empty($notrigger)) {
1916 // Call trigger
1917 $result = $this->call_trigger('BILL_SUPPLIER_VALIDATE', $user);
1918 if ($result < 0) {
1919 $error++;
1920 }
1921 // End call triggers
1922 }
1923
1924 if (!$error) {
1925 $this->oldref = $this->ref;
1926
1927 // Rename directory if dir was a temporary ref
1928 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
1929 // Now we rename also files into index
1930 $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)."'";
1931 $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;
1932 $resql = $this->db->query($sql);
1933 if (!$resql) {
1934 $error++;
1935 $this->error = $this->db->lasterror();
1936 }
1937 $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)."'";
1938 $sql .= " WHERE filepath = 'fournisseur/facture/".get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier').$this->db->escape($this->ref)."' and entity = ".$conf->entity;
1939 $resql = $this->db->query($sql);
1940 if (!$resql) {
1941 $error++;
1942 $this->error = $this->db->lasterror();
1943 }
1944
1945 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
1946 $oldref = dol_sanitizeFileName($this->ref);
1947 $dirsource = $conf->fournisseur->facture->dir_output.'/'.get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier').$oldref;
1948 $dirdest = $conf->fournisseur->facture->dir_output.'/'.get_exdir($this->id, 2, 0, 0, $this, 'invoice_supplier').$this->newref;
1949 if (!$error && file_exists($dirsource)) {
1950 dol_syslog(get_class($this)."::validate rename dir ".$dirsource." into ".$dirdest);
1951
1952 if (@rename($dirsource, $dirdest)) {
1953 dol_syslog("Rename ok");
1954 // Rename docs starting with $oldref with $this->newref
1955 $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, '/'));
1956 foreach ($listoffiles as $fileentry) {
1957 $dirsource = $fileentry['name'];
1958 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $this->newref, $dirsource);
1959 $dirsource = $fileentry['path'].'/'.$dirsource;
1960 $dirdest = $fileentry['path'].'/'.$dirdest;
1961 @rename($dirsource, $dirdest);
1962 }
1963 }
1964 }
1965 }
1966 }
1967
1968 // Set new ref and define current status
1969 if (!$error) {
1970 $this->ref = $this->newref;
1971 $this->statut = self::STATUS_VALIDATED;
1973 //$this->date_validation=$now; this is stored into log table
1974 }
1975
1976 if (!$error) {
1977 $this->db->commit();
1978 return 1;
1979 } else {
1980 $this->db->rollback();
1981 return -1;
1982 }
1983 } else {
1984 $this->error = $this->db->error();
1985 $this->db->rollback();
1986 return -1;
1987 }
1988 }
1989
1998 public function setDraft($user, $idwarehouse = -1, $notrigger = 0)
1999 {
2000 // phpcs:enable
2001 global $conf, $langs;
2002
2003 $error = 0;
2004
2005 if ($this->status == self::STATUS_DRAFT) {
2006 dol_syslog(__METHOD__." already draft status", LOG_WARNING);
2007 return 0;
2008 }
2009
2010 dol_syslog(__METHOD__, LOG_DEBUG);
2011
2012 $this->db->begin();
2013
2014 $sql = "UPDATE ".MAIN_DB_PREFIX."facture_fourn";
2015 $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
2016 $sql .= " WHERE rowid = ".((int) $this->id);
2017
2018 $result = $this->db->query($sql);
2019 if ($result) {
2020 if (!$error) {
2021 $this->oldcopy = clone $this;
2022 }
2023
2024 // Si on incremente le produit principal et ses composants a la validation de facture fournisseur, on decremente
2025 if ($result >= 0 && isModEnabled('stock') && getDolGlobalString('STOCK_CALCULATE_ON_SUPPLIER_BILL')) {
2026 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2027 $langs->load("agenda");
2028
2029 $cpt = count($this->lines);
2030 for ($i = 0; $i < $cpt; $i++) {
2031 if ($this->lines[$i]->fk_product > 0) {
2032 $mouvP = new MouvementStock($this->db);
2033 $mouvP->origin = &$this;
2034 $mouvP->setOrigin($this->element, $this->id);
2035 // We increase stock for product
2037 $result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceBackToDraftInDolibarr", $this->ref));
2038 } else {
2039 $result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("InvoiceBackToDraftInDolibarr", $this->ref));
2040 }
2041 }
2042 }
2043 }
2044 // Triggers call
2045 if (!$error && empty($notrigger)) {
2046 // Call trigger
2047 $result = $this->call_trigger('BILL_SUPPLIER_UNVALIDATE', $user);
2048 if ($result < 0) {
2049 $error++;
2050 }
2051 // End call triggers
2052 }
2053 if ($error == 0) {
2054 $this->db->commit();
2055 return 1;
2056 } else {
2057 $this->db->rollback();
2058 return -1;
2059 }
2060 } else {
2061 $this->error = $this->db->error();
2062 $this->db->rollback();
2063 return -1;
2064 }
2065 }
2066
2067
2101 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)
2102 {
2103 global $langs, $mysoc;
2104
2105 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);
2106 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2107
2108 if ($this->status == self::STATUS_DRAFT) {
2109 // Clean parameters
2110 if (empty($remise_percent)) {
2111 $remise_percent = 0;
2112 }
2113 if (empty($qty)) {
2114 $qty = 0;
2115 }
2116 if (empty($info_bits)) {
2117 $info_bits = 0;
2118 }
2119 if (empty($rang)) {
2120 $rang = 0;
2121 }
2122 if (empty($fk_code_ventilation)) {
2123 $fk_code_ventilation = 0;
2124 }
2125 if (empty($txtva)) {
2126 $txtva = 0;
2127 }
2128 if (empty($txlocaltax1)) {
2129 $txlocaltax1 = 0;
2130 }
2131 if (empty($txlocaltax2)) {
2132 $txlocaltax2 = 0;
2133 }
2134
2135 $remise_percent = price2num($remise_percent);
2136 $qty = price2num($qty);
2137 $pu = price2num($pu);
2138 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
2139 $txtva = price2num($txtva); // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
2140 }
2141 $txlocaltax1 = price2num($txlocaltax1);
2142 $txlocaltax2 = price2num($txlocaltax2);
2143
2144 if ($date_start && $date_end && $date_start > $date_end) {
2145 $langs->load("errors");
2146 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
2147 return -1;
2148 }
2149
2150 $this->db->begin();
2151
2152 if ($fk_product > 0) {
2153 if (getDolGlobalInt('SUPPLIER_INVOICE_WITH_PREDEFINED_PRICES_ONLY') == 1) { // Not the common case
2154 // Check quantity is enough
2155 dol_syslog(get_class($this)."::addline we check supplier prices fk_product=".$fk_product." qty=".$qty." ref_supplier=".$ref_supplier);
2156 $prod = new ProductFournisseur($this->db);
2157 if ($prod->fetch($fk_product) > 0) {
2158 $product_type = $prod->type;
2159 $label = $prod->label;
2160 $fk_prod_fourn_price = 0;
2161
2162 // We use 'none' instead of $ref_supplier, because $ref_supplier may not exists anymore. So we will take the first supplier price ok.
2163 // If we want a dedicated supplier price, we must provide $fk_prod_fourn_price.
2164 $result = $prod->get_buyprice($fk_prod_fourn_price, $qty, $fk_product, 'none', ($this->fk_soc ? $this->fk_soc : $this->socid)); // Search on couple $fk_prod_fourn_price/$qty first, then on triplet $qty/$fk_product/$ref_supplier/$this->fk_soc
2165 if ($result > 0) {
2166 if (empty($pu)) {
2167 $pu = $prod->fourn_pu; // Unit price supplier price set by get_buyprice
2168 }
2169 $ref_supplier = $prod->ref_supplier; // Ref supplier price set by get_buyprice
2170 // is remise percent not keyed but present for the product we add it
2171 if ($remise_percent == 0 && $prod->remise_percent != 0) {
2172 $remise_percent = $prod->remise_percent;
2173 }
2174 }
2175 if ($result == 0) { // If result == 0, we failed to found the supplier reference price
2176 $langs->load("errors");
2177 $this->error = "Ref ".$prod->ref." ".$langs->trans("ErrorQtyTooLowForThisSupplier");
2178 $this->db->rollback();
2179 dol_syslog(get_class($this)."::addline we did not found supplier price, so we can't guess unit price");
2180 //$pu = $prod->fourn_pu; // We do not overwrite unit price
2181 //$ref = $prod->ref_fourn; // We do not overwrite ref supplier price
2182 return -1;
2183 }
2184 if ($result == -1) {
2185 $langs->load("errors");
2186 $this->error = "Ref ".$prod->ref." ".$langs->trans("ErrorQtyTooLowForThisSupplier");
2187 $this->db->rollback();
2188 dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_DEBUG);
2189 return -1;
2190 }
2191 if ($result < -1) {
2192 $this->error = $prod->error;
2193 $this->db->rollback();
2194 dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_ERR);
2195 return -1;
2196 }
2197 } else {
2198 $this->error = $prod->error;
2199 $this->db->rollback();
2200 return -1;
2201 }
2202 }
2203 } else {
2204 $product_type = $type;
2205 }
2206
2207 if (isModEnabled("multicurrency") && $pu_devise > 0) {
2208 $pu = 0;
2209 }
2210
2211 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
2212
2213 // Clean vat code
2214 $reg = array();
2215 $vat_src_code = '';
2216 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
2217 $vat_src_code = $reg[1];
2218 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
2219 }
2220
2221 // Calcul du total TTC et de la TVA pour la ligne a partir de
2222 // qty, pu, remise_percent et txtva
2223 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2224 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2225
2226 $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $this->thirdparty, $localtaxes_type, 100, $this->multicurrency_tx, $pu_devise);
2227 $total_ht = $tabprice[0];
2228 $total_tva = $tabprice[1];
2229 $total_ttc = $tabprice[2];
2230 $total_localtax1 = $tabprice[9];
2231 $total_localtax2 = $tabprice[10];
2232 $pu_ht = $tabprice[3];
2233
2234 // MultiCurrency
2235 $multicurrency_total_ht = $tabprice[16];
2236 $multicurrency_total_tva = $tabprice[17];
2237 $multicurrency_total_ttc = $tabprice[18];
2238 $pu_ht_devise = $tabprice[19];
2239
2240 // Check parameters
2241 if ($type < 0) {
2242 return -1;
2243 }
2244
2245 if ($rang < 0) {
2246 $rangmax = $this->line_max();
2247 $rang = $rangmax + 1;
2248 }
2249
2250 // Insert line
2251 $supplierinvoiceline = new SupplierInvoiceLine($this->db);
2252
2253 $supplierinvoiceline->context = $this->context;
2254
2255 $supplierinvoiceline->fk_facture_fourn = $this->id;
2256 //$supplierinvoiceline->label=$label; // deprecated
2257 $supplierinvoiceline->desc = $desc;
2258 $supplierinvoiceline->ref_supplier = $ref_supplier;
2259
2260 $supplierinvoiceline->qty = ($this->type == self::TYPE_CREDIT_NOTE ? abs((float) $qty) : (float) $qty); // For credit note, quantity is always positive and unit price negative
2261 $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
2262
2263 $supplierinvoiceline->vat_src_code = $vat_src_code;
2264 $supplierinvoiceline->tva_tx = $txtva;
2265 $supplierinvoiceline->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
2266 $supplierinvoiceline->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
2267 $supplierinvoiceline->localtax1_type = empty($localtaxes_type[0]) ? 0 : $localtaxes_type[0];
2268 $supplierinvoiceline->localtax2_type = empty($localtaxes_type[2]) ? 0 : $localtaxes_type[2];
2269
2270 $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
2271 $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
2272 $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
2273 $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
2274 $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
2275
2276 $supplierinvoiceline->fk_product = $fk_product;
2277 $supplierinvoiceline->product_type = $type;
2278 $supplierinvoiceline->remise_percent = $remise_percent;
2279 $supplierinvoiceline->date_start = $date_start;
2280 $supplierinvoiceline->date_end = $date_end;
2281 $supplierinvoiceline->fk_code_ventilation = $fk_code_ventilation;
2282 $supplierinvoiceline->rang = $rang;
2283 $supplierinvoiceline->info_bits = $info_bits;
2284 $supplierinvoiceline->fk_remise_except = $fk_remise_except;
2285
2286
2287 $supplierinvoiceline->special_code = (int) $special_code;
2288 $supplierinvoiceline->fk_parent_line = $fk_parent_line;
2289 $supplierinvoiceline->origin = $this->origin;
2290 $supplierinvoiceline->origin_id = $origin_id;
2291 $supplierinvoiceline->fk_unit = $fk_unit;
2292
2293 // Multicurrency
2294 $supplierinvoiceline->fk_multicurrency = $this->fk_multicurrency;
2295 $supplierinvoiceline->multicurrency_code = $this->multicurrency_code;
2296 $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
2297
2298 $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
2299 $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
2300 $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
2301
2302 if (is_array($array_options) && count($array_options) > 0) {
2303 $supplierinvoiceline->array_options = $array_options;
2304 }
2305
2306 $result = $supplierinvoiceline->insert($notrigger);
2307 if ($result > 0) {
2308 // Update denormalized fields at the order level
2309 $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.
2310
2311 if ($result > 0) {
2312 if (!isset($this->context['createfromclone'])) {
2313 if (!empty($fk_parent_line)) {
2314 // Always reorder if child line
2315 $this->line_order(true, 'DESC');
2316 } elseif ($rang > 0 && $rang <= count($this->lines)) {
2317 // Update all rank of all other lines starting from the same $ranktouse
2318 $linecount = count($this->lines);
2319 for ($ii = $rang; $ii <= $linecount; $ii++) {
2320 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
2321 }
2322 }
2323
2324 $this->lines[] = $supplierinvoiceline;
2325 }
2326
2327 $this->db->commit();
2328 return $supplierinvoiceline->id;
2329 } else {
2330 $this->error = $this->db->error();
2331 $this->db->rollback();
2332 return -1;
2333 }
2334 } else {
2335 $this->error = $supplierinvoiceline->error;
2336 $this->errors = $supplierinvoiceline->errors;
2337 $this->db->rollback();
2338 return -2;
2339 }
2340 } else {
2341 return 0;
2342 }
2343 }
2344
2370 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)
2371 {
2372 global $mysoc, $langs;
2373
2374 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);
2375 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2376
2377 $pu = price2num($pu);
2378 $qty = price2num($qty);
2379 $remise_percent = (float) price2num($remise_percent);
2380 $pu_devise = price2num($pu_devise);
2381
2382 // Check parameters
2383 //if (! is_numeric($pu) || ! is_numeric($qty)) return -1;
2384 if ($type < 0) {
2385 return -1;
2386 }
2387
2388 if ($date_start && $date_end && $date_start > $date_end) {
2389 $langs->load("errors");
2390 $this->error = $langs->trans('ErrorStartDateGreaterEnd');
2391 return -1;
2392 }
2393
2394 // Clean parameters
2395 if (empty($vatrate)) {
2396 $vatrate = 0;
2397 }
2398 if (empty($txlocaltax1)) {
2399 $txlocaltax1 = 0;
2400 }
2401 if (empty($txlocaltax2)) {
2402 $txlocaltax2 = 0;
2403 }
2404
2405 $txlocaltax1 = (float) price2num($txlocaltax1);
2406 $txlocaltax2 = (float) price2num($txlocaltax2);
2407
2408 // Calcul du total TTC et de la TVA pour la ligne a partir de
2409 // qty, pu, remise_percent et txtva
2410 // TRES IMPORTANT: C'est au moment de l'insertion ligne qu'on doit stocker
2411 // la part ht, tva et ttc, et ce au niveau de la ligne qui a son propre taux tva.
2412
2413 $localtaxes_type = getLocalTaxesFromRate($vatrate, 0, $mysoc, $this->thirdparty);
2414
2415 $reg = array();
2416
2417 // Clean vat code
2418 $vat_src_code = '';
2419 if (preg_match('/\‍((.*)\‍)/', (string) $vatrate, $reg)) {
2420 $vat_src_code = $reg[1];
2421 $vatrate = preg_replace('/\s*\‍(.*\‍)/', '', (string) $vatrate); // Remove code into vatrate.
2422 }
2423
2424 $tabprice = calcul_price_total($qty, $pu, $remise_percent, $vatrate, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $this->thirdparty, $localtaxes_type, 100, $this->multicurrency_tx, $pu_devise);
2425 $total_ht = $tabprice[0];
2426 $total_tva = $tabprice[1];
2427 $total_ttc = $tabprice[2];
2428 $pu_ht = $tabprice[3];
2429 $pu_tva = $tabprice[4];
2430 $pu_ttc = $tabprice[5];
2431 $total_localtax1 = $tabprice[9];
2432 $total_localtax2 = $tabprice[10];
2433
2434 // MultiCurrency
2435 $multicurrency_total_ht = $tabprice[16];
2436 $multicurrency_total_tva = $tabprice[17];
2437 $multicurrency_total_ttc = $tabprice[18];
2438 $pu_ht_devise = $tabprice[19];
2439
2440 if (empty($info_bits)) {
2441 $info_bits = 0;
2442 }
2443
2444 // Fetch current line from the database and then clone the object and set it in $oldline property
2445 $line = new SupplierInvoiceLine($this->db);
2446 $line->fetch($id);
2447 $line->fetch_optionals();
2448
2449 $staticline = clone $line;
2450
2451 if ($idproduct) {
2452 $product = new Product($this->db);
2453 $result = $product->fetch($idproduct);
2454 $product_type = $product->type;
2455 } else {
2456 $idproduct = $staticline->fk_product;
2457 $product_type = $type;
2458 }
2459
2460 $line->oldline = $staticline;
2461 $line->context = $this->context;
2462
2463 $line->description = $desc;
2464 $line->desc = $desc;
2465
2466 $line->qty = ($this->type == self::TYPE_CREDIT_NOTE ? abs((float) $qty) : (float) $qty); // For credit note, quantity is always positive and unit price negative
2467 $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
2468 $line->pu_ht = $line->subprice; // deprecated
2469 $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
2470
2471 $line->remise_percent = $remise_percent;
2472 $line->ref_supplier = $ref_supplier;
2473
2474 $line->date_start = $date_start;
2475 $line->date_end = $date_end;
2476
2477 $line->vat_src_code = $vat_src_code;
2478 $line->tva_tx = $vatrate;
2479 $line->localtax1_tx = $txlocaltax1;
2480 $line->localtax2_tx = $txlocaltax2;
2481 $line->localtax1_type = empty($localtaxes_type[0]) ? 0 : $localtaxes_type[0];
2482 $line->localtax2_type = empty($localtaxes_type[2]) ? 0 : $localtaxes_type[2];
2483
2484 $line->total_ht = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $total_ht) : (float) $total_ht);
2485 $line->total_tva = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $total_tva) : (float) $total_tva);
2486 $line->total_localtax1 = (float) $total_localtax1;
2487 $line->total_localtax2 = (float) $total_localtax2;
2488 $line->total_ttc = (($this->type == self::TYPE_CREDIT_NOTE || $qty < 0) ? -abs((float) $total_ttc) : (float) $total_ttc);
2489
2490 $line->fk_product = $idproduct;
2491 $line->product_type = $product_type;
2492 $line->info_bits = $info_bits;
2493 $line->fk_unit = $fk_unit;
2494 $line->rang = $rang;
2495
2496 if (is_array($array_options) && count($array_options) > 0) {
2497 // We replace values in this->line->array_options only for entries defined into $array_options
2498 foreach ($array_options as $key => $value) {
2499 $line->array_options[$key] = $array_options[$key];
2500 }
2501 }
2502
2503 // Multicurrency
2504 $line->multicurrency_subprice = (float) $pu_ht_devise;
2505 $line->multicurrency_total_ht = (float) $multicurrency_total_ht;
2506 $line->multicurrency_total_tva = (float) $multicurrency_total_tva;
2507 $line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
2508
2509 $res = $line->update($notrigger);
2510
2511 if ($res < 1) {
2512 $this->errors[] = $line->error;
2513 $this->errors = array_merge($this->errors, $line->errors);
2514 } else {
2515 // Update total price into invoice record
2516 $res = $this->update_price('1', 'auto', 0, $this->thirdparty);
2517 }
2518
2519 return $res;
2520 }
2521
2529 public function deleteLine($rowid, $notrigger = 0)
2530 {
2531 if (!$rowid) {
2532 $rowid = $this->id;
2533 }
2534
2535 $this->db->begin();
2536
2537 // Free the discount linked to a line of invoice
2538 $sql = 'UPDATE '.MAIN_DB_PREFIX.'societe_remise_except';
2539 $sql .= ' SET fk_invoice_supplier_line = NULL';
2540 $sql .= ' WHERE fk_invoice_supplier_line = '.((int) $rowid);
2541
2542 dol_syslog(get_class($this)."::deleteline", LOG_DEBUG);
2543 $result = $this->db->query($sql);
2544 if (!$result) {
2545 $this->error = $this->db->error();
2546 $this->db->rollback();
2547 return -2;
2548 }
2549
2550 $line = new SupplierInvoiceLine($this->db);
2551
2552 if ($line->fetch($rowid) < 1) {
2553 return -1;
2554 }
2555
2556 $res = $line->delete($notrigger);
2557
2558 if ($res < 1) {
2559 $this->errors[] = $line->error;
2560 $this->db->rollback();
2561 return -3;
2562 } else {
2563 $res = $this->update_price(1);
2564
2565 if ($res > 0) {
2566 $this->db->commit();
2567 return 1;
2568 } else {
2569 $this->db->rollback();
2570 $this->error = $this->db->lasterror();
2571 return -4;
2572 }
2573 }
2574 }
2575
2576
2583 public function info($id)
2584 {
2585 $sql = 'SELECT c.rowid, datec, tms as datem, ';
2586 $sql .= ' fk_user_author, fk_user_modif, fk_user_valid';
2587 $sql .= ' FROM '.MAIN_DB_PREFIX.'facture_fourn as c';
2588 $sql .= ' WHERE c.rowid = '.((int) $id);
2589
2590 $result = $this->db->query($sql);
2591 if ($result) {
2592 if ($this->db->num_rows($result)) {
2593 $obj = $this->db->fetch_object($result);
2594
2595 $this->id = $obj->rowid;
2596
2597 $this->user_creation_id = $obj->fk_user_author;
2598 $this->user_validation_id = $obj->fk_user_valid;
2599 $this->user_modification_id = $obj->fk_user_modif;
2600 $this->date_creation = $this->db->jdate($obj->datec);
2601 $this->date_modification = $this->db->jdate($obj->datem);
2602 //$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)
2603 }
2604 $this->db->free($result);
2605 } else {
2606 dol_print_error($this->db);
2607 }
2608 }
2609
2610 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2619 public function list_replacable_supplier_invoices($socid = 0)
2620 {
2621 // phpcs:enable
2622 global $conf;
2623
2624 $return = array();
2625
2626 $sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut,";
2627 $sql .= " ff.rowid as rowidnext";
2628 $sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn as f";
2629 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."paiementfourn_facturefourn as pf ON f.rowid = pf.fk_facturefourn";
2630 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."facture_fourn as ff ON f.rowid = ff.fk_facture_source";
2631 $sql .= " WHERE (f.fk_statut = ".self::STATUS_VALIDATED." OR (f.fk_statut = ".self::STATUS_ABANDONED." AND f.close_code = '".self::CLOSECODE_ABANDONED."'))";
2632 $sql .= " AND f.entity = ".$conf->entity;
2633 $sql .= " AND f.paye = 0"; // Pas classee payee completement
2634 $sql .= " AND pf.fk_paiementfourn IS NULL"; // Aucun paiement deja fait
2635 $sql .= " AND ff.fk_statut IS NULL"; // Renvoi vrai si pas facture de replacement
2636 if ($socid > 0) {
2637 $sql .= " AND f.fk_soc = ".((int) $socid);
2638 }
2639 $sql .= " ORDER BY f.ref";
2640
2641 dol_syslog(get_class($this)."::list_replacable_supplier_invoices", LOG_DEBUG);
2642 $resql = $this->db->query($sql);
2643 if ($resql) {
2644 while ($obj = $this->db->fetch_object($resql)) {
2645 $return[$obj->rowid] = array(
2646 'id' => $obj->rowid,
2647 'ref' => $obj->ref,
2648 'status' => $obj->fk_statut
2649 );
2650 }
2651 //print_r($return);
2652 return $return;
2653 } else {
2654 $this->error = $this->db->error();
2655 return -1;
2656 }
2657 }
2658
2659 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2669 public function list_qualified_avoir_supplier_invoices($socid = 0)
2670 {
2671 // phpcs:enable
2672 global $conf;
2673
2674 $return = array();
2675
2676 $sql = "SELECT f.rowid as rowid, f.ref, f.fk_statut, f.type, f.subtype, f.paye, pf.fk_paiementfourn";
2677 $sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn as f";
2678 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."paiementfourn_facturefourn as pf ON f.rowid = pf.fk_facturefourn";
2679 $sql .= " WHERE f.entity = ".$conf->entity;
2680 $sql .= " AND f.fk_statut in (".self::STATUS_VALIDATED.",".self::STATUS_CLOSED.")";
2681 $sql .= " AND NOT EXISTS (SELECT rowid from ".MAIN_DB_PREFIX."facture_fourn as ff WHERE f.rowid = ff.fk_facture_source";
2682 $sql .= " AND ff.type=".self::TYPE_REPLACEMENT.")";
2683 $sql .= " AND f.type != ".self::TYPE_CREDIT_NOTE; // Type non 2 si facture non avoir
2684 if ($socid > 0) {
2685 $sql .= " AND f.fk_soc = ".((int) $socid);
2686 }
2687 $sql .= " ORDER BY f.ref";
2688
2689 dol_syslog(get_class($this)."::list_qualified_avoir_supplier_invoices", LOG_DEBUG);
2690 $resql = $this->db->query($sql);
2691 if ($resql) {
2692 while ($obj = $this->db->fetch_object($resql)) {
2693 $qualified = 0;
2694 if ($obj->fk_statut == self::STATUS_VALIDATED) {
2695 $qualified = 1;
2696 }
2697 if ($obj->fk_statut == self::STATUS_CLOSED) {
2698 $qualified = 1;
2699 }
2700 if ($qualified) {
2701 $paymentornot = ($obj->fk_paiementfourn ? 1 : 0);
2702 $return[$obj->rowid] = array('ref' => (string) $obj->ref, 'status' => (int) $obj->fk_statut, 'type' => (int) $obj->type, 'paye' => (int) $obj->paye, 'paymentornot' => (int) $paymentornot);
2703 }
2704 }
2705
2706 return $return;
2707 } else {
2708 $this->error = $this->db->error();
2709 return -1;
2710 }
2711 }
2712
2713 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2720 public function load_board($user)
2721 {
2722 // phpcs:enable
2723 global $conf, $langs;
2724
2725 $sql = 'SELECT ff.rowid, ff.date_lim_reglement as datefin, ff.fk_statut as status, ff.total_ht, ff.total_ttc';
2726 $sql .= ' FROM '.MAIN_DB_PREFIX.'facture_fourn as ff';
2727 if (empty($user->socid) && !$user->hasRight("societe", "client", "voir")) {
2728 $sql .= " JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON ff.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
2729 }
2730 $sql .= ' WHERE ff.paye = 0';
2731 $sql .= " AND ff.fk_statut IN (".self::STATUS_VALIDATED.")";
2732 $sql .= " AND ff.entity = ".$conf->entity;
2733 if ($user->socid) {
2734 $sql .= ' AND ff.fk_soc = '.((int) $user->socid);
2735 }
2736
2737 $resql = $this->db->query($sql);
2738 if ($resql) {
2739 $langs->load("bills");
2740 $now = dol_now();
2741
2742 $response = new WorkboardResponse();
2743 $response->warning_delay = $conf->facture->fournisseur->warning_delay / 60 / 60 / 24;
2744 $response->label = $langs->trans("SupplierBillsToPay");
2745 $response->labelShort = $langs->trans("StatusToPay");
2746
2747 $response->url = DOL_URL_ROOT.'/fourn/facture/list.php?search_status=1&mainmenu=billing&leftmenu=suppliers_bills';
2748 $response->img = img_object($langs->trans("Bills"), "bill");
2749
2750 $facturestatic = new FactureFournisseur($this->db);
2751
2752 while ($obj = $this->db->fetch_object($resql)) {
2753 $facturestatic->date_echeance = $this->db->jdate($obj->datefin);
2754 $facturestatic->statut = $obj->status; // For backward compatibility
2755 $facturestatic->status = $obj->status;
2756
2757 $response->nbtodo++;
2758 $response->total += $obj->total_ht;
2759
2760 if ($facturestatic->hasDelay()) {
2761 $response->nbtodolate++;
2762 $response->url_late = DOL_URL_ROOT.'/fourn/facture/list.php?search_option=late&mainmenu=billing&leftmenu=suppliers_bills';
2763 }
2764 }
2765
2766 $this->db->free($resql);
2767 return $response;
2768 } else {
2769 dol_print_error($this->db);
2770 $this->error = $this->db->error();
2771 return -1;
2772 }
2773 }
2774
2782 public function getTooltipContentArray($params)
2783 {
2784 global $conf, $langs, $mysoc;
2785
2786 $langs->load('bills');
2787
2788 $datas = [];
2789 $moretitle = $params['moretitle'] ?? '';
2790
2791 $picto = $this->picto;
2792 if ($this->type == self::TYPE_REPLACEMENT) {
2793 $picto .= 'r'; // Replacement invoice
2794 }
2795 if ($this->type == self::TYPE_CREDIT_NOTE) {
2796 $picto .= 'a'; // Credit note
2797 }
2798 if ($this->type == self::TYPE_DEPOSIT) {
2799 $picto .= 'd'; // Deposit invoice
2800 }
2801
2802 $datas['picto'] = img_picto('', $picto).' <u class="paddingrightonly">'.$langs->trans("SupplierInvoice").'</u>';
2803 if ($this->type == self::TYPE_REPLACEMENT) {
2804 $datas['picto'] .= '<u class="paddingrightonly">'.$langs->transnoentitiesnoconv("InvoiceReplace").'</u>';
2805 } elseif ($this->type == self::TYPE_CREDIT_NOTE) {
2806 $datas['picto'] .= '<u class="paddingrightonly">'.$langs->transnoentitiesnoconv("CreditNote").'</u>';
2807 } elseif ($this->type == self::TYPE_DEPOSIT) {
2808 $datas['picto'] .= '<u class="paddingrightonly">'.$langs->transnoentitiesnoconv("Deposit").'</u>';
2809 }
2810 if (isset($this->status)) {
2811 $alreadypaid = -1;
2812 if (isset($this->totalpaid)) {
2813 $alreadypaid = $this->totalpaid;
2814 }
2815
2816 $datas['picto'] .= ' '.$this->getLibStatut(5, $alreadypaid);
2817 }
2818 if ($moretitle) {
2819 $datas['picto'] .= ' - '.$moretitle;
2820 }
2821 if (!empty($this->ref)) {
2822 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
2823 }
2824 if (!empty($this->ref_supplier)) {
2825 $datas['refsupplier'] = '<br><b>'.$langs->trans('RefSupplier').':</b> '.$this->ref_supplier;
2826 }
2827 if (!empty($this->label)) {
2828 $datas['label'] = '<br><b>'.$langs->trans('Label').':</b> '.$this->label;
2829 }
2830 if (!empty($this->date)) {
2831 $datas['date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
2832 }
2833 if (!empty($this->date_echeance)) {
2834 $datas['date_echeance'] = '<br><b>'.$langs->trans('DateDue').':</b> '.dol_print_date($this->date_echeance, 'day');
2835 }
2836 if (!empty($this->total_ht)) {
2837 $datas['amountht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
2838 }
2839 if (!empty($this->total_tva)) {
2840 $datas['totaltva'] = '<br><b>'.$langs->trans('AmountVAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
2841 }
2842 if (!empty($this->total_localtax1) && $this->total_localtax1 != 0) {
2843 // We keep test != 0 because $this->total_localtax1 can be '0.00000000'
2844 $datas['amountlt1'] = '<br><b>'.$langs->transcountry('AmountLT1', $mysoc->country_code).':</b> '.price($this->total_localtax1, 0, $langs, 0, -1, -1, $conf->currency);
2845 }
2846 if (!empty($this->total_localtax2) && $this->total_localtax2 != 0) {
2847 $datas['amountlt2'] = '<br><b>'.$langs->transcountry('AmountLT2', $mysoc->country_code).':</b> '.price($this->total_localtax2, 0, $langs, 0, -1, -1, $conf->currency);
2848 }
2849 if (!empty($this->revenuestamp)) {
2850 $datas['amountrevenustamp'] = '<br><b>'.$langs->trans('RevenueStamp').':</b> '.price($this->revenuestamp, 0, $langs, 0, -1, -1, $conf->currency);
2851 }
2852 if (!empty($this->total_ttc)) {
2853 $datas['totalttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
2854 }
2855 return $datas;
2856 }
2857
2871 public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $moretitle = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = 0)
2872 {
2873 global $langs, $conf, $user, $hookmanager;
2874
2875 $result = '';
2876
2877 if ($option == 'withdraw') {
2878 $url = DOL_URL_ROOT.'/compta/facture/prelevement.php?facid='.$this->id.'&type=bank-transfer';
2879 } elseif ($option == 'document') {
2880 $url = DOL_URL_ROOT.'/fourn/facture/document.php?facid='.$this->id;
2881 } else {
2882 $url = DOL_URL_ROOT.'/fourn/facture/card.php?facid='.$this->id;
2883 }
2884
2885 if ($short) {
2886 return $url;
2887 }
2888
2889 if ($option !== 'nolink') {
2890 // Add param to save lastsearch_values or not
2891 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
2892 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
2893 $add_save_lastsearch_values = 1;
2894 }
2895 if ($add_save_lastsearch_values) {
2896 $url .= '&save_lastsearch_values=1';
2897 }
2898 }
2899
2900 $picto = $this->picto;
2901 if ($this->type == self::TYPE_REPLACEMENT) {
2902 $picto .= 'r'; // Replacement invoice
2903 }
2904 if ($this->type == self::TYPE_CREDIT_NOTE) {
2905 $picto .= 'a'; // Credit note
2906 }
2907 if ($this->type == self::TYPE_DEPOSIT) {
2908 $picto .= 'd'; // Deposit invoice
2909 }
2910
2911 $params = [
2912 'id' => $this->id,
2913 'objecttype' => $this->element,
2914 'option' => $option,
2915 'moretitle' => $moretitle,
2916 ];
2917 $classfortooltip = 'classfortooltip';
2918 $dataparams = '';
2919 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
2920 $classfortooltip = 'classforajaxtooltip';
2921 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
2922 $label = '';
2923 } else {
2924 $label = implode($this->getTooltipContentArray($params));
2925 }
2926
2927 $ref = $this->ref;
2928 if (empty($ref)) {
2929 $ref = $this->id;
2930 }
2931
2932 $linkclose = '';
2933 if (empty($notooltip)) {
2934 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2935 $label = $langs->trans("ShowSupplierInvoice");
2936 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
2937 }
2938 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
2939 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
2940 }
2941
2942 $linkstart = '<a href="'.$url.'"';
2943 $linkstart .= $linkclose.'>';
2944 $linkend = '</a>';
2945
2946 $result .= $linkstart;
2947 if ($withpicto) {
2948 $result .= img_object(($notooltip ? '' : $label), ($picto ? $picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'"'), 0, 0, $notooltip ? 0 : 1);
2949 }
2950 if ($withpicto != 2) {
2951 $result .= ($max ? dol_trunc($ref, $max) : $ref);
2952 }
2953 $result .= $linkend;
2954
2955 if ($addlinktonotes) {
2956 $txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
2957 if ($txttoshow) {
2958 $notetoshow = $langs->trans("ViewPrivateNote").':<br>'.dol_string_nohtmltag($txttoshow, 1);
2959 $result .= ' <span class="note inline-block">';
2960 $result .= '<a href="'.DOL_URL_ROOT.'/fourn/facture/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($notetoshow).'">';
2961 $result .= img_picto('', 'note');
2962 $result .= '</a>';
2963 $result .= '</span>';
2964 }
2965 }
2966 global $action;
2967 $hookmanager->initHooks(array($this->element . 'dao'));
2968 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
2969 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2970 if ($reshook > 0) {
2971 $result = $hookmanager->resPrint;
2972 } else {
2973 $result .= $hookmanager->resPrint;
2974 }
2975 return $result;
2976 }
2977
2986 public function getNextNumRef($soc, $mode = 'next')
2987 {
2988 global $db, $langs, $conf;
2989 $langs->load("orders");
2990
2991 // Clean parameters (if not defined or using deprecated value)
2992 if (!getDolGlobalString('INVOICE_SUPPLIER_ADDON_NUMBER')) {
2993 $conf->global->INVOICE_SUPPLIER_ADDON_NUMBER = 'mod_facture_fournisseur_cactus';
2994 }
2995
2996 $mybool = false;
2997
2998 $file = getDolGlobalString('INVOICE_SUPPLIER_ADDON_NUMBER') . ".php";
2999 $classname = getDolGlobalString('INVOICE_SUPPLIER_ADDON_NUMBER');
3000
3001 // Include file with class
3002 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
3003
3004 foreach ($dirmodels as $reldir) {
3005 $dir = dol_buildpath($reldir."core/modules/supplier_invoice/");
3006
3007 // Load file with numbering class (if found)
3008 $mybool = ((bool) @include_once $dir.$file) || $mybool;
3009 }
3010
3011 if (!$mybool) {
3012 dol_print_error(null, "Failed to include file ".$file);
3013 return '';
3014 }
3015
3016 $obj = new $classname();
3017 '@phan-var-force ModeleNumRefSuppliersInvoices $obj';
3018 $numref = "";
3019 $numref = $obj->getNextValue($soc, $this, $mode);
3020
3021 if ($numref != "") {
3022 return $numref;
3023 } else {
3024 $this->error = $obj->error;
3025 return -1;
3026 }
3027 }
3028
3029
3038 public function initAsSpecimen($option = '')
3039 {
3040 global $langs, $conf;
3041 include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
3042
3043 $now = dol_now();
3044
3045 // Load array of products prodids
3046 $num_prods = 0;
3047 $prodids = array();
3048
3049 $sql = "SELECT rowid";
3050 $sql .= " FROM ".MAIN_DB_PREFIX."product";
3051 $sql .= " WHERE entity IN (".getEntity('product').")";
3052 $sql .= $this->db->plimit(100);
3053
3054 $resql = $this->db->query($sql);
3055 if ($resql) {
3056 $num_prods = $this->db->num_rows($resql);
3057 $i = 0;
3058 while ($i < $num_prods) {
3059 $i++;
3060 $row = $this->db->fetch_row($resql);
3061 $prodids[$i] = $row[0];
3062 }
3063 }
3064
3065 // Initialise parameters
3066 $this->id = 0;
3067 $this->ref = 'SPECIMEN';
3068 $this->ref_supplier = 'SUPPLIER_REF_SPECIMEN';
3069 $this->specimen = 1;
3070 $this->socid = 1;
3071 $this->date = $now;
3072 $this->date_lim_reglement = $this->date + 3600 * 24 * 30;
3073 $this->cond_reglement_code = 'RECEP';
3074 $this->mode_reglement_code = 'CHQ';
3075
3076 $this->note_public = 'This is a comment (public)';
3077 $this->note_private = 'This is a comment (private)';
3078
3079 $this->multicurrency_tx = 1;
3080 $this->multicurrency_code = $conf->currency;
3081
3082 $xnbp = 0;
3083 if (empty($option) || $option != 'nolines') {
3084 // Lines
3085 $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)
3086 while ($xnbp < $nbp) {
3087 $line = new SupplierInvoiceLine($this->db);
3088 $line->desc = $langs->trans("Description")." ".$xnbp;
3089 $line->qty = 1;
3090 $line->subprice = 100;
3091 $line->pu_ht = $line->subprice; // the canelle template use pu_ht and not subprice
3092 $line->price = 100;
3093 $line->tva_tx = 19.6;
3094 $line->localtax1_tx = 0;
3095 $line->localtax2_tx = 0;
3096 if ($xnbp == 2) {
3097 $line->total_ht = 50;
3098 $line->total_ttc = 59.8;
3099 $line->total_tva = 9.8;
3100 $line->remise_percent = 50;
3101 } else {
3102 $line->total_ht = 100;
3103 $line->total_ttc = 119.6;
3104 $line->total_tva = 19.6;
3105 $line->remise_percent = 0;
3106 }
3107
3108 if ($num_prods > 0) {
3109 $prodid = mt_rand(1, $num_prods);
3110 $line->fk_product = $prodids[$prodid];
3111 }
3112 $line->product_type = 0;
3113
3114 $this->lines[$xnbp] = $line;
3115
3116 $this->total_ht += $line->total_ht;
3117 $this->total_tva += $line->total_tva;
3118 $this->total_ttc += $line->total_ttc;
3119
3120 $xnbp++;
3121 }
3122 }
3123
3124 $this->total_ht = $xnbp * 100;
3125 $this->total_tva = $xnbp * 19.6;
3126 $this->total_ttc = $xnbp * 119.6;
3127
3128 return 1;
3129 }
3130
3136 public function loadStateBoard()
3137 {
3138 global $conf, $user;
3139
3140 $this->nb = array();
3141
3142 $clause = "WHERE";
3143
3144 $sql = "SELECT count(f.rowid) as nb";
3145 $sql .= " FROM ".MAIN_DB_PREFIX."facture_fourn as f";
3146 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON f.fk_soc = s.rowid";
3147 if (empty($user->socid) && !$user->hasRight("societe", "client", "voir")) {
3148 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3149 $sql .= " WHERE sc.fk_user = ".((int) $user->id);
3150 $clause = "AND";
3151 }
3152 $sql .= " ".$clause." f.entity = ".((int) $conf->entity);
3153
3154 $resql = $this->db->query($sql);
3155 if ($resql) {
3156 while ($obj = $this->db->fetch_object($resql)) {
3157 $this->nb["supplier_invoices"] = $obj->nb;
3158 }
3159 $this->db->free($resql);
3160 return 1;
3161 } else {
3162 dol_print_error($this->db);
3163 $this->error = $this->db->error();
3164 return -1;
3165 }
3166 }
3167
3176 public function createFromClone(User $user, $fromid, $invertdetail = 0)
3177 {
3178 global $conf, $langs;
3179
3180 $error = 0;
3181
3182 $object = new FactureFournisseur($this->db);
3183
3184 $this->db->begin();
3185
3186 // Load source object
3187 $object->fetch($fromid);
3188 $object->id = 0;
3189 $object->statut = self::STATUS_DRAFT; // For backward compatibility
3190 $object->status = self::STATUS_DRAFT;
3191
3192 $object->fetch_thirdparty(); // We need it to recalculate VAT localtaxes according to main sale taxes and vendor
3193
3194 // Clear fields
3195 $object->ref_supplier = (empty($this->ref_supplier) ? $langs->trans("CopyOf").' '.$object->ref_supplier : $this->ref_supplier);
3196 $object->author = $user->id; // FIXME? user_validation_id is replacement for author
3197 $object->user_validation_id = 0; // FIXME? user_validation_id is replacement for author
3198 $object->fk_facture_source = 0;
3199 $object->date_creation = '';
3200 $object->date_validation = '';
3201 $object->date = (empty($this->date) ? dol_now() : $this->date);
3202 $object->ref_client = '';
3203 $object->close_code = '';
3204 $object->close_note = '';
3205 if (getDolGlobalInt('MAIN_DONT_KEEP_NOTE_ON_CLONING') == 1) {
3206 $object->note_private = '';
3207 $object->note_public = '';
3208 }
3209
3210 $object->date_echeance = $object->calculate_date_lim_reglement();
3211
3212 // Loop on each line of new invoice
3213 foreach ($object->lines as $i => $line) {
3214 if (isset($object->lines[$i]->info_bits) && ($object->lines[$i]->info_bits & 0x02) == 0x02) { // We do not clone line of discounts
3215 unset($object->lines[$i]);
3216 }
3217 }
3218
3219 // Create clone
3220 $object->context['createfromclone'] = 'createfromclone';
3221 $result = $object->create($user);
3222
3223 // Other options
3224 if ($result < 0) {
3225 $this->error = $object->error;
3226 $this->errors = $object->errors;
3227 $error++;
3228 }
3229
3230 if (!$error) {
3231 }
3232
3233 unset($object->context['createfromclone']);
3234
3235 // End
3236 if (!$error) {
3237 $this->db->commit();
3238 return $object->id;
3239 } else {
3240 $this->db->rollback();
3241 return -1;
3242 }
3243 }
3244
3256 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3257 {
3258 global $langs;
3259
3260 $langs->load("suppliers");
3261 $outputlangs->load("products");
3262
3263 // Set the model on the model name to use
3264 if (empty($modele)) {
3265 if (getDolGlobalString('INVOICE_SUPPLIER_ADDON_PDF')) {
3266 $modele = getDolGlobalString('INVOICE_SUPPLIER_ADDON_PDF');
3267 } else {
3268 $modele = ''; // No default value. For supplier invoice, we allow to disable all PDF generation
3269 }
3270 } elseif ($modele == 'auto') {
3271 $modele = 'canelle';
3272 }
3273
3274 if (empty($modele)) {
3275 return 0;
3276 } else {
3277 $modelpath = "core/modules/supplier_invoice/doc/";
3278
3279 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3280 }
3281 }
3282
3287 public function getRights()
3288 {
3289 global $user;
3290
3291 return $user->hasRight("fournisseur", "facture");
3292 }
3293
3302 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
3303 {
3304 $tables = array(
3305 'facture_fourn'
3306 );
3307
3308 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
3309 }
3310
3319 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
3320 {
3321 $tables = array(
3322 'facture_fourn_det'
3323 );
3324
3325 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
3326 }
3327
3333 public function hasDelay()
3334 {
3335 global $conf;
3336
3337 $now = dol_now();
3338
3339 if (!$this->date_echeance) {
3340 return false;
3341 }
3342
3343 $status = isset($this->status) ? $this->status : $this->statut;
3344
3345 return ($status == self::STATUS_VALIDATED) && ($this->date_echeance < ($now - $conf->facture->fournisseur->warning_delay));
3346 }
3347
3353 public function isCreditNoteUsed()
3354 {
3355 $isUsed = false;
3356
3357 $sql = "SELECT fk_invoice_supplier FROM ".MAIN_DB_PREFIX."societe_remise_except WHERE fk_invoice_supplier_source = ".((int) $this->id);
3358 $resql = $this->db->query($sql);
3359 if (!empty($resql)) {
3360 $obj = $this->db->fetch_object($resql);
3361 if (!empty($obj->fk_invoice_supplier)) {
3362 $isUsed = true;
3363 }
3364 }
3365
3366 return $isUsed;
3367 }
3375 public function getKanbanView($option = '', $arraydata = null)
3376 {
3377 global $langs;
3378
3379 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
3380
3381 $picto = $this->picto;
3382 if ($this->type == self::TYPE_REPLACEMENT) {
3383 $picto .= 'r'; // Replacement invoice
3384 }
3385 if ($this->type == self::TYPE_CREDIT_NOTE) {
3386 $picto .= 'a'; // Credit note
3387 }
3388 if ($this->type == self::TYPE_DEPOSIT) {
3389 $picto .= 'd'; // Deposit invoice
3390 }
3391
3392 $return = '<div class="box-flex-item box-flex-grow-zero">';
3393 $return .= '<div class="info-box info-box-sm">';
3394 $return .= '<span class="info-box-icon bg-infobox-action">';
3395 $return .= img_picto('', $picto);
3396 $return .= '</span>';
3397 $return .= '<div class="info-box-content">';
3398 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl(1) : $this->ref).'</span>';
3399 if ($selected >= 0) {
3400 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
3401 }
3402 if (!empty($arraydata['thirdparty'])) {
3403 $return .= '<br><span class="info-box-label">'.$arraydata['thirdparty'].'</span>';
3404 }
3405 if (property_exists($this, 'date')) {
3406 $return .= '<br><span class="info-box-label">'.dol_print_date($this->date, 'day').'</span>';
3407 }
3408 if (property_exists($this, 'total_ht')) {
3409 $return .= ' &nbsp; <span class="info-box-label amount" title="'.dol_escape_htmltag($langs->trans("AmountHT")).'">'.price($this->total_ht);
3410 $return .= ' '.$langs->trans("HT");
3411 $return .= '</span>';
3412 }
3413 if (method_exists($this, 'getLibStatut')) {
3414 $alreadypaid = (empty($arraydata['alreadypaid']) ? 0 : $arraydata['alreadypaid']);
3415 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3, $alreadypaid).'</div>';
3416 }
3417 $return .= '</div>';
3418 $return .= '</div>';
3419 $return .= '</div>';
3420 return $return;
3421 }
3422
3429 public function setVATReverseCharge($vatreversecharge)
3430 {
3431 if (!$this->table_element) {
3432 dol_syslog(get_class($this)."::setVATReverseCharge was called on object with property table_element not defined", LOG_ERR);
3433 return -1;
3434 }
3435
3436 dol_syslog(get_class($this).'::setVATReverseCharge('.$vatreversecharge.')');
3437
3438 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
3439 $sql .= " SET vat_reverse_charge = ".((int) $vatreversecharge);
3440 $sql .= " WHERE rowid=".((int) $this->id);
3441
3442 if ($this->db->query($sql)) {
3443 $this->vat_reverse_charge = ($vatreversecharge == 0) ? 0 : 1;
3444 return 1;
3445 } else {
3446 dol_syslog(get_class($this).'::setVATReverseCharge Error ', LOG_DEBUG);
3447 $this->error = $this->db->error();
3448 return 0;
3449 }
3450 }
3451
3463 public function sendEmailsRemindersOnSupplierInvoiceDueDate($nbdays = 0, $paymentmode = 'all', $template = '', $datetouse = 'duedate', $forcerecipient = '')
3464 {
3465 global $conf, $langs, $user;
3466
3467 $this->output = '';
3468 $this->error = '';
3469 $nbMailSend = 0;
3470
3471 $error = 0;
3472 $errorsMsg = array();
3473
3474 $langs->load('bills');
3475
3476 if (!isModEnabled(empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD) ? 'fournisseur' : 'supplier_invoice')) { // Should not happen. If module disabled, cron job should not be visible.
3477 $this->output .= $langs->trans('ModuleNotEnabled', $langs->transnoentitiesnoconv('Suppliers'));
3478 return 0;
3479 }
3480 if (!in_array($datetouse, array('duedate', 'invoicedate'))) {
3481 $this->output .= 'Bad value for parameter datetouse. Must be "duedate" or "invoicedate"';
3482 return 0;
3483 }
3484
3485 require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
3486 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php';
3487 require_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
3488 $formmail = new FormMail($this->db);
3489
3490 $now = dol_now();
3491 $tmpidate = dol_get_first_hour(dol_time_plus_duree($now, $nbdays, 'd'), 'gmt');
3492
3493 $tmpinvoice = new FactureFournisseur($this->db);
3494
3495 dol_syslog(__METHOD__." start", LOG_INFO);
3496
3497 // Select all action comm reminder
3498 $sql = "SELECT rowid as id FROM ".MAIN_DB_PREFIX."facture_fourn as f";
3499 if (!empty($paymentmode) && $paymentmode != 'all') {
3500 $sql .= ", ".MAIN_DB_PREFIX."c_paiement as cp";
3501 }
3502 $sql .= " WHERE f.paye = 0"; // Only unpaid
3503 $sql .= " AND f.fk_statut = ".self::STATUS_VALIDATED; // Only validated status
3504 if ($datetouse == 'invoicedate') {
3505 $sql .= " AND f.datef = '".$this->db->idate($tmpidate, 'gmt')."'";
3506 } else {
3507 $sql .= " AND f.date_lim_reglement = '".$this->db->idate($tmpidate, 'gmt')."'";
3508 }
3509 $sql .= " AND f.entity IN (".getEntity('supplier_invoice', 0).")"; // One batch process only one company (no sharing)
3510 if (!empty($paymentmode) && $paymentmode != 'all') {
3511 $sql .= " AND f.fk_mode_reglement = cp.id AND cp.code = '".$this->db->escape($paymentmode)."'";
3512 }
3513 // TODO Add a filter to check there is no payment started yet
3514 if ($datetouse == 'invoicedate') {
3515 $sql .= $this->db->order("datef", "ASC");
3516 } else {
3517 $sql .= $this->db->order("date_lim_reglement", "ASC");
3518 }
3519
3520 $resql = $this->db->query($sql);
3521
3522 $stmpidate = dol_print_date($tmpidate, 'day', 'gmt');
3523 if ($datetouse == 'invoicedate') {
3524 $this->output .= $langs->transnoentitiesnoconv("SearchValidatedSupplierInvoicesWithDate", $stmpidate);
3525 } else {
3526 $this->output .= $langs->transnoentitiesnoconv("SearchUnpaidSupplierInvoicesWithDueDate", $stmpidate);
3527 }
3528 if (!empty($paymentmode) && $paymentmode != 'all') {
3529 $this->output .= ' ('.$langs->transnoentitiesnoconv("PaymentMode").' '.$paymentmode.')';
3530 }
3531 $this->output .= '<br>';
3532
3533 if ($resql) {
3534 while ($obj = $this->db->fetch_object($resql)) {
3535 if (!$error) {
3536 // Load event
3537 $res = $tmpinvoice->fetch($obj->id);
3538 if ($res > 0) {
3539 $tmpinvoice->fetch_thirdparty();
3540
3541 $outputlangs = new Translate('', $conf);
3542 if ($tmpinvoice->thirdparty->default_lang) {
3543 $outputlangs->setDefaultLang($tmpinvoice->thirdparty->default_lang);
3544 $outputlangs->loadLangs(array("main", "suppliers"));
3545 } else {
3546 $outputlangs = $langs;
3547 }
3548
3549 // Select email template according to language of recipient
3550 $templateId = 0;
3551 $templateLabel = '';
3552 if (empty($template) || $template == 'EmailTemplateCode') {
3553 $templateLabel = '(SendingReminderEmailOnUnpaidSupplierInvoice)';
3554 } else {
3555 if (is_numeric($template)) {
3556 $templateId = $template;
3557 } else {
3558 $templateLabel = $template;
3559 }
3560 }
3561
3562 $arraymessage = $formmail->getEMailTemplate($this->db, 'invoice_supplier_send', $user, $outputlangs, $templateId, 1, $templateLabel);
3563 if (is_numeric($arraymessage) && $arraymessage <= 0) {
3564 $langs->load("errors");
3565 $this->output .= $langs->trans('ErrorFailedToFindEmailTemplate', $template);
3566 return 0;
3567 }
3568
3569 // PREPARE EMAIL
3570 $errormesg = '';
3571
3572 // Make substitution in email content
3573 $substitutionarray = getCommonSubstitutionArray($outputlangs, 0, null, $tmpinvoice);
3574
3575 complete_substitutions_array($substitutionarray, $outputlangs, $tmpinvoice);
3576
3577 // Topic
3578 $sendTopic = make_substitutions(empty($arraymessage->topic) ? $outputlangs->transnoentitiesnoconv('InformationMessage') : $arraymessage->topic, $substitutionarray, $outputlangs, 1);
3579
3580 // Content
3581 $content = $outputlangs->transnoentitiesnoconv($arraymessage->content);
3582
3583 $sendContent = make_substitutions($content, $substitutionarray, $outputlangs, 1);
3584
3585 // Recipient
3586 $to = array();
3587 if ($forcerecipient) { // If a recipient was forced
3588 $to = array($forcerecipient);
3589 } else {
3590 $res = $tmpinvoice->fetch_thirdparty();
3591 $recipient = $tmpinvoice->thirdparty;
3592 if ($res > 0) {
3593 $tmparraycontact = $tmpinvoice->liste_contact(-1, 'internal', 0, 'SALESREPFOLL');
3594 if (is_array($tmparraycontact) && count($tmparraycontact) > 0) {
3595 foreach ($tmparraycontact as $data_email) {
3596 if (!empty($data_email['email'])) {
3597 $to[] = $data_email['email'];
3598 }
3599 }
3600 }
3601 if (empty($to) && !empty($recipient->email)) {
3602 $to[] = $recipient->email;
3603 }
3604 if (empty($to)) {
3605 $errormesg = "Failed to send remind to thirdparty id=".$tmpinvoice->socid.". No email defined for supplier invoice or customer.";
3606 $error++;
3607 }
3608 } else {
3609 $errormesg = "Failed to load recipient with thirdparty id=".$tmpinvoice->socid;
3610 $error++;
3611 }
3612 }
3613
3614 // Sender
3615 $from = getDolGlobalString('MAIN_MAIL_EMAIL_FROM');
3616 if (!empty($arraymessage->email_from)) { // If a sender is defined into template, we use it in priority
3617 $from = $arraymessage->email_from;
3618 }
3619 if (empty($from)) {
3620 $errormesg = "Failed to get sender into global setup MAIN_MAIL_EMAIL_FROM";
3621 $error++;
3622 }
3623
3624 if (!$error && !empty($to)) {
3625 $this->db->begin();
3626
3627 $to = implode(',', $to);
3628 if (!empty($arraymessage->email_to)) { // If a recipient is defined into template, we add it
3629 $to = $to.','.$arraymessage->email_to;
3630 }
3631
3632 // Errors Recipient
3633 $errors_to = $conf->global->MAIN_MAIL_ERRORS_TO;
3634
3635 $trackid = 'inv'.$tmpinvoice->id;
3636 $sendcontext = 'standard';
3637
3638 $email_tocc = '';
3639 if (!empty($arraymessage->email_tocc)) { // If a CC is defined into template, we use it
3640 $email_tocc = $arraymessage->email_tocc;
3641 }
3642
3643 $email_tobcc = '';
3644 if (!empty($arraymessage->email_tobcc)) { // If a BCC is defined into template, we use it
3645 $email_tobcc = $arraymessage->email_tobcc;
3646 }
3647
3648 // Mail Creation
3649 $cMailFile = new CMailFile($sendTopic, $to, $from, $sendContent, array(), array(), array(), $email_tocc, $email_tobcc, 0, 1, $errors_to, '', $trackid, '', $sendcontext, '');
3650
3651 // Sending Mail
3652 if ($cMailFile->sendfile()) {
3653 $nbMailSend++;
3654
3655 // Add a line into event table
3656 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
3657
3658 // Insert record of emails sent
3659 $actioncomm = new ActionComm($this->db);
3660
3661 $actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
3662 $actioncomm->socid = $tmpinvoice->thirdparty->id; // To link to a company
3663 $actioncomm->contact_id = 0;
3664
3665 $actioncomm->code = 'AC_EMAIL';
3666 $actioncomm->label = 'sendEmailsRemindersOnInvoiceDueDateOK (nbdays='.$nbdays.' paymentmode='.$paymentmode.' template='.$template.' datetouse='.$datetouse.' forcerecipient='.$forcerecipient.')';
3667 $actioncomm->note_private = $sendContent;
3668 $actioncomm->fk_project = $tmpinvoice->fk_project;
3669 $actioncomm->datep = dol_now();
3670 $actioncomm->datef = $actioncomm->datep;
3671 $actioncomm->percentage = -1; // Not applicable
3672 $actioncomm->authorid = $user->id; // User saving action
3673 $actioncomm->userownerid = $user->id; // Owner of action
3674 // Fields when action is an email (content should be added into note)
3675 $actioncomm->email_msgid = $cMailFile->msgid;
3676 $actioncomm->email_subject = $sendTopic;
3677 $actioncomm->email_from = $from;
3678 $actioncomm->email_sender = '';
3679 $actioncomm->email_to = $to;
3680 //$actioncomm->email_tocc = $sendtocc;
3681 //$actioncomm->email_tobcc = $sendtobcc;
3682 //$actioncomm->email_subject = $subject;
3683 $actioncomm->errors_to = $errors_to;
3684
3685 $actioncomm->elementtype = 'invoice_supplier';
3686 $actioncomm->fk_element = $tmpinvoice->id;
3687
3688 //$actioncomm->extraparams = $extraparams;
3689
3690 $actioncomm->create($user);
3691 } else {
3692 $errormesg = $cMailFile->error.' : '.$to;
3693 $error++;
3694
3695 // Add a line into event table
3696 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
3697
3698 // Insert record of emails sent
3699 $actioncomm = new ActionComm($this->db);
3700
3701 $actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
3702 $actioncomm->socid = $tmpinvoice->thirdparty->id; // To link to a company
3703 $actioncomm->contact_id = 0;
3704
3705 $actioncomm->code = 'AC_EMAIL';
3706 $actioncomm->label = 'sendEmailsRemindersOnInvoiceDueDateKO';
3707 $actioncomm->note_private = $errormesg;
3708 $actioncomm->fk_project = $tmpinvoice->fk_project;
3709 $actioncomm->datep = dol_now();
3710 $actioncomm->datef = $actioncomm->datep;
3711 $actioncomm->percentage = -1; // Not applicable
3712 $actioncomm->authorid = $user->id; // User saving action
3713 $actioncomm->userownerid = $user->id; // Owner of action
3714 // Fields when action is an email (content should be added into note)
3715 $actioncomm->email_msgid = $cMailFile->msgid;
3716 $actioncomm->email_from = $from;
3717 $actioncomm->email_sender = '';
3718 $actioncomm->email_to = $to;
3719 //$actioncomm->email_tocc = $sendtocc;
3720 //$actioncomm->email_tobcc = $sendtobcc;
3721 //$actioncomm->email_subject = $subject;
3722 $actioncomm->errors_to = $errors_to;
3723
3724 //$actioncomm->extraparams = $extraparams;
3725
3726 $actioncomm->create($user);
3727 }
3728
3729 $this->db->commit(); // We always commit
3730 }
3731
3732 if ($errormesg) {
3733 $errorsMsg[] = $errormesg;
3734 }
3735 } else {
3736 $errorsMsg[] = 'Failed to fetch record invoice with ID = '.$obj->id;
3737 $error++;
3738 }
3739 }
3740 }
3741 } else {
3742 $error++;
3743 }
3744
3745 if (!$error) {
3746 $this->output .= 'Nb of emails sent : '.$nbMailSend;
3747
3748 dol_syslog(__METHOD__." end - ".$this->output, LOG_INFO);
3749
3750 return 0;
3751 } else {
3752 $this->error = 'Nb of emails sent : '.$nbMailSend.', '.(empty($errorsMsg) ? $error : implode(', ', $errorsMsg));
3753
3754 dol_syslog(__METHOD__." end - ".$this->error, LOG_INFO);
3755
3756 return $error;
3757 }
3758 }
3759}
if( $user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
Definition card.php:66
$object ref
Definition info.php:89
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.
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 picto eventually)
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 getIdFromCode($dbs, $code)
Get id of currency from code.
static getIdAndTxFromCode($dbs, $code, $date_document='')
Get id and rate 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.
getCommonSubstitutionArray($outputlangs, $onlykey=0, $exclude=null, $object=null, $include=null)
Return array of possible common substitutions.
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='', $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:/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
Definition repair.php:150