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