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