dolibarr 25.0.0-alpha
facture-rec.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2003-2005 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2004-2019 Laurent Destailleur <eldy@users.sourceforge.net>
4 * Copyright (C) 2009-2012 Regis Houssin <regis.houssin@inodbox.com>
5 * Copyright (C) 2010-2011 Juanjo Menent <jmenent@2byte.es>
6 * Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr>
7 * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
8 * Copyright (C) 2015 Marcos García <marcosgdf@gmail.com>
9 * Copyright (C) 2017-2026 Frédéric France <frederic.france@free.fr>
10 * Copyright (C) 2023 Nick Fragoulis
11 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
12 * Copyright (C) 2026 Joris Le Blansch <ping@apio.systems>
13 *
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 3 of the License, or
17 * (at your option) any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with this program. If not, see <https://www.gnu.org/licenses/>.
26 */
27
34require_once DOL_DOCUMENT_ROOT.'/core/class/commoninvoice.class.php';
35require_once DOL_DOCUMENT_ROOT.'/core/class/notify.class.php';
36require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
37require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
38require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/factureligne.class.php';
39require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
40require_once DOL_DOCUMENT_ROOT.'/subtotals/class/commonsubtotal.class.php';
41
42
47{
48 use CommonSubtotal;
49
54 public $TRIGGER_PREFIX = 'BILLREC';
55
59 public $element = 'facturerec';
60
64 public $table_element = 'facture_rec';
65
69 public $table_element_line = 'facturedet_rec';
70
74 public $fk_element = 'fk_facture';
75
79 public $picto = 'bill';
80
84 protected $table_ref_field = 'titre';
85
89 public $title;
90
95 public $titre;
96
100 public $multicurrency_subprice;
104 public $socid;
108 public $number;
112 public $date;
113
119 public $total;
120
126 public $tva;
127
131 public $date_last_gen;
135 public $date_when;
139 public $nb_gen_done;
143 public $nb_gen_max;
144
148 public $user_author;
149
153 public $frequency;
154
158 public $unit_frequency;
159
163 public $rule_for_lines_dates;
164
168 public $rang;
169
173 public $special_code;
174
178 public $usenewprice = 0;
179
183 public $date_lim_reglement;
187 public $cond_reglement_code; // Code in llx_c_paiement
191 public $mode_reglement_code; // Code in llx_c_paiement
192
196 public $fk_societe_rib;
197
201 public $suspended; // status
202
206 public $auto_validate; // 0 to create in draft, 1 to create and validate the new invoice, 2 to create, validate and sending the new invoice
207
211 public $fk_email_template; // Email template for auto sending invoices
212
216 public $generate_pdf; // 1 to generate PDF on invoice generation (default)
217
221 public $usenewcurrencyrate;
222
223
224
225 const PAYMENTCODETOEDITSOCIETERIB = "PRE";
226
227
252 // BEGIN MODULEBUILDER PROPERTIES
256 public $fields = array(
257 'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 10),
258 'titre' => array('type' => 'varchar(100)', 'label' => 'Titre', 'enabled' => 1, 'showoncombobox' => 1, 'visible' => -1, 'position' => 15),
259 'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 20, 'index' => 1),
260 'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 'isModEnabled("societe")', 'visible' => -1, 'notnull' => 1, 'position' => 25),
261 'subtype' => array('type' => 'smallint(6)', 'label' => 'InvoiceSubtype', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 30),
262 'datec' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -1, 'position' => 35),
263 'total_tva' => array('type' => 'double(24,8)', 'label' => 'Tva', 'enabled' => 1, 'visible' => -1, 'position' => 55, 'isameasure' => 1),
264 'localtax1' => array('type' => 'double(24,8)', 'label' => 'Localtax1', 'enabled' => 1, 'visible' => -1, 'position' => 60, 'isameasure' => 1),
265 'localtax2' => array('type' => 'double(24,8)', 'label' => 'Localtax2', 'enabled' => 1, 'visible' => -1, 'position' => 65, 'isameasure' => 1),
266 'total_ht' => array('type' => 'double(24,8)', 'label' => 'Total', 'enabled' => 1, 'visible' => -1, 'position' => 70, 'isameasure' => 1),
267 'total_ttc' => array('type' => 'double(24,8)', 'label' => 'Total ttc', 'enabled' => 1, 'visible' => -1, 'position' => 75, 'isameasure' => 1),
268 'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => -1, 'position' => 80),
269 'fk_projet' => array('type' => 'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label' => 'Project', 'enabled' => "isModEnabled('project')", 'visible' => -1, 'position' => 85),
270 'fk_cond_reglement' => array('type' => 'integer', 'label' => 'PaymentTerm', 'enabled' => 1, 'visible' => -1, 'position' => 90),
271 'fk_mode_reglement' => array('type' => 'integer', 'label' => 'PaymentMode', 'enabled' => 1, 'visible' => -1, 'position' => 95),
272 'date_lim_reglement' => array('type' => 'date', 'label' => 'Date lim reglement', 'enabled' => 1, 'visible' => -1, 'position' => 100),
273 'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 105),
274 'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 110),
275 'modelpdf' => array('type' => 'varchar(255)', 'label' => 'Modelpdf', 'enabled' => 1, 'visible' => -1, 'position' => 115),
276 'date_when' => array('type' => 'datetime', 'label' => 'Date when', 'enabled' => 1, 'visible' => -1, 'position' => 130),
277 'date_last_gen' => array('type' => 'datetime', 'label' => 'Date last gen', 'enabled' => 1, 'visible' => -1, 'position' => 135),
278 'nb_gen_done' => array('type' => 'integer', 'label' => 'Nb gen done', 'enabled' => 1, 'visible' => -1, 'position' => 140),
279 'nb_gen_max' => array('type' => 'integer', 'label' => 'Nb gen max', 'enabled' => 1, 'visible' => -1, 'position' => 145),
280 'frequency' => array('type' => 'integer', 'label' => 'Frequency', 'enabled' => 1, 'visible' => -1, 'position' => 150),
281 'unit_frequency' => array('type' => 'varchar(2)', 'label' => 'UnitFrequency', 'enabled' => 1, 'visible' => -1, 'position' => 152),
282 'rule_for_lines_dates' => array('type' => 'varchar(255)', 'label' => 'RuleForLinesDates', 'enabled' => 'getDolGlobalInt("FACTUREREC_SUPPORT_RULE_FOR_LINES")', 'visible' => 1, 'position' => 153, 'arrayofkeyval' => array('prepaid' => "Prepaid", 'postpaid' => "Postpaid"), 'default' => 'prepaid'),
283 'usenewprice' => array('type' => 'integer', 'label' => 'UseNewPrice', 'enabled' => 1, 'visible' => 0, 'position' => 155),
284 'revenuestamp' => array('type' => 'double(24,8)', 'label' => 'RevenueStamp', 'enabled' => 1, 'visible' => -1, 'position' => 160, 'isameasure' => 1),
285 'auto_validate' => array('type' => 'integer', 'label' => 'Auto validate', 'enabled' => 1, 'visible' => -1, 'position' => 165),
286 'fk_email_template' => array('type' => 'integer:CEmailTemplate:core/class/cemailtemplate.class.php', 'label' => "Modèle d'e-mail pour l'envoi automatique", 'enabled' => 1, 'visible' => -1, 'position' => 167),
287 'generate_pdf' => array('type' => 'integer', 'label' => 'Generate pdf', 'enabled' => 1, 'visible' => -1, 'position' => 170),
288 'fk_account' => array('type' => 'integer', 'label' => 'BankAccount', 'enabled' => 'isModEnabled("bank")', 'visible' => -1, 'position' => 175),
289 'fk_multicurrency' => array('type' => 'integer', 'label' => 'Currency', 'enabled' => 1, 'visible' => -1, 'position' => 180),
290 'multicurrency_code' => array('type' => 'varchar(255)', 'label' => 'Multicurrency code', 'enabled' => 1, 'visible' => -1, 'position' => 185),
291 'multicurrency_tx' => array('type' => 'double(24,8)', 'label' => 'Multicurrency tx', 'enabled' => 1, 'visible' => -1, 'position' => 190, 'isameasure' => 1),
292 'multicurrency_total_ht' => array('type' => 'double(24,8)', 'label' => 'Multicurrency total ht', 'enabled' => 1, 'visible' => -1, 'position' => 195, 'isameasure' => 1),
293 'multicurrency_total_tva' => array('type' => 'double(24,8)', 'label' => 'Multicurrency total tva', 'enabled' => 1, 'visible' => -1, 'position' => 200, 'isameasure' => 1),
294 'multicurrency_total_ttc' => array('type' => 'double(24,8)', 'label' => 'Multicurrency total ttc', 'enabled' => 1, 'visible' => -1, 'position' => 205, 'isameasure' => 1),
295 'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'position' => 210),
296 'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 215),
297 'suspended' => array('type' => 'integer', 'label' => 'Suspended', 'enabled' => 1, 'visible' => -1, 'position' => 225),
298 'fk_societe_rib' => array('type' => 'integer', 'label' => 'RIB', 'enabled' => 'isModEnabled("bank")', 'visible' => -1, 'position' => 175),
299 );
300 // END MODULEBUILDER PROPERTIES
301
302 const STATUS_NOTSUSPENDED = 0;
303 const STATUS_SUSPENDED = 1;
304
305
306
312 public function __construct(DoliDB $db)
313 {
314 $this->db = $db;
315 }
316
326 public function create($user, $facid, $notrigger = 0, $onlylines = array())
327 {
328 global $conf;
329
330 $error = 0;
331 $now = dol_now();
332
333 // Clean parameters
334 $this->titre = trim(isset($this->titre) ? $this->titre : $this->title); // deprecated
335 $this->title = trim($this->title);
336 $this->usenewprice = empty($this->usenewprice) ? 0 : $this->usenewprice;
337 if (empty($this->suspended)) {
338 $this->suspended = 0;
339 }
340 if (empty($this->rule_for_lines_dates)) {
341 $this->rule_for_lines_dates = 'prepaid';
342 }
343
344 // No frequency defined then no next date to execution
345 if (empty($this->frequency)) {
346 $this->frequency = 0;
347 $this->date_when = null;
348 }
349
350 $this->frequency = abs($this->frequency);
351 $this->nb_gen_done = 0;
352 $this->nb_gen_max = empty($this->nb_gen_max) ? 0 : $this->nb_gen_max;
353 $this->auto_validate = empty($this->auto_validate) ? 0 : $this->auto_validate;
354 $this->generate_pdf = empty($this->generate_pdf) ? 0 : $this->generate_pdf;
355
356 $this->db->begin();
357
358 // Load invoice template
359 $facsrc = new Facture($this->db);
360 $result = $facsrc->fetch($facid);
361 if ($result > 0) {
362 $this->socid = $facsrc->socid;
363
364 $sql = "INSERT INTO ".MAIN_DB_PREFIX."facture_rec (";
365 $sql .= "titre";
366 $sql .= ", fk_soc";
367 $sql .= ", subtype";
368 $sql .= ", entity";
369 $sql .= ", datec";
370 $sql .= ", amount";
371 //$sql .= ", remise";
372 $sql .= ", note_private";
373 $sql .= ", note_public";
374 $sql .= ", modelpdf";
375 $sql .= ", fk_user_author";
376 $sql .= ", fk_projet";
377 $sql .= ", fk_account";
378 $sql .= ", fk_cond_reglement";
379 $sql .= ", fk_mode_reglement";
380 $sql .= ", usenewprice";
381 $sql .= ", frequency";
382 $sql .= ", unit_frequency";
383 $sql .= ", date_when";
384 $sql .= ", date_last_gen";
385 $sql .= ", nb_gen_done";
386 $sql .= ", nb_gen_max";
387 $sql .= ", auto_validate";
388 $sql .= ", fk_email_template";
389 $sql .= ", generate_pdf";
390 $sql .= ", fk_multicurrency";
391 $sql .= ", multicurrency_code";
392 $sql .= ", multicurrency_tx";
393 $sql .= ", suspended";
394 $sql .= ", fk_societe_rib";
395 $sql .= ", rule_for_lines_dates";
396 $sql .= ") VALUES (";
397 $sql .= "'".$this->db->escape($this->titre ? $this->titre : $this->title)."'";
398 $sql .= ", ".((int) $this->socid);
399 $sql .= ", ".(isset($this->subtype) ? (int) $this->subtype : "null");
400 $sql .= ", ".((int) $conf->entity);
401 $sql .= ", '".$this->db->idate($now)."'";
402 $sql .= ", ".(!empty($facsrc->total_ttc) ? ((float) $facsrc->total_ttc) : '0');
403 $sql .= ", ".(!empty($this->note_private) ? ("'".$this->db->escape($this->note_private)."'") : "NULL");
404 $sql .= ", ".(!empty($this->note_public) ? ("'".$this->db->escape($this->note_public)."'") : "NULL");
405 $sql .= ", ".(!empty($this->model_pdf) ? ("'".$this->db->escape($this->model_pdf)."'") : "NULL");
406 $sql .= ", ".((int) $user->id);
407 $sql .= ", ".(!empty($this->fk_project) ? ((int) $this->fk_project) : "null");
408 $sql .= ", ".(!empty($facsrc->fk_account) ? ((int) $facsrc->fk_account) : "null");
409 $sql .= ", ".($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : "null");
410 $sql .= ", ".($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : "null");
411 $sql .= ", ".((int) $this->usenewprice);
412 $sql .= ", ".((int) $this->frequency);
413 $sql .= ", '".$this->db->escape($this->unit_frequency)."'";
414 $sql .= ", ".(!empty($this->date_when) ? "'".$this->db->idate($this->date_when)."'" : 'NULL');
415 $sql .= ", ".(!empty($this->date_last_gen) ? "'".$this->db->idate($this->date_last_gen)."'" : 'NULL');
416 $sql .= ", ".((int) $this->nb_gen_done);
417 $sql .= ", ".((int) $this->nb_gen_max);
418 $sql .= ", ".((int) $this->auto_validate);
419 $sql .= ", ".((int) $this->fk_email_template);
420 $sql .= ", ".((int) $this->generate_pdf);
421 $sql .= ", ".((int) $facsrc->fk_multicurrency);
422 $sql .= ", '".$this->db->escape($facsrc->multicurrency_code)."'";
423 $sql .= ", ".((float) $facsrc->multicurrency_tx);
424 $sql .= ", ".((int) $this->suspended);
425 $sql .= ", ".(!empty($this->fk_societe_rib) ? ((int) $this->fk_societe_rib) : 'NULL');
426 $sql .= ", '".$this->db->escape($this->rule_for_lines_dates)."'";
427 $sql .= ")";
428
429 if ($this->db->query($sql)) {
430 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."facture_rec");
431
432 // Fields used into addline later
433 $this->fk_multicurrency = $facsrc->fk_multicurrency;
434 $this->multicurrency_code = $facsrc->multicurrency_code;
435 $this->multicurrency_tx = $facsrc->multicurrency_tx;
436
437 // Add lines
438 $fk_parent_line = 0;
439
440 $num = count($facsrc->lines);
441 for ($i = 0; $i < $num; $i++) {
442 $facline = $facsrc->lines[$i];
443 '@phan-var-force FactureLigneRec $facline';
444 if (!empty($onlylines) && !in_array($facline->id, $onlylines)) {
445 continue; // Skip unselected lines
446 }
447
448 // Reset fk_parent_line for no child products and special product
449 if (($facline->product_type != 9 && empty($facline->fk_parent_line)) || $facline->product_type == 9) {
450 $fk_parent_line = 0;
451 }
452
453 $tva_tx = $facline->tva_tx;
454 if (!empty($facline->vat_src_code) && !preg_match('/\‍(/', (string) $tva_tx)) {
455 $tva_tx .= ' ('.$facline->vat_src_code.')';
456 }
457
458 $default_start_fill = getDolGlobalInt('INVOICEREC_SET_AUTOFILL_DATE_START');
459 $default_end_fill = getDolGlobalInt('INVOICEREC_SET_AUTOFILL_DATE_END');
460
461 $result_insert = $this->addline(
462 $facline->desc,
463 $facline->subprice,
464 $facline->qty,
465 $tva_tx,
466 $facline->localtax1_tx,
467 $facline->localtax2_tx,
468 $facline->fk_product,
469 $facline->remise_percent,
470 'HT',
471 $facline->info_bits,
472 0,
473 0,
474 $facline->product_type,
475 $facline->rang,
476 $facline->special_code,
477 $facline->label,
478 $facline->fk_unit,
479 $facline->multicurrency_subprice,
480 $default_start_fill,
481 $default_end_fill,
482 0,
483 $facline->pa_ht,
484 $fk_parent_line
485 );
486
487 // Defined the new fk_parent_line
488 if ($result_insert > 0 && $facline->product_type == 9) {
489 $fk_parent_line = $result_insert;
490 }
491
492 if ($result_insert < 0) {
493 $error++;
494 } else {
495 $objectline = new FactureLigneRec($this->db);
496
497 $result2 = $objectline->fetch($result_insert);
498 if ($result2 > 0) {
499 // Extrafields
500 if (method_exists($facline, 'fetch_optionals')) {
501 $facline->fetch_optionals($facline->id);
502 $objectline->array_options = $facline->array_options;
503 }
504
505 $result = $objectline->insertExtraFields();
506 if ($result < 0) {
507 $error++;
508 }
509
510 $objectline->extraparams = $facline->extraparams;
511 $result = $objectline->setExtraParameters();
512 if ($result < 0) {
513 $error++;
514 }
515 } elseif ($result2 < 0) {
516 $this->errors[] = $objectline->error;
517 $error++;
518 }
519 }
520 }
521
522 if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
523 $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
524 }
525
526 // Add object linked
527 if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
528 foreach ($this->linked_objects as $origin => $tmp_origin_id) {
529 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, ...))
530 foreach ($tmp_origin_id as $origin_id) {
531 $ret = $this->add_object_linked($origin, $origin_id);
532 if (!$ret) {
533 $this->error = $this->db->lasterror();
534 $error++;
535 }
536 }
537 } else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
538 $origin_id = $tmp_origin_id;
539 $ret = $this->add_object_linked($origin, $origin_id);
540 if (!$ret) {
541 $this->error = $this->db->lasterror();
542 $error++;
543 }
544 }
545 }
546 }
547
548 if (!$error) {
549 $result = $this->insertExtraFields();
550 if ($result < 0) {
551 $error++;
552 }
553 }
554
555 if (!$error && !$notrigger) {
556 // Call trigger
557 $result = $this->call_trigger('BILLREC_CREATE', $user);
558 if ($result < 0) {
559 $this->db->rollback();
560 return -2;
561 }
562 // End call triggers
563 }
564
565 if ($error) {
566 $this->db->rollback();
567 return -3;
568 } else {
569 $this->db->commit();
570 return $this->id;
571 }
572 } else {
573 $this->error = $this->db->lasterror();
574 $this->db->rollback();
575 return -2;
576 }
577 } else {
578 $this->db->rollback();
579 return -1;
580 }
581 }
582
583
591 public function update(User $user, $notrigger = 0)
592 {
593 $error = 0;
594
595 $sql = "UPDATE ".$this->db->prefix()."facture_rec SET";
596 $sql .= " entity = ".((int) $this->entity).",";
597 $sql .= " titre = '".$this->db->escape($this->title)."',";
598 $sql .= " suspended = ".((int) $this->suspended).",";
599 $sql .= " fk_soc = ".((int) $this->socid).",";
600 $sql .= " total_tva = ".((float) $this->total_tva).",";
601 $sql .= " localtax1 = ".((float) $this->total_localtax1).",";
602 $sql .= " localtax2 = ".((float) $this->total_localtax2).",";
603 $sql .= " total_ht = ".((float) $this->total_ht).",";
604 $sql .= " total_ttc = ".((float) $this->total_ttc).",";
605 $sql .= " fk_societe_rib = ".(!empty($this->fk_societe_rib) ? ((int) $this->fk_societe_rib) : 'NULL').",";
606 $sql .= " auto_validate = ".((int) $this->auto_validate).",";
607 $sql .= " fk_email_template = ".((int) $this->fk_email_template);
608
609 // TODO Add missing fields
610 $sql .= " WHERE rowid = ".((int) $this->id);
611
612 $this->db->begin();
613
614 dol_syslog(get_class($this)."::update", LOG_DEBUG);
615
616 $resql = $this->db->query($sql);
617 if ($resql) {
618 if (!$error) {
619 $result = $this->insertExtraFields();
620 if ($result < 0) {
621 $error++;
622 }
623 }
624
625 if (!$error && !$notrigger) {
626 // Call trigger
627 $result = $this->call_trigger('BILLREC_MODIFY', $user);
628 if ($result < 0) {
629 $this->db->rollback();
630 return -2;
631 }
632 // End call triggers
633 }
634 $this->db->commit();
635 return 1;
636 } else {
637 $this->error = $this->db->lasterror();
638 $this->db->rollback();
639 return -1;
640 }
641 }
642
653 public function fetch($rowid, $ref = '', $ref_ext = '', $noextrafields = 0, $nolines = 0)
654 {
655 dol_syslog('FactureRec::fetch', LOG_DEBUG);
656
657 $sql = 'SELECT f.rowid, f.entity, f.titre as title, f.suspended, f.fk_soc, f.subtype, f.total_tva, f.localtax1, f.localtax2, f.total_ht, f.total_ttc';
658 $sql .= ', f.date_lim_reglement as dlr';
659 $sql .= ', f.note_private, f.note_public, f.fk_user_author';
660 $sql .= ', f.modelpdf as model_pdf';
661 $sql .= ', f.fk_mode_reglement, f.fk_cond_reglement, f.fk_projet as fk_project';
662 $sql .= ', f.fk_account, f.fk_societe_rib';
663 $sql .= ', f.frequency, f.unit_frequency, f.rule_for_lines_dates, f.date_when, f.date_last_gen, f.nb_gen_done, f.nb_gen_max, f.usenewprice, f.auto_validate';
664 $sql .= ', f.fk_email_template';
665 $sql .= ', f.generate_pdf';
666 $sql .= ", f.fk_multicurrency, f.multicurrency_code, f.multicurrency_tx, f.multicurrency_total_ht, f.multicurrency_total_tva, f.multicurrency_total_ttc";
667 $sql .= ', p.code as mode_reglement_code, p.libelle as mode_reglement_libelle';
668 $sql .= ', c.code as cond_reglement_code, c.libelle as cond_reglement_libelle, c.libelle_facture as cond_reglement_libelle_doc';
669 //$sql.= ', el.fk_source';
670 $sql .= ' FROM '.MAIN_DB_PREFIX.'facture_rec as f';
671 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_payment_term as c ON f.fk_cond_reglement = c.rowid';
672 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as p ON f.fk_mode_reglement = p.id';
673 //$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."element_element as el ON el.fk_target = f.rowid AND el.targettype = 'facture'";
674 $sql .= ' WHERE f.entity IN ('.getEntity('invoice').')';
675 if ($rowid) {
676 $sql .= ' AND f.rowid = '.((int) $rowid);
677 } elseif ($ref) {
678 $sql .= " AND f.titre = '".$this->db->escape($ref)."'";
679 } else {
680 $sql .= ' AND f.rowid = 0';
681 }
682 /* This field are not used for template invoice
683 if ($ref_ext) $sql.= " AND f.ref_ext='".$this->db->escape($ref_ext)."'";
684 */
685
686 $result = $this->db->query($sql);
687 if ($result) {
688 if ($this->db->num_rows($result)) {
689 $obj = $this->db->fetch_object($result);
690
691 $this->id = $obj->rowid;
692 $this->entity = $obj->entity;
693 $this->titre = $obj->title; // deprecated
694 $this->title = $obj->title;
695 $this->ref = $obj->title;
696 $this->subtype = $obj->subtype;
697
698 $this->status = $obj->suspended;
699 $this->suspended = $obj->suspended;
700
701 $this->total_ht = $obj->total_ht;
702 $this->total_tva = $obj->total_tva;
703 $this->total_localtax1 = $obj->localtax1;
704 $this->total_localtax2 = $obj->localtax2;
705 $this->total_ttc = $obj->total_ttc;
706 $this->socid = $obj->fk_soc;
707 $this->date_lim_reglement = $this->db->jdate($obj->dlr);
708 $this->mode_reglement_id = $obj->fk_mode_reglement;
709 $this->mode_reglement_code = $obj->mode_reglement_code;
710 $this->mode_reglement = $obj->mode_reglement_libelle;
711 $this->cond_reglement_id = $obj->fk_cond_reglement;
712 $this->cond_reglement_code = $obj->cond_reglement_code;
713 $this->cond_reglement = $obj->cond_reglement_libelle;
714 $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
715 $this->fk_project = $obj->fk_project;
716 $this->fk_account = $obj->fk_account;
717 $this->fk_societe_rib = $obj->fk_societe_rib;
718 $this->note_private = $obj->note_private;
719 $this->note_public = $obj->note_public;
720 $this->user_creation_id = $obj->fk_user_author;
721 $this->model_pdf = $obj->model_pdf;
722 //$this->special_code = $obj->special_code;
723 $this->frequency = $obj->frequency;
724 $this->unit_frequency = $obj->unit_frequency;
725 $this->rule_for_lines_dates = $obj->rule_for_lines_dates;
726 $this->date_when = $this->db->jdate($obj->date_when);
727 $this->date_last_gen = $this->db->jdate($obj->date_last_gen);
728 $this->nb_gen_done = $obj->nb_gen_done;
729 $this->nb_gen_max = $obj->nb_gen_max;
730 $this->usenewprice = $obj->usenewprice;
731 $this->auto_validate = $obj->auto_validate;
732 $this->fk_email_template = $obj->fk_email_template;
733 $this->generate_pdf = $obj->generate_pdf;
734
735 // Multicurrency
736 $this->fk_multicurrency = $obj->fk_multicurrency;
737 $this->multicurrency_code = $obj->multicurrency_code;
738 $this->multicurrency_tx = $obj->multicurrency_tx;
739 $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
740 $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
741 $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
742
743 // Retrieve all extrafield
744 // fetch optionals attributes and labels
745 if (empty($noextrafields)) {
746 $result = $this->fetch_optionals();
747 if ($result < 0) {
748 $this->error = $this->db->lasterror();
749 return -4;
750 }
751 }
752
753 // Retrieve lines
754 if (empty($nolines)) {
755 $result = $this->fetch_lines();
756 if ($result < 0) {
757 $this->error = $this->db->lasterror();
758 return -3;
759 }
760 }
761
762 return 1;
763 } else {
764 $this->error = 'Bill with id '.$rowid.' or ref '.$ref.' not found';
765 dol_syslog('Facture::Fetch Error '.$this->error, LOG_ERR);
766 return -2;
767 }
768 } else {
769 $this->error = $this->db->error();
770 return -1;
771 }
772 }
773
774
780 public function getLinesArray()
781 {
782 return $this->fetch_lines();
783 }
784
785
786 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
792 public function fetch_lines()
793 {
794 // phpcs:enable
795
796 $this->lines = array();
797
798 dol_syslog('FactureRec::fetch_lines', LOG_DEBUG);
799
800 $sql = 'SELECT l.rowid, l.fk_facture, l.fk_product, l.fk_parent_line, l.product_type, l.label as custom_label, l.description, l.product_type, l.price, l.qty, l.vat_src_code, l.tva_tx, ';
801 $sql .= ' l.localtax1_tx, l.localtax2_tx, l.localtax1_type, l.localtax2_type, l.remise, l.remise_percent, l.subprice,';
802 $sql .= ' l.info_bits, l.date_start_fill, l.date_end_fill, l.total_ht, l.total_tva, l.total_ttc, l.fk_product_fournisseur_price, l.buy_price_ht as pa_ht,';
803 $sql .= ' l.rang, l.special_code,';
804 $sql .= ' l.fk_unit, l.fk_contract_line, l.extraparams,';
805 $sql .= ' l.fk_multicurrency, l.multicurrency_code, l.multicurrency_subprice, l.multicurrency_total_ht, l.multicurrency_total_tva, l.multicurrency_total_ttc,';
806 $sql .= ' p.ref as product_ref, p.fk_product_type as fk_product_type, p.label as product_label, p.description as product_desc';
807 $sql .= ' FROM '.MAIN_DB_PREFIX.'facturedet_rec as l';
808 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON l.fk_product = p.rowid';
809 $sql .= ' WHERE l.fk_facture = '.((int) $this->id);
810 $sql .= ' ORDER BY l.rang';
811
812 $result = $this->db->query($sql);
813 if ($result) {
814 $num = $this->db->num_rows($result);
815 $i = 0;
816 while ($i < $num) {
817 $objp = $this->db->fetch_object($result);
818 $line = new FactureLigneRec($this->db);
819
820 $line->id = $objp->rowid;
821 $line->rowid = $objp->rowid;
822 $line->fk_facture = $objp->fk_facture;
823 $line->fk_parent_line = $objp->fk_parent_line;
824 $line->desc = $objp->description; // Description line
825 $line->description = $objp->description; // Description line
826 $line->ref = $objp->product_ref; // Ref product
827 $line->product_ref = $objp->product_ref; // Ref product
828 $line->libelle = $objp->product_label; // deprecated
829 $line->product_label = $objp->product_label; // Label product
830 $line->product_desc = $objp->product_desc; // Description product
831 $line->product_type = $objp->product_type; // Type of line
832 $line->fk_product_type = $objp->fk_product_type; // Type of product
833 $line->qty = $objp->qty;
834 $line->subprice = $objp->subprice;
835
836 $line->label = $objp->custom_label; // @deprecated
837
838 $line->vat_src_code = $objp->vat_src_code;
839 $line->tva_tx = $objp->tva_tx;
840 $line->localtax1_tx = $objp->localtax1_tx;
841 $line->localtax2_tx = $objp->localtax2_tx;
842 $line->localtax1_type = $objp->localtax1_type;
843 $line->localtax2_type = $objp->localtax2_type;
844 $line->remise_percent = $objp->remise_percent;
845 //$line->fk_remise_except = $objp->fk_remise_except;
846 $line->fk_product = $objp->fk_product;
847 $line->date_start_fill = $objp->date_start_fill;
848 $line->date_end_fill = $objp->date_end_fill;
849 $line->info_bits = $objp->info_bits;
850 $line->total_ht = $objp->total_ht;
851 $line->total_tva = $objp->total_tva;
852 $line->total_ttc = $objp->total_ttc;
853
854 $line->fk_product_fournisseur_price = $objp->fk_product_fournisseur_price;
855 $line->fk_fournprice = $objp->fk_product_fournisseur_price; // For backward compatibility
856
857 $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $objp->fk_product_fournisseur_price, $objp->pa_ht);
858
859 $line->buyprice = $marginInfos[0];
860 $line->pa_ht = $marginInfos[0]; // For backward compatibility
861 $line->marge_tx = (string) $marginInfos[1];
862 $line->marque_tx = (string) $marginInfos[2];
863 $line->rang = $objp->rang;
864 $line->special_code = $objp->special_code;
865 $line->fk_unit = $objp->fk_unit;
866 $line->fk_contract_line = $objp->fk_contract_line;
867
868 $line->extraparams = !empty($objp->extraparams) ? (array) json_decode($objp->extraparams, true) : array();
869
870 // Ne plus utiliser
871 $line->price = $objp->price;
872 $line->remise = $objp->remise;
873
874 $line->fetch_optionals();
875
876 // Multicurrency
877 $line->fk_multicurrency = $objp->fk_multicurrency;
878 $line->multicurrency_code = $objp->multicurrency_code;
879 $line->multicurrency_subprice = $objp->multicurrency_subprice;
880 $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
881 $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
882 $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
883
884 $this->lines[$i] = $line;
885
886 $i++;
887 }
888
889 $this->db->free($result);
890 return 1;
891 } else {
892 $this->error = $this->db->lasterror();
893 return -3;
894 }
895 }
896
897
906 public function delete(User $user, $notrigger = 0, $idwarehouse = -1)
907 {
908 $rowid = $this->id;
909
910 dol_syslog(get_class($this)."::delete rowid=".((int) $rowid), LOG_DEBUG);
911
912 $error = 0;
913 $this->db->begin();
914
915 $sql_main_table = MAIN_DB_PREFIX.'facturedet_rec';
916 $sql_ef_table = $sql_main_table."_extrafields";
917
918 $sqlef = "DELETE FROM $sql_ef_table WHERE fk_object IN (SELECT rowid FROM ".$sql_main_table." WHERE fk_facture = ".((int) $rowid).")";
919 $sql = "DELETE FROM ".MAIN_DB_PREFIX."facturedet_rec WHERE fk_facture = ".((int) $rowid);
920
921 if ($this->db->query($sqlef) && $this->db->query($sql)) {
922 $sql = "DELETE FROM ".MAIN_DB_PREFIX."facture_rec WHERE rowid = ".((int) $rowid);
923 dol_syslog($sql);
924 if ($this->db->query($sql)) {
925 // Delete linked object
926 $res = $this->deleteObjectLinked();
927 if ($res < 0) {
928 $error = -3;
929 }
930 // Delete extrafields
931 $res = $this->deleteExtraFields();
932 if ($res < 0) {
933 $error = -4;
934 }
935 } else {
936 $this->error = $this->db->lasterror();
937 $error = -1;
938 }
939 } else {
940 $this->error = $this->db->lasterror();
941 $error = -2;
942 }
943 if (!$error && !$notrigger) {
944 // Call trigger
945 $result = $this->call_trigger('BILLREC_DELETE', $user);
946 if ($result < 0) {
947 $error++;
948 }
949 // End call triggers
950 }
951 if (!$error) {
952 $this->db->commit();
953 return 1;
954 } else {
955 $this->db->rollback();
956 return $error;
957 }
958 }
959
960
990 public function addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1 = 0, $txlocaltax2 = 0, $fk_product = 0, $remise_percent = 0, $price_base_type = 'HT', $info_bits = 0, $fk_remise_except = 0, $pu_ttc = 0, $type = 0, $rang = -1, $special_code = 0, $label = '', $fk_unit = null, $pu_ht_devise = 0, $date_start_fill = 0, $date_end_fill = 0, $fk_fournprice = null, $pa_ht = 0, $fk_parent_line = 0, $array_options = array())
991 {
992 global $mysoc;
993
994 $facid = $this->id;
995
996 dol_syslog(get_class($this)."::addline facid=$facid,desc=$desc,pu_ht=$pu_ht,qty=$qty,txtva=$txtva,txlocaltax1=$txlocaltax1,txlocaltax2=$txlocaltax2,fk_product=$fk_product,remise_percent=$remise_percent,info_bits=$info_bits,fk_remise_except=$fk_remise_except,price_base_type=$price_base_type,pu_ttc=$pu_ttc,type=$type,fk_unit=$fk_unit,pu_ht_devise=$pu_ht_devise,date_start_fill=$date_start_fill,date_end_fill=$date_end_fill,pa_ht=$pa_ht", LOG_DEBUG);
997 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
998
999 // Check parameters
1000 if ($type < 0) {
1001 return -1;
1002 }
1003
1004 $localtaxes_type = getLocalTaxesFromRate((float) $txtva, 0, $this->thirdparty, $mysoc);
1005
1006 // Clean vat code
1007 $reg = array();
1008 $vat_src_code = '';
1009 if (preg_match('/\‍((.*)\‍)/', (string) $txtva, $reg)) {
1010 $vat_src_code = $reg[1];
1011 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', (string) $txtva); // Remove code from vatrate.
1012 }
1013
1014
1015 // Clean parameters
1016 $remise_percent = price2num($remise_percent);
1017 if (empty($remise_percent)) {
1018 $remise_percent = 0;
1019 }
1020 $qty = (float) price2num($qty);
1021 $pu_ht = (float) price2num($pu_ht);
1022 $pu_ttc = (float) price2num($pu_ttc);
1023 if (!preg_match('/\‍((.*)\‍)/', $txtva)) {
1024 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
1025 }
1026 $txlocaltax1 = (float) price2num($txlocaltax1);
1027 $txlocaltax2 = (float) price2num($txlocaltax2);
1028 if (empty($txtva)) {
1029 $txtva = 0;
1030 }
1031 if (empty($txlocaltax1)) {
1032 $txlocaltax1 = 0;
1033 }
1034 if (empty($txlocaltax2)) {
1035 $txlocaltax2 = 0;
1036 }
1037 if (empty($info_bits)) {
1038 $info_bits = 0;
1039 }
1040
1041 if ($price_base_type == 'HT') {
1042 $pu = $pu_ht;
1043 } else {
1044 $pu = $pu_ttc;
1045 }
1046
1047 // Calculation of the gross total (TTC) and VAT for the line from qty, pu, remise_percent and txtva
1048 // VERY IMPORTANT: It's at the time of line insertion that we must store the net, VAT, and gross amounts,
1049 // and this is done at the line level, which has its own VAT rate
1050
1051 $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise);
1052 $total_ht = $tabprice[0];
1053 $total_tva = $tabprice[1];
1054 $total_ttc = $tabprice[2];
1055 $total_localtax1 = $tabprice[9];
1056 $total_localtax2 = $tabprice[10];
1057 $pu_ht = $tabprice[3];
1058
1059 // MultiCurrency
1060 $multicurrency_total_ht = $tabprice[16];
1061 $multicurrency_total_tva = $tabprice[17];
1062 $multicurrency_total_ttc = $tabprice[18];
1063 $pu_ht_devise = $tabprice[19];
1064
1065 $product_type = $type;
1066 if ($fk_product) {
1067 $product = new Product($this->db);
1068 $result = $product->fetch($fk_product);
1069 $product_type = $product->type;
1070 }
1071
1072 if (empty($fk_parent_line) || $fk_parent_line < 0) {
1073 $fk_parent_line = 0;
1074 }
1075
1076 // Rank to use
1077 $ranktouse = $rang;
1078 if ($ranktouse == -1) {
1079 $rangmax = $this->line_max(0);
1080 $ranktouse = $rangmax + 1;
1081 }
1082
1083 $sql = "INSERT INTO ".MAIN_DB_PREFIX."facturedet_rec (";
1084 $sql .= "fk_facture";
1085 $sql .= ", fk_parent_line";
1086 $sql .= ", label";
1087 $sql .= ", description";
1088 $sql .= ", price";
1089 $sql .= ", qty";
1090 $sql .= ", tva_tx";
1091 $sql .= ", vat_src_code";
1092 $sql .= ", localtax1_tx";
1093 $sql .= ", localtax1_type";
1094 $sql .= ", localtax2_tx";
1095 $sql .= ", localtax2_type";
1096 $sql .= ", fk_product";
1097 $sql .= ", product_type";
1098 $sql .= ", remise_percent";
1099 $sql .= ", subprice";
1100 $sql .= ", remise";
1101 $sql .= ", total_ht";
1102 $sql .= ", total_tva";
1103 $sql .= ", total_localtax1";
1104 $sql .= ", total_localtax2";
1105 $sql .= ", total_ttc";
1106 $sql .= ", date_start_fill";
1107 $sql .= ", date_end_fill";
1108 $sql .= ", fk_product_fournisseur_price";
1109 $sql .= ", buy_price_ht";
1110 $sql .= ", info_bits";
1111 $sql .= ", rang";
1112 $sql .= ", special_code";
1113 $sql .= ", fk_unit";
1114 $sql .= ', fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc';
1115 $sql .= ") VALUES (";
1116 $sql .= " ".((int) $facid);
1117 $sql .= ", ".($fk_parent_line > 0 ? ((int) $fk_parent_line) : "null");
1118 $sql .= ", ".(!empty($label) ? "'".$this->db->escape($label)."'" : "null");
1119 $sql .= ", '".$this->db->escape($desc)."'";
1120 $sql .= ", ".price2num($pu_ht);
1121 $sql .= ", ".price2num($qty);
1122 $sql .= ", ".price2num($txtva);
1123 $sql .= ", '".$this->db->escape($vat_src_code)."'";
1124 $sql .= ", ".price2num($txlocaltax1);
1125 $sql .= ", '".$this->db->escape(isset($localtaxes_type[0]) ? $localtaxes_type[0] : '')."'";
1126 $sql .= ", ".price2num($txlocaltax2);
1127 $sql .= ", '".$this->db->escape(isset($localtaxes_type[2]) ? $localtaxes_type[2] : '')."'";
1128 $sql .= ", ".(!empty($fk_product) ? "'".$this->db->escape((string) $fk_product)."'" : "null");
1129 $sql .= ", ".((int) $product_type);
1130 $sql .= ", ".price2num($remise_percent);
1131 $sql .= ", ".price2num($pu_ht);
1132 $sql .= ", null";
1133 $sql .= ", ".price2num($total_ht);
1134 $sql .= ", ".price2num($total_tva);
1135 $sql .= ", ".price2num($total_localtax1);
1136 $sql .= ", ".price2num($total_localtax2);
1137 $sql .= ", ".price2num($total_ttc);
1138 $sql .= ", ".(int) $date_start_fill;
1139 $sql .= ", ".(int) $date_end_fill;
1140 $sql .= ", ".($fk_fournprice > 0 ? ((int) $fk_fournprice) : 'null');
1141 $sql .= ", ".($pa_ht ? price2num($pa_ht) : 0);
1142 $sql .= ", ".((int) $info_bits);
1143 $sql .= ", ".((int) $ranktouse);
1144 $sql .= ", ".((int) $special_code);
1145 $sql .= ", ".($fk_unit ? ((int) $fk_unit) : "null");
1146 $sql .= ", ".(int) $this->fk_multicurrency;
1147 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
1148 $sql .= ", ".price2num($pu_ht_devise, 'CU');
1149 $sql .= ", ".price2num($multicurrency_total_ht, 'CT');
1150 $sql .= ", ".price2num($multicurrency_total_tva, 'CT');
1151 $sql .= ", ".price2num($multicurrency_total_ttc, 'CT');
1152 $sql .= ")";
1153
1154 dol_syslog(get_class($this)."::addline", LOG_DEBUG);
1155 if ($this->db->query($sql)) {
1156 $lineId = $this->db->last_insert_id(MAIN_DB_PREFIX."facturedet_rec");
1157 $this->id = $facid;
1158 $this->update_price(1);
1159
1160 if (is_array($array_options) && count($array_options) > 0) {
1161 $factureRecLine = new FactureLigneRec($this->db);
1162 $factureRecLine->id = $lineId;
1163 $factureRecLine->array_options = $array_options;
1164 $result = $factureRecLine->insertExtraFields();
1165 if ($result < 0) {
1166 $this->errors[] = $factureRecLine->error;
1167 return -2;
1168 }
1169 }
1170
1171 return $lineId;
1172 } else {
1173 $this->error = $this->db->lasterror();
1174 return -1;
1175 }
1176 }
1177
1208 public function updateline($rowid, $desc, $pu_ht, $qty, $txtva, $txlocaltax1 = 0, $txlocaltax2 = 0, $fk_product = 0, $remise_percent = 0, $price_base_type = 'HT', $info_bits = 0, $fk_remise_except = 0, $pu_ttc = 0, $type = 0, $rang = -1, $special_code = 0, $label = '', $fk_unit = null, $pu_ht_devise = 0, $notrigger = 0, $date_start_fill = 0, $date_end_fill = 0, $fk_fournprice = null, $pa_ht = 0, $fk_parent_line = 0)
1209 {
1210 global $mysoc;
1211
1212 if (!$this->isLineOfObject($rowid)) {
1213 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
1214 return -1;
1215 }
1216
1217 $facid = $this->id;
1218
1219 dol_syslog(get_class($this)."::updateline facid=".$facid." rowid=$rowid, desc=$desc, pu_ht=$pu_ht, qty=$qty, txtva=$txtva, txlocaltax1=$txlocaltax1, txlocaltax2=$txlocaltax2, fk_product=$fk_product, remise_percent=$remise_percent, info_bits=$info_bits, fk_remise_except=$fk_remise_except, price_base_type=$price_base_type, pu_ttc=$pu_ttc, type=$type, fk_unit=$fk_unit, pu_ht_devise=$pu_ht_devise", LOG_DEBUG);
1220 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
1221
1222 // Clean parameters
1223 if (empty($remise_percent)) {
1224 $remise_percent = 0;
1225 }
1226
1227 // Check parameters
1228 if ($type < 0) {
1229 return -1;
1230 }
1231
1232 // Clean parameters
1233 $remise_percent = (float) price2num($remise_percent);
1234 $qty = (float) price2num($qty);
1235 if (empty($info_bits)) {
1236 $info_bits = 0;
1237 }
1238 $pu_ht = (float) price2num($pu_ht);
1239 $pu_ttc = (float) price2num($pu_ttc);
1240 $pu_ht_devise = (float) price2num($pu_ht_devise);
1241 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
1242 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
1243 }
1244 $txlocaltax1 = (float) price2num($txlocaltax1);
1245 $txlocaltax2 = (float) price2num($txlocaltax2);
1246 if (empty($txlocaltax1)) {
1247 $txlocaltax1 = 0;
1248 }
1249 if (empty($txlocaltax2)) {
1250 $txlocaltax2 = 0;
1251 }
1252
1253 if (empty($this->multicurrency_subprice)) {
1254 $this->multicurrency_subprice = 0;
1255 }
1256 if (empty($this->multicurrency_total_ht)) {
1257 $this->multicurrency_total_ht = 0;
1258 }
1259 if (empty($this->multicurrency_total_tva)) {
1260 $this->multicurrency_total_tva = 0;
1261 }
1262 if (empty($this->multicurrency_total_ttc)) {
1263 $this->multicurrency_total_ttc = 0;
1264 }
1265
1266 if ($price_base_type == 'HT') {
1267 $pu = $pu_ht;
1268 } else {
1269 $pu = $pu_ttc;
1270 }
1271
1272 // Calculation of the gross total (TTC) and VAT for the line from qty, pu, remise_percent and txtva
1273 // VERY IMPORTANT: It's at the time of line insertion that we must store the net, VAT, and gross amounts,
1274 // and this is done at the line level, which has its own VAT rate
1275
1276 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
1277
1278 // Clean vat code
1279 $vat_src_code = '';
1280 $reg = array();
1281 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
1282 $vat_src_code = $reg[1];
1283 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
1284 }
1285
1286 $tabprice = calcul_price_total($qty, $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, $pu_ht_devise);
1287
1288 $total_ht = $tabprice[0];
1289 $total_tva = $tabprice[1];
1290 $total_ttc = $tabprice[2];
1291 $total_localtax1 = $tabprice[9];
1292 $total_localtax2 = $tabprice[10];
1293 $pu_ht = $tabprice[3];
1294 $pu_tva = $tabprice[4];
1295 $pu_ttc = $tabprice[5];
1296
1297 // MultiCurrency
1298 $multicurrency_total_ht = $tabprice[16];
1299 $multicurrency_total_tva = $tabprice[17];
1300 $multicurrency_total_ttc = $tabprice[18];
1301 $pu_ht_devise = $tabprice[19];
1302
1303 $product_type = $type;
1304 if ($fk_product) {
1305 $product = new Product($this->db);
1306 $result = $product->fetch($fk_product);
1307 $product_type = $product->type;
1308 }
1309
1310 if (empty($fk_parent_line) || $fk_parent_line < 0) {
1311 $fk_parent_line = 0;
1312 }
1313
1314 $sql = "UPDATE ".MAIN_DB_PREFIX."facturedet_rec SET ";
1315 $sql .= "fk_facture = ".((int) $facid);
1316 $sql .= ", fk_parent_line = ".($fk_parent_line > 0 ? ((int) $fk_parent_line) : "null");
1317 $sql .= ", label=".(!empty($label) ? "'".$this->db->escape($label)."'" : "null");
1318 $sql .= ", description='".$this->db->escape($desc)."'";
1319 $sql .= ", price=".price2num($pu_ht);
1320 $sql .= ", qty=".price2num($qty);
1321 $sql .= ", tva_tx=".price2num($txtva);
1322 $sql .= ", vat_src_code='".$this->db->escape($vat_src_code)."'";
1323 $sql .= ", localtax1_tx=".((float) $txlocaltax1);
1324 $sql .= ", localtax1_type='".$this->db->escape($localtaxes_type[0])."'";
1325 $sql .= ", localtax2_tx=".((float) $txlocaltax2);
1326 $sql .= ", localtax2_type='".$this->db->escape($localtaxes_type[2])."'";
1327 $sql .= ", fk_product=".(!empty($fk_product) ? "'".$this->db->escape((string) $fk_product)."'" : "null");
1328 $sql .= ", product_type=".((int) $product_type);
1329 $sql .= ", remise_percent='".price2num($remise_percent)."'";
1330 $sql .= ", subprice='".price2num($pu_ht)."'";
1331 $sql .= ", total_ht='".price2num($total_ht)."'";
1332 $sql .= ", total_tva='".price2num($total_tva)."'";
1333 $sql .= ", total_localtax1='".price2num($total_localtax1)."'";
1334 $sql .= ", total_localtax2='".price2num($total_localtax2)."'";
1335 $sql .= ", total_ttc='".price2num($total_ttc)."'";
1336 $sql .= ", date_start_fill=".((int) $date_start_fill);
1337 $sql .= ", date_end_fill=".((int) $date_end_fill);
1338 $sql .= ", fk_product_fournisseur_price=".($fk_fournprice > 0 ? (int) $fk_fournprice : 'null');
1339 $sql .= ", buy_price_ht=".($pa_ht ? price2num($pa_ht) : 0);
1340 $sql .= ", info_bits=".((int) $info_bits);
1341 $sql .= ", rang=".((int) $rang);
1342 $sql .= ", special_code=".((int) $special_code);
1343 $sql .= ", fk_unit=".($fk_unit ? "'".$this->db->escape((string) $fk_unit)."'" : "null");
1344 $sql .= ', multicurrency_subprice = '.price2num($pu_ht_devise);
1345 $sql .= ', multicurrency_total_ht = '.price2num($multicurrency_total_ht);
1346 $sql .= ', multicurrency_total_tva = '.price2num($multicurrency_total_tva);
1347 $sql .= ', multicurrency_total_ttc = '.price2num($multicurrency_total_ttc);
1348 $sql .= " WHERE rowid = ".((int) $rowid);
1349
1350 dol_syslog(get_class($this)."::updateline", LOG_DEBUG);
1351 if ($this->db->query($sql)) {
1352 $this->id = $facid;
1353 $this->update_price(1);
1354 return 1;
1355 } else {
1356 $this->error = $this->db->lasterror();
1357 return -1;
1358 }
1359 }
1360
1361
1370 public function getNextDate()
1371 {
1372 if (empty($this->date_when)) {
1373 return false;
1374 }
1375
1376 // Get the original day of the month from date_when
1377 $dateInfo = dol_getdate($this->date_when);
1378 $originalDay = (int) $dateInfo['mday'];
1379 $originalMonth = (int) $dateInfo['mon'];
1380 $originalYear = (int) $dateInfo['year'];
1381 $originalHour = (int) $dateInfo['hours'];
1382 $originalMin = (int) $dateInfo['minutes'];
1383 $originalSec = (int) $dateInfo['seconds'];
1384
1385 // Special handling for end-of-month: if day >= 28 and frequency is monthly
1386 if ($originalDay >= 28 && $this->unit_frequency == 'm') {
1387 // Get the last day of the original month to determine if this was an "end of month" date
1388 $lastDayOfOriginalMonth = (int) date('t', $this->date_when);
1389
1390 // Calculate target month and year
1391 $targetMonth = $originalMonth + (int) $this->frequency;
1392 $targetYear = $originalYear;
1393
1394 // Handle year rollover
1395 while ($targetMonth > 12) {
1396 $targetMonth -= 12;
1397 $targetYear++;
1398 }
1399 while ($targetMonth < 1) {
1400 $targetMonth += 12;
1401 $targetYear--;
1402 }
1403
1404 // Get the last day of the target month
1405 $lastDayOfTargetMonth = (int) date('t', dol_mktime(0, 0, 0, $targetMonth, 1, $targetYear));
1406
1407 // Determine the target day:
1408 // If original was last day of month, OR original day >= 29, use end-of-month behavior
1409 if ($originalDay >= $lastDayOfOriginalMonth || $originalDay >= 29) {
1410 // End of month mode: use the last day of target month
1411 $targetDay = $lastDayOfTargetMonth;
1412 } else {
1413 // Day is 28 but not end of month in a 30/31 day month
1414 // Keep as 28 or use last day if target month is shorter (like February)
1415 $targetDay = min($originalDay, $lastDayOfTargetMonth);
1416 }
1417
1418 // Return the calculated date
1419 return dol_mktime($originalHour, $originalMin, $originalSec, $targetMonth, $targetDay, $targetYear);
1420 }
1421
1422 // For yearly frequency or days < 28, use standard calculation
1423 return dol_time_plus_duree($this->date_when, $this->frequency, $this->unit_frequency);
1424 }
1425
1431 public function isMaxNbGenReached()
1432 {
1433 $ret = false;
1434 if ($this->nb_gen_max > 0 && ($this->nb_gen_done >= $this->nb_gen_max)) {
1435 $ret = true;
1436 }
1437 return $ret;
1438 }
1439
1446 public function strikeIfMaxNbGenReached($ret)
1447 {
1448 return $this->isMaxNbGenReached() ? '<strike>'.$ret.'</strike>' : $ret;
1449 }
1450
1463 public function createRecurringInvoices($restrictioninvoiceid = 0, $forcevalidation = 0, $notrigger = 0, $forcebuilddoc = 0)
1464 {
1465 global $conf, $langs, $user, $hookmanager, $action;
1466
1467 $error = 0;
1468 $nb_create = 0;
1469
1470 // Load translation files required by the page
1471 $langs->loadLangs(array("main", "bills"));
1472
1473 $now = dol_now();
1474 $tmparray = dol_getdate($now);
1475 $today = dol_mktime(23, 59, 59, $tmparray['mon'], $tmparray['mday'], $tmparray['year']); // Today is last second of current day
1476
1477 $this->output = '';
1478
1479 dol_syslog("createRecurringInvoices restrictioninvoiceid=".$restrictioninvoiceid." forcevalidation=".$forcevalidation." forcebuilddoc=".$forcebuilddoc, LOG_DEBUG);
1480
1481 $sql = 'SELECT rowid FROM '.MAIN_DB_PREFIX.'facture_rec';
1482 $sql .= ' WHERE frequency > 0'; // A recurring invoice is an invoice with a frequency
1483 $sql .= " AND (date_when IS NULL OR date_when <= '".$this->db->idate($today)."')";
1484 $sql .= ' AND (nb_gen_done < nb_gen_max OR nb_gen_max = 0)';
1485 $sql .= ' AND suspended = 0';
1486 $sql .= ' AND entity = '.((int) $conf->entity); // MUST STAY = $conf->entity here
1487 if ($restrictioninvoiceid > 0) {
1488 $sql .= ' AND rowid = '.((int) $restrictioninvoiceid);
1489 }
1490 $sql .= $this->db->order('entity', 'ASC');
1491 if (getDolGlobalInt('NB_REC_FACT_CUSTOMER_GEN_BY_CALL')) {
1492 $sql .= $this->db->plimit(getDolGlobalInt('NB_REC_FACT_CUSTOMER_GEN_BY_CALL'));
1493 }
1494 //print $sql;exit;
1495 $parameters = array(
1496 'restrictioninvoiceid' => $restrictioninvoiceid,
1497 'forcevalidation' => $forcevalidation,
1498 );
1499 $reshook = $hookmanager->executeHooks('beforeCreationOfRecurringInvoices', $parameters, $sql); // note that $sql might be modified by hooks
1500
1501 $resql = $this->db->query($sql);
1502 if ($resql) {
1503 $i = 0;
1504 $num = $this->db->num_rows($resql);
1505
1506 if ($num) {
1507 $this->output .= $langs->trans("FoundXQualifiedRecurringInvoiceTemplate", $num)."\n";
1508 } else {
1509 $this->output .= $langs->trans("NoQualifiedRecurringInvoiceTemplateFound");
1510 }
1511
1512 $saventity = $conf->entity;
1513
1514 while ($i < $num) { // Loop on each template invoice. If $num = 0, test is false at first pass.
1515 $line = $this->db->fetch_object($resql);
1516
1517 $this->db->begin();
1518
1519 $errorforinvoice = 0;
1520 $invoiceidgenerated = 0;
1521 $mailHasSent = false;
1522 // Create a loopError that is reset at each loop, this counter is added to the global counter at the end of loop
1523 $loopError = 0;
1524
1525 $facture = null;
1526 $facturerec = new FactureRec($this->db);
1527 $facturerec->fetch($line->rowid);
1528
1529 if ($facturerec->id > 0) {
1530 // Set entity context
1531 $conf->entity = $facturerec->entity;
1532
1533 dol_syslog("createRecurringInvoices Process invoice template id=".$facturerec->id.", ref=".$facturerec->ref.", entity=".$facturerec->entity);
1534
1535 $facture = new Facture($this->db);
1536 $facture->fac_rec = $facturerec->id; // We will create $facture from this recurring invoice
1537 $facture->fk_fac_rec_source = $facturerec->id; // We will create $facture from this recurring invoice
1538
1539 $facture->type = self::TYPE_STANDARD;
1540 $facture->subtype = $facturerec->subtype;
1541 $facture->statut = self::STATUS_DRAFT; // deprecated
1542 $facture->status = self::STATUS_DRAFT;
1543 $facture->date = (empty($facturerec->date_when) ? $now : $facturerec->date_when); // We could also use dol_now here but we prefer date_when so invoice has real date when we would like even if we generate later.
1544 $facture->socid = $facturerec->socid;
1545
1546 if (!empty($facturerec->fk_multicurrency)) {
1547 $facture->fk_multicurrency = $facturerec->fk_multicurrency;
1548 $facture->multicurrency_code = $facturerec->multicurrency_code;
1549 $facture->multicurrency_tx = $facturerec->multicurrency_tx;
1550 }
1551
1552 if (isset($facture->array_options) && isset($facturerec->array_options)) {
1553 foreach ($facturerec->array_options as $key => $value) {
1554 if (isset($facture->array_options[$key])) {
1555 $facture->array_options[$key] = $value;
1556 }
1557 }
1558 }
1559
1560 $parameters['facture'] = &$facture;
1561 $reshook = $hookmanager->executeHooks('beforeCreationOfEachRecurringInvoice', $parameters, $facturerec, $action); // note that $facturerec or $facture might be modified by hooks
1562
1563 // Create invoice. This may update prices according to multiplrice rules
1564 $invoiceidgenerated = $facture->create($user, 0, 0, (isModEnabled('multicurrency') ? $facturerec->usenewcurrencyrate : 0));
1565 if ($invoiceidgenerated <= 0) {
1566 $this->setErrorsFromObject($facture);
1567 $error++;
1568 $errorforinvoice++;
1569 }
1570
1571
1572 if (!$errorforinvoice && ($facturerec->auto_validate || $forcevalidation)) {
1573 $result = $facture->validate($user);
1574 if ($result <= 0) {
1575 $this->setErrorsFromObject($facture);
1576 $error++;
1577 $errorforinvoice++;
1578 }
1579 }
1580 if (!$errorforinvoice && ($facturerec->generate_pdf || $forcebuilddoc || $facturerec->auto_validate == 2)) { // ->generate_pdf is 1 by default (can be edited if INVOICE_REC_CAN_DISABLE_DOCUMENT_FILE_GENERATION is set to 1)
1581 // We reload the object in order to have all necessary data (like date_lim_reglement)
1582 $facture->fetch($facture->id);
1583 $facture->fetch_thirdparty();
1584
1585 $outputlangs = $langs;
1586 if (getDolGlobalInt('MAIN_MULTILANGS')) {
1587 if (!empty($facture->thirdparty->default_lang)) {
1588 $outputlangs = new Translate('', $conf);
1589 $outputlangs->setDefaultLang($facture->thirdparty->default_lang);
1590 $outputlangs->loadLangs(array('main', 'bills'));
1591 }
1592 }
1593
1594 $result = 1;
1595 if ($facturerec->generate_pdf || $forcebuilddoc) {
1596 $result = $facture->generateDocument($facturerec->model_pdf, $outputlangs);
1597 if ($result <= 0) {
1598 $this->setErrorsFromObject($facture);
1599 $error++;
1600 $errorforinvoice++;
1601 }
1602 }
1603
1604 // Auto sending of the invoice
1605 if ($result > 0 && $facture->status == Facture::STATUS_VALIDATED && $facturerec->auto_validate == 2) {
1606 require_once DOL_DOCUMENT_ROOT . '/core/class/html.formmail.class.php';
1607 require_once DOL_DOCUMENT_ROOT . '/core/class/CMailFile.class.php';
1608 $formmail = new FormMail($this->db);
1609
1610 // Select email template according to language of recipient
1611 $template = $facturerec->fk_email_template;
1612 $arraymessage = $formmail->getEMailTemplate($this->db, 'facture_send', $user, $outputlangs, (is_numeric($template) ? $template : 0), 1, (is_numeric($template) ? '' : $template), (($template != 0) ? -1 : 1));
1613 if (is_numeric($arraymessage) && $arraymessage <= 0) {
1614 $langs->load("errors");
1615 $this->output .= $langs->trans('ErrorFailedToFindEmailTemplate', $template ?? '');
1616 return 0;
1617 }
1618
1619 // PREPARE EMAIL
1620 $errormesg = '';
1621
1622 // Make substitution in email content
1623 $substitutionarray = getCommonSubstitutionArray($outputlangs, 0, null, $facture);
1624
1625 complete_substitutions_array($substitutionarray, $outputlangs, $facture);
1626
1627 // Topic
1628 $sendTopic = make_substitutions(empty($arraymessage->topic) ? $outputlangs->transnoentitiesnoconv('InformationMessage') : $arraymessage->topic, $substitutionarray, $outputlangs, 1);
1629
1630 // Content
1631 $content = $outputlangs->transnoentitiesnoconv($arraymessage->content);
1632
1633 $sendContent = make_substitutions($content, $substitutionarray, $outputlangs, 1);
1634
1635 // Recipient
1636 $to = array();
1637 $res = $facture->fetch_thirdparty();
1638 $recipient = $facture->thirdparty;
1639 if ($res > 0) {
1640 $tmparraycontact = $facture->liste_contact(-1, 'external', 0, 'BILLING');
1641 if (is_array($tmparraycontact) && count($tmparraycontact) > 0) {
1642 foreach ($tmparraycontact as $data_email) {
1643 if (!empty($data_email['email'])) {
1644 $to[] = $facture->thirdparty->contact_get_property($data_email['id'], 'email');
1645 }
1646 }
1647 }
1648 if (empty($to) && !empty($recipient->email)) {
1649 $to[] = $recipient->email;
1650 }
1651 if (empty($to)) {
1652 $errormesg = "Failed to send invoice to thirdparty id=".$facture->socid.". No email defined for invoice or customer.";
1653 $loopError++;
1654 }
1655 } else {
1656 $errormesg = "Failed to load recipient with thirdparty id=".$facture->socid;
1657 $loopError++;
1658 }
1659
1660 // Sender
1661 $email_from = getDolGlobalString('MAIN_MAIL_EMAIL_FROM');
1662 if (!empty($arraymessage->email_from)) { // If a sender is defined into template, we use it in priority
1663 $email_from = (string) $arraymessage->email_from;
1664 }
1665 if (empty($email_from)) {
1666 $errormesg = "Failed to get sender into global setup MAIN_MAIL_EMAIL_FROM";
1667 $loopError++;
1668 }
1669
1670 if (!$loopError && !empty($to)) {
1671 $to = implode(',', $to);
1672 if (!empty($arraymessage->email_to)) { // If a recipient is defined into template, we add it
1673 $to = $to . ',' . $arraymessage->email_to;
1674 }
1675
1676 // Errors Recipient
1677 $errors_to = getDolGlobalString('MAIN_MAIL_ERRORS_TO');
1678
1679 $trackid = 'inv'.$facture->id;
1680 $sendcontext = 'standard';
1681
1682 // CC
1683 $email_tocc = '';
1684 if (!empty($arraymessage->email_tocc)) { // If a CC is defined into template, we use it
1685 $email_tocc = (string) $arraymessage->email_tocc;
1686 }
1687
1688 // BCC
1689 $email_tobcc = '';
1690 if (!empty($arraymessage->email_tobcc)) { // If a BCC is defined into template, we use it
1691 $email_tobcc = (string) $arraymessage->email_tobcc;
1692 }
1693
1694 //join file is asked
1695 $joinFile = [];
1696 $joinFileName = [];
1697 $joinFileMime = [];
1698 if ($arraymessage->joinfiles == 1 && !empty($facture->last_main_doc)) {
1699 $joinFile[] = DOL_DATA_ROOT.'/'.$facture->last_main_doc;
1700 $joinFileName[] = basename($facture->last_main_doc);
1701 $joinFileMime[] = dol_mimetype(DOL_DATA_ROOT.'/'.$facture->last_main_doc);
1702 }
1703
1704 // Mail Creation
1705 $cMailFile = new CMailFile($sendTopic, $to, $email_from, $sendContent, $joinFile, $joinFileMime, $joinFileName, $email_tocc, $email_tobcc, 0, 1, $errors_to, '', $trackid, '', $sendcontext, '');
1706
1707 $resultsendmail = $cMailFile->sendfile();
1708
1709 $this->db->begin();
1710
1711 // Sending Mail
1712 if ($resultsendmail) {
1713 $mailHasSent = true;
1714
1715 // Add a line into event table
1716 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
1717
1718 // Insert record of emails sent
1719 $actioncomm = new ActionComm($this->db);
1720
1721 $actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
1722 $actioncomm->socid = $facture->thirdparty->id; // To link to a company
1723 $actioncomm->contact_id = 0;
1724
1725 $actioncomm->code = 'AC_EMAIL';
1726 $actioncomm->label = $langs->trans('MailSentByTo', $email_from, $to);
1727 $actioncomm->note_private = $sendContent;
1728 $actioncomm->fk_project = $facture->fk_project;
1729 $actioncomm->datep = dol_now();
1730 $actioncomm->datef = $actioncomm->datep;
1731 $actioncomm->percentage = -1; // Not applicable
1732 $actioncomm->authorid = $user->id; // User saving action
1733 $actioncomm->userownerid = $user->id; // Owner of action
1734 // Fields when action is an email (content should be added into note)
1735 $actioncomm->email_msgid = $cMailFile->msgid;
1736 $actioncomm->email_subject = $sendTopic;
1737 $actioncomm->email_from = $email_from;
1738 $actioncomm->email_sender = '';
1739 $actioncomm->email_to = $to;
1740 //$actioncomm->email_tocc = $sendtocc;
1741 //$actioncomm->email_tobcc = $sendtobcc;
1742 //$actioncomm->email_subject = $subject;
1743 $actioncomm->errors_to = $errors_to;
1744
1745 $actioncomm->elementtype = 'invoice';
1746 $actioncomm->elementid = $facture->id;
1747
1748 //$actioncomm->extraparams = $extraparams;
1749
1750 $actioncomm->create($user);
1751 } else {
1752 $errormesg = $cMailFile->error.' : '.$to;
1753 $loopError++;
1754
1755 // Add a line into event table
1756 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
1757
1758 // Insert record of emails sent
1759 $actioncomm = new ActionComm($this->db);
1760
1761 $actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
1762 $actioncomm->socid = $facture->thirdparty->id; // To link to a company
1763 $actioncomm->contact_id = 0;
1764
1765 $actioncomm->code = 'AC_EMAIL';
1766 $actioncomm->label = $langs->trans('sendAutoEmailInvoiceKO', $email_from, $to);
1767 $actioncomm->note_private = $errormesg;
1768 $actioncomm->fk_project = $facture->fk_project;
1769 $actioncomm->datep = dol_now();
1770 $actioncomm->datef = $actioncomm->datep;
1771 $actioncomm->percentage = -1; // Not applicable
1772 $actioncomm->authorid = $user->id; // User saving action
1773 $actioncomm->userownerid = $user->id; // Owner of action
1774 // Fields when action is an email (content should be added into note)
1775 $actioncomm->email_msgid = $cMailFile->msgid;
1776 $actioncomm->email_subject = $sendTopic;
1777 $actioncomm->email_from = $email_from;
1778 $actioncomm->email_sender = '';
1779 $actioncomm->email_to = $to;
1780 //$actioncomm->email_tocc = $sendtocc;
1781 //$actioncomm->email_tobcc = $sendtobcc;
1782 //$actioncomm->email_subject = $subject;
1783 $actioncomm->errors_to = $errors_to;
1784
1785 $actioncomm->elementtype = 'invoice';
1786 $actioncomm->elementid = $facture->id;
1787
1788 //$actioncomm->extraparams = $extraparams;
1789
1790 $actioncomm->create($user);
1791 }
1792
1793 $this->db->commit(); // We always commit
1794 }
1795 }
1796 }
1797 } else {
1798 $error++;
1799 $this->error = "Failed to load invoice template with id=".$line->rowid.", entity=".$conf->entity."\n";
1800 $this->errors[] = "Failed to load invoice template with id=".$line->rowid.", entity=".$conf->entity;
1801 dol_syslog("createRecurringInvoices Failed to load invoice template with id=".$line->rowid.", entity=".$conf->entity);
1802 }
1803
1804 // Commit or rollback. Only the result of the current template must be tested (not the cumulative $error), so that a failure on one template does not cancel the templates processed after it.
1805 // $invoiceidgenerated must be > 0 (not >= 0): it is still 0 when $facturerec->fetch() failed above, in which case $facture is still null.
1806 if (!$errorforinvoice && $invoiceidgenerated > 0) {
1807 $this->db->commit("createRecurringInvoices Process invoice template id=".$facturerec->id.", ref=".$facturerec->ref);
1808 dol_syslog("createRecurringInvoices Process invoice template ".$facturerec->ref." is finished with a success generation");
1809 $nb_create++;
1810 $this->output .= $langs->trans("InvoiceGeneratedFromTemplate", $facture->ref, $facturerec->ref)."\n";
1811
1812 // Mail error
1813 if ($loopError > 0) {
1814 $this->output .= $langs->trans("InvoiceSentFromTemplateError", $facture->ref, $facturerec->ref, $errormesg ?? '')."\n";
1815 }
1816
1817 // Mail sent
1818 if ($mailHasSent) {
1819 $this->output .= $langs->trans("InvoiceSentFromTemplate", $facture->ref, $facturerec->ref)."\n";
1820 }
1821 } else {
1822 // $facture is still null when $facturerec->fetch() failed above
1823 $this->output .= $langs->trans("InvoiceGeneratedFromTemplateError", (is_object($facture) ? $facture->ref : ''), $facturerec->ref, $this->error)."\n";
1824 $this->db->rollback("createRecurringInvoices Process invoice template id=".$facturerec->id.", ref=".$facturerec->ref);
1825 }
1826
1827 $parameters = array(
1828 'cpt' => $i,
1829 'total' => $num,
1830 'errorCount' => $error,
1831 'errorForInvoice' => $errorforinvoice,
1832 'invoiceidgenerated' => $invoiceidgenerated,
1833 'facturerec' => $facturerec, // it's an object which PHP passes by "reference", so modifiable by hooks.
1834 'this' => $this, // it's an object which PHP passes by "reference", so modifiable by hooks.
1835 );
1836 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
1837 $reshook = $hookmanager->executeHooks('afterCreationOfRecurringInvoice', $parameters, $facture); // note: $facture can be modified by hooks (warning: $facture can be null)
1838
1839 $i++;
1840 }
1841
1842 $conf->entity = $saventity; // Restore entity context
1843 } else {
1844 dol_print_error($this->db);
1845 }
1846
1847 $this->output = trim($this->output);
1848
1849 return $error ? $error : 0;
1850 }
1851
1864 public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $moretitle = '', $notooltip = 0, $save_lastsearch_value = -1)
1865 {
1866 global $langs, $hookmanager;
1867
1868 $result = '';
1869
1870 $label = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("RepeatableInvoice").'</u>';
1871 if (!empty($this->ref)) {
1872 $label .= '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
1873 }
1874 if ($this->frequency > 0) {
1875 $label .= '<br><b>'.$langs->trans('Frequency').':</b> '.$langs->trans('FrequencyPer_'.$this->unit_frequency, $this->frequency);
1876 }
1877 if (!empty($this->date_last_gen)) {
1878 $label .= '<br><b>'.$langs->trans('DateLastGeneration').':</b> '.dol_print_date($this->date_last_gen, 'dayhour');
1879 }
1880 if ($this->frequency > 0) {
1881 if (!empty($this->date_when)) {
1882 $label .= '<br><b>'.$langs->trans('NextDateToExecution').':</b> ';
1883 $label .= (empty($this->suspended) ? '' : '<strike>').dol_print_date($this->date_when, 'day').(empty($this->suspended) ? '' : '</strike>'); // No hour for this property
1884 if (!empty($this->suspended)) {
1885 $label .= ' ('.$langs->trans("Disabled").')';
1886 }
1887 }
1888 }
1889
1890 $url = DOL_URL_ROOT.'/compta/facture/card-rec.php?id='.$this->id;
1891
1892 if ($short) {
1893 return $url;
1894 }
1895
1896 if ($option != 'nolink') {
1897 // Add param to save lastsearch_values or not
1898 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1899 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1900 $add_save_lastsearch_values = 1;
1901 }
1902 if ($add_save_lastsearch_values) {
1903 $url .= '&save_lastsearch_values=1';
1904 }
1905 }
1906
1907 $linkstart = '<a href="'.$url.'" title="'.dol_escape_htmltag($label, 1).'" class="classfortooltip">';
1908 $linkend = '</a>';
1909
1910 $result .= $linkstart;
1911 if ($withpicto) {
1912 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
1913 }
1914 if ($withpicto != 2) {
1915 $result .= dol_trunc($this->ref, $max);
1916 }
1917 $result .= $linkend;
1918 global $action;
1919 $hookmanager->initHooks(array($this->element . 'dao'));
1920 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1921 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1922 if ($reshook > 0) {
1923 $result = $hookmanager->resPrint;
1924 } else {
1925 $result .= $hookmanager->resPrint;
1926 }
1927 return $result;
1928 }
1929
1937 public function getLibStatut($mode = 0, $alreadypaid = -1)
1938 {
1939 return $this->LibStatut($this->frequency ? 1 : 0, $this->suspended, $mode, $alreadypaid, empty($this->type) ? 0 : $this->type);
1940 }
1941
1942 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1954 public function LibStatut($recur, $status, $mode = 0, $alreadypaid = -1, $type = 0, $nbofopendirectdebitorcredittransfer = 0)
1955 {
1956 // phpcs:enable
1957 global $langs;
1958 $langs->load('bills');
1959
1960 $labelStatus = $langs->transnoentitiesnoconv('Active');
1961 $statusType = 'status0';
1962
1963 //print "$recur,$status,$mode,$alreadypaid,$type";
1964 if ($mode == 0) {
1965 if ($recur) {
1966 if ($status == self::STATUS_SUSPENDED) {
1967 $labelStatus = $langs->transnoentitiesnoconv('Disabled');
1968 } else {
1969 $labelStatus = $langs->transnoentitiesnoconv('Active');
1970 }
1971 } else {
1972 if ($status == self::STATUS_SUSPENDED) {
1973 $labelStatus = $langs->transnoentitiesnoconv('Disabled');
1974 } else {
1975 $labelStatus = $langs->transnoentitiesnoconv("Draft");
1976 }
1977 }
1978 } elseif ($mode == 1) {
1979 $prefix = 'Short';
1980 if ($recur) {
1981 if ($status == self::STATUS_SUSPENDED) {
1982 $labelStatus = $langs->transnoentitiesnoconv('Disabled');
1983 } else {
1984 $labelStatus = $langs->transnoentitiesnoconv('Active');
1985 }
1986 } else {
1987 if ($status == self::STATUS_SUSPENDED) {
1988 $labelStatus = $langs->transnoentitiesnoconv('Disabled');
1989 } else {
1990 $labelStatus = $langs->transnoentitiesnoconv("Draft");
1991 }
1992 }
1993 } elseif ($mode == 2) {
1994 if ($recur) {
1995 if ($status == self::STATUS_SUSPENDED) {
1996 $statusType = 'status6';
1997 $labelStatus = $langs->transnoentitiesnoconv('Disabled');
1998 } else {
1999 $statusType = 'status4';
2000 $labelStatus = $langs->transnoentitiesnoconv('Active');
2001 }
2002 } else {
2003 if ($status == self::STATUS_SUSPENDED) {
2004 $statusType = 'status6';
2005 $labelStatus = $langs->transnoentitiesnoconv('Disabled');
2006 } else {
2007 $statusType = 'status0';
2008 $labelStatus = $langs->transnoentitiesnoconv('Draft');
2009 }
2010 }
2011 } elseif ($mode == 3) {
2012 if ($recur) {
2013 $prefix = 'Short';
2014 if ($status == self::STATUS_SUSPENDED) {
2015 $statusType = 'status6';
2016 $labelStatus = $langs->transnoentitiesnoconv('Disabled');
2017 } else {
2018 $statusType = 'status4';
2019 $labelStatus = $langs->transnoentitiesnoconv('Active');
2020 }
2021 } else {
2022 if ($status == self::STATUS_SUSPENDED) {
2023 $statusType = 'status6';
2024 $labelStatus = $langs->transnoentitiesnoconv('Disabled');
2025 } else {
2026 $statusType = 'status0';
2027 $labelStatus = $langs->transnoentitiesnoconv('Draft');
2028 }
2029 }
2030 } elseif ($mode == 4) {
2031 $prefix = '';
2032 if ($recur) {
2033 if ($status == self::STATUS_SUSPENDED) {
2034 $statusType = 'status6';
2035 $labelStatus = $langs->transnoentitiesnoconv('Disabled');
2036 } else {
2037 $statusType = 'status4';
2038 $labelStatus = $langs->transnoentitiesnoconv('Active');
2039 }
2040 } else {
2041 if ($status == self::STATUS_SUSPENDED) {
2042 $statusType = 'status6';
2043 $labelStatus = $langs->transnoentitiesnoconv('Disabled');
2044 } else {
2045 $statusType = 'status0';
2046 $labelStatus = $langs->transnoentitiesnoconv('Draft');
2047 }
2048 }
2049 } elseif ($mode == 5 || $mode == 6) {
2050 $prefix = '';
2051 if ($mode == 5) {
2052 $prefix = 'Short';
2053 }
2054 if ($recur) {
2055 if ($status == self::STATUS_SUSPENDED) {
2056 $statusType = 'status6';
2057 $labelStatus = $langs->transnoentitiesnoconv('Disabled');
2058 } else {
2059 $statusType = 'status4';
2060 $labelStatus = $langs->transnoentitiesnoconv('Active');
2061 }
2062 } else {
2063 if ($status == self::STATUS_SUSPENDED) {
2064 $statusType = 'status6';
2065 $labelStatus = $langs->transnoentitiesnoconv('Disabled');
2066 } else {
2067 $statusType = 'status0';
2068 $labelStatus = $langs->transnoentitiesnoconv('Draft');
2069 }
2070 }
2071 }
2072
2073 $labelStatusShort = $labelStatus;
2074
2075 return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode);
2076 }
2077
2085 public function getNextNumRef($soc, $mode = 'next')
2086 {
2087 // Not used for recurring invoices
2088 return '';
2089 }
2090
2097 public function info($id)
2098 {
2099 $sql = 'SELECT c.rowid, datec, tms as datem,';
2100 $sql .= ' fk_user_author, fk_user_modif';
2101 $sql .= ' FROM '.MAIN_DB_PREFIX.'facture_rec as c';
2102 $sql .= ' WHERE c.rowid = '.((int) $id);
2103
2104 $result = $this->db->query($sql);
2105 if ($result) {
2106 if ($this->db->num_rows($result)) {
2107 $obj = $this->db->fetch_object($result);
2108
2109 $this->id = $obj->rowid;
2110
2111 $this->user_creation_id = $obj->fk_user_author;
2112 $this->user_modification_id = $obj->fk_user_modif;
2113 $this->date_creation = $this->db->jdate($obj->datec);
2114 $this->date_modification = $this->db->jdate($obj->datem);
2115 }
2116 $this->db->free($result);
2117 } else {
2118 dol_print_error($this->db);
2119 }
2120 }
2121
2130 public function initAsSpecimen($option = '')
2131 {
2132 global $langs;
2133
2134 $now = dol_now();
2135 $arraynow = dol_getdate($now);
2136 $nownotime = dol_mktime(0, 0, 0, $arraynow['mon'], $arraynow['mday'], $arraynow['year']);
2137
2138 // Load array of products prodids
2139 $num_prods = 0;
2140 $prodids = array();
2141
2142 $sql = "SELECT rowid";
2143 $sql .= " FROM ".MAIN_DB_PREFIX."product";
2144 $sql .= " WHERE entity IN (".getEntity('product').")";
2145 $sql .= $this->db->plimit(100);
2146
2147 $resql = $this->db->query($sql);
2148 if ($resql) {
2149 $num_prods = $this->db->num_rows($resql);
2150 $i = 0;
2151 while ($i < $num_prods) {
2152 $i++;
2153 $row = $this->db->fetch_row($resql);
2154 $prodids[$i] = $row[0];
2155 }
2156 }
2157
2158 // Initialize parameters
2159 $this->id = 0;
2160 $this->ref = 'SPECIMEN';
2161 $this->title = 'SPECIMEN';
2162 $this->specimen = 1;
2163 $this->socid = 1;
2164 $this->date = $nownotime;
2165 $this->date_lim_reglement = $nownotime + 3600 * 24 * 30;
2166 $this->cond_reglement_id = 1;
2167 $this->cond_reglement_code = 'RECEP';
2168 $this->date_lim_reglement = $this->calculate_date_lim_reglement();
2169 $this->mode_reglement_id = 0; // Not forced to show payment mode CHQ + VIR
2170 $this->mode_reglement_code = ''; // Not forced to show payment mode CHQ + VIR
2171 $this->note_public = 'This is a comment (public)';
2172 $this->note_private = 'This is a comment (private)';
2173 $this->fk_incoterms = 0;
2174 $this->location_incoterms = '';
2175
2176 if (empty($option) || $option != 'nolines') {
2177 // Lines
2178 $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)
2179 $xnbp = 0;
2180 while ($xnbp < $nbp) {
2181 $line = new FactureLigne($this->db);
2182 $line->desc = $langs->trans("Description")." ".$xnbp;
2183 $line->qty = 1;
2184 $line->subprice = 100;
2185 $line->tva_tx = 19.6;
2186 $line->localtax1_tx = 0;
2187 $line->localtax2_tx = 0;
2188 $line->remise_percent = 0;
2189 if ($xnbp == 1) { // Qty is negative (product line)
2190 $prodid = mt_rand(1, $num_prods);
2191 $line->fk_product = $prodids[$prodid];
2192 $line->qty = -1;
2193 $line->total_ht = -100;
2194 $line->total_ttc = -119.6;
2195 $line->total_tva = -19.6;
2196 } elseif ($xnbp == 2) { // UP is negative (free line)
2197 $line->subprice = -100;
2198 $line->total_ht = -100;
2199 $line->total_ttc = -119.6;
2200 $line->total_tva = -19.6;
2201 $line->remise_percent = 0;
2202 } elseif ($xnbp == 3) { // Discount is 50% (product line)
2203 $prodid = mt_rand(1, $num_prods);
2204 $line->fk_product = $prodids[$prodid];
2205 $line->total_ht = 50;
2206 $line->total_ttc = 59.8;
2207 $line->total_tva = 9.8;
2208 $line->remise_percent = 50;
2209 } else { // (product line)
2210 $prodid = mt_rand(1, $num_prods);
2211 $line->fk_product = $prodids[$prodid];
2212 $line->total_ht = 100;
2213 $line->total_ttc = 119.6;
2214 $line->total_tva = 19.6;
2215 $line->remise_percent = 00;
2216 }
2217
2218 $this->lines[$xnbp] = $line;
2219 $xnbp++;
2220
2221 $this->total_ht += $line->total_ht;
2222 $this->total_tva += $line->total_tva;
2223 $this->total_ttc += $line->total_ttc;
2224 }
2225 $this->revenuestamp = 0;
2226
2227 // Add a line "offered"
2228 $line = new FactureLigne($this->db);
2229 $line->desc = $langs->trans("Description")." (offered line)";
2230 $line->qty = 1;
2231 $line->subprice = 100;
2232 $line->tva_tx = 19.6;
2233 $line->localtax1_tx = 0;
2234 $line->localtax2_tx = 0;
2235 $line->remise_percent = 100;
2236 $line->total_ht = 0;
2237 $line->total_ttc = 0; // 90 * 1.196
2238 $line->total_tva = 0;
2239 $prodid = mt_rand(1, $num_prods);
2240 $line->fk_product = $prodids[$prodid];
2241
2242 $this->lines[$xnbp] = $line;
2243 $xnbp++;
2244 }
2245
2246 $this->usenewprice = 0;
2247
2248 return 1;
2249 }
2250
2259 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2260 {
2261 $tables = array(
2262 'facture_rec'
2263 );
2264
2265 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
2266 }
2267
2276 public static function replaceProduct(DoliDB $dbs, $origin_id, $dest_id)
2277 {
2278 $tables = array(
2279 'facturedet_rec'
2280 );
2281
2282 return CommonObject::commonReplaceProduct($dbs, $origin_id, $dest_id, $tables);
2283 }
2284
2293 public function setFrequencyAndUnit($frequency, $unit, $notrigger = 0)
2294 {
2295 global $user;
2296
2297 if (!$this->table_element) {
2298 dol_syslog(get_class($this)."::setFrequencyAndUnit was called on object with property table_element not defined", LOG_ERR);
2299 return -1;
2300 }
2301
2302 if (!empty($frequency) && empty($unit)) {
2303 dol_syslog(get_class($this)."::setFrequencyAndUnit was called on object with params frequency defined but unit not defined", LOG_ERR);
2304 return -2;
2305 }
2306
2307 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2308 $sql .= ' SET frequency = '.($frequency ? $this->db->escape((string) $frequency) : 'null');
2309 if (!empty($unit)) {
2310 $sql .= ', unit_frequency = \''.$this->db->escape($unit).'\'';
2311 }
2312 $sql .= " WHERE rowid = ".((int) $this->id);
2313
2314 dol_syslog(get_class($this)."::setFrequencyAndUnit", LOG_DEBUG);
2315 if ($this->db->query($sql)) {
2316 $this->frequency = $frequency;
2317 if (!empty($unit)) {
2318 $this->unit_frequency = $unit;
2319 }
2320
2321 if (!$notrigger) {
2322 // Call trigger
2323 $result = $this->call_trigger('BILLREC_MODIFY', $user);
2324 if ($result < 0) {
2325 return $result;
2326 }
2327 // End call triggers
2328 }
2329
2330 return 1;
2331 } else {
2332 dol_print_error($this->db);
2333 return -1;
2334 }
2335 }
2336
2345 public function setNextDate($date, $increment_nb_gen_done = 0, $notrigger = 0)
2346 {
2347 global $user;
2348
2349 if (!$this->table_element) {
2350 dol_syslog(get_class($this)."::setNextDate was called on object with property table_element not defined", LOG_ERR);
2351 return -1;
2352 }
2353 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2354 $sql .= " SET date_when = ".($date ? "'".$this->db->idate($date)."'" : "null");
2355 if ($increment_nb_gen_done > 0) {
2356 $sql .= ', nb_gen_done = nb_gen_done + 1';
2357 }
2358 $sql .= " WHERE rowid = ".((int) $this->id);
2359
2360 dol_syslog(get_class($this)."::setNextDate", LOG_DEBUG);
2361 if ($this->db->query($sql)) {
2362 $this->date_when = $date;
2363 if ($increment_nb_gen_done > 0) {
2364 $this->nb_gen_done++;
2365
2366 if (getDolGlobalInt('MAIN_SUSPEND_FACTURE_REC_ON_MAX_GEN_REACHED') && $this->isMaxNbGenReached()) {
2367 $resSuspend = $this->setValueFrom('suspended', 1);
2368
2369 if ($resSuspend <= 0) {
2370 dol_syslog(__METHOD__ . '::setValueFrom Error : ' . $this->error, LOG_ERR);
2371 return -1;
2372 }
2373 }
2374 }
2375
2376 if (!$notrigger) {
2377 if (empty($this->context['actionmsgmore'])) {
2378 $this->context['actionmsgmore'] = 'Trigger called by setNextDate';
2379 }
2380
2381 // Call trigger
2382 $result = $this->call_trigger('BILLREC_MODIFY', $user);
2383 if ($result < 0) {
2384 return $result;
2385 }
2386 // End call triggers
2387 }
2388 return 1;
2389 } else {
2390 dol_print_error($this->db);
2391 return -1;
2392 }
2393 }
2394
2402 public function setMaxPeriod($nb, $notrigger = 0)
2403 {
2404 global $user;
2405
2406 if (!$this->table_element) {
2407 dol_syslog(get_class($this)."::setMaxPeriod was called on object with property table_element not defined", LOG_ERR);
2408 return -1;
2409 }
2410
2411 if (empty($nb)) {
2412 $nb = 0;
2413 }
2414
2415 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2416 $sql .= ' SET nb_gen_max = '.((int) $nb);
2417 $sql .= " WHERE rowid = ".((int) $this->id);
2418
2419 dol_syslog(get_class($this)."::setMaxPeriod", LOG_DEBUG);
2420 if ($this->db->query($sql)) {
2421 $this->nb_gen_max = $nb;
2422
2423 if (!$notrigger) {
2424 // Call trigger
2425 $result = $this->call_trigger('BILLREC_MODIFY', $user);
2426 if ($result < 0) {
2427 return $result;
2428 }
2429 // End call triggers
2430 }
2431
2432 return 1;
2433 } else {
2434 dol_print_error($this->db);
2435 return -1;
2436 }
2437 }
2438
2446 public function setAutoValidate($validate, $notrigger = 0)
2447 {
2448 global $user;
2449
2450 if (!$this->table_element) {
2451 dol_syslog(get_class($this)."::setAutoValidate was called on object with property table_element not defined", LOG_ERR);
2452 return -1;
2453 }
2454
2455 $this->auto_validate = $validate;
2456
2457 $result = $this->update($user);
2458
2459 dol_syslog(get_class($this)."::setAutoValidate", LOG_DEBUG);
2460 if ($result > 0) {
2461 return 1;
2462 } else {
2463 dol_print_error($this->db);
2464 return -1;
2465 }
2466 }
2467
2475 public function setGeneratePdf($validate, $notrigger = 0)
2476 {
2477 global $user;
2478
2479 if (!$this->table_element) {
2480 dol_syslog(get_class($this)."::setGeneratePdf was called on object with property table_element not defined", LOG_ERR);
2481 return -1;
2482 }
2483
2484 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2485 $sql .= ' SET generate_pdf = '.((int) $validate);
2486 $sql .= " WHERE rowid = ".((int) $this->id);
2487
2488 dol_syslog(get_class($this)."::setGeneratePdf", LOG_DEBUG);
2489 if ($this->db->query($sql)) {
2490 $this->generate_pdf = $validate;
2491
2492 if (!$notrigger) {
2493 // Call trigger
2494 $result = $this->call_trigger('BILLREC_MODIFY', $user);
2495 if ($result < 0) {
2496 return $result;
2497 }
2498 // End call triggers
2499 }
2500
2501 return 1;
2502 } else {
2503 dol_print_error($this->db);
2504 return -1;
2505 }
2506 }
2507
2515 public function setModelPdf($model, $notrigger = 0)
2516 {
2517 global $user;
2518 if (!$this->table_element) {
2519 dol_syslog(get_class($this)."::setModelPdf was called on object with property table_element not defined", LOG_ERR);
2520 return -1;
2521 }
2522
2523 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2524 $sql .= " SET modelpdf = '".$this->db->escape($model)."'";
2525 $sql .= " WHERE rowid = ".((int) $this->id);
2526
2527 dol_syslog(get_class($this)."::setModelPdf", LOG_DEBUG);
2528 if ($this->db->query($sql)) {
2529 $this->model_pdf = $model;
2530
2531 if (!$notrigger) {
2532 // Call trigger
2533 $result = $this->call_trigger('BILLREC_MODIFY', $user);
2534 if ($result < 0) {
2535 return $result;
2536 }
2537 // End call triggers
2538 }
2539
2540 return 1;
2541 } else {
2542 dol_print_error($this->db);
2543 return -1;
2544 }
2545 }
2546
2554 public function setMailTemplate($idEmailTemplate, $notrigger = 0): int
2555 {
2556 global $user;
2557 if (!$this->table_element) {
2558 dol_syslog(get_class($this)."::setMailTemplate was called on object with property table_element not defined", LOG_ERR);
2559 return -1;
2560 }
2561
2562 $this->fk_email_template = $idEmailTemplate;
2563 $result = $this->update($user);
2564
2565 dol_syslog(get_class($this)."::setMailTemplate", LOG_DEBUG);
2566 if ($result > 0) {
2567 if (!$notrigger) {
2568 // Call trigger
2569 $result = $this->call_trigger('BILLREC_MODIFY', $user);
2570 if ($result < 0) {
2571 return $result;
2572 }
2573 // End call triggers
2574 }
2575
2576 return 1;
2577 } else {
2578 dol_print_error($this->db);
2579 return -1;
2580 }
2581 }
2582}
2583
2584
2585
2591{
2595 public $element = 'facturedetrec';
2596
2600 public $table_element = 'facturedet_rec';
2601
2605 public $parent_element = 'facturerec';
2606
2610 public $fk_parent_attribute = 'fk_facture';
2611
2613
2616 public $fk_facture;
2620 public $fk_parent_line;
2621
2625 public $fk_product_fournisseur_price;
2630 public $fk_fournprice;
2631
2635 public $rang;
2636 //public $situation_percent; // Not supported on recurring invoice line
2637
2641 public $desc;
2645 public $description;
2646
2651 public $fk_product_type;
2652
2656 public $fk_contract_line;
2657
2661 public $skip_update_total;
2662
2670 public function delete(User $user, $notrigger = 0)
2671 {
2672 $error = 0;
2673
2674 $this->db->begin();
2675
2676 if (!$error) {
2677 if (!$notrigger) {
2678 // Call triggers
2679 $result = $this->call_trigger('LINEBILLREC_DELETE', $user);
2680 if ($result < 0) {
2681 $error++;
2682 } // Do also here what you must do to rollback action if trigger fail
2683 // End call triggers
2684 }
2685 }
2686
2687 if (!$error) {
2688 $result = $this->deleteExtraFields();
2689 if ($result < 0) {
2690 $error++;
2691 }
2692 }
2693
2694 if (!$error) {
2695 $sql = 'DELETE FROM '.MAIN_DB_PREFIX.$this->table_element.' WHERE rowid='.((int) $this->id);
2696
2697 $res = $this->db->query($sql);
2698 if (!$res) {
2699 $error++;
2700 $this->errors[] = $this->db->lasterror();
2701 }
2702 }
2703
2704 // Commit or rollback
2705 if ($error) {
2706 $this->db->rollback();
2707 return -1;
2708 } else {
2709 $this->db->commit();
2710 return 1;
2711 }
2712 }
2713
2714
2721 public function fetch($rowid)
2722 {
2723 $sql = 'SELECT l.rowid, l.fk_facture, l.fk_parent_line, l.fk_product, l.product_type, l.label as custom_label, l.description, l.product_type, l.price, l.qty, l.vat_src_code, l.tva_tx,';
2724 $sql .= ' l.localtax1_tx, l.localtax2_tx, l.localtax1_type, l.localtax2_type, l.remise, l.remise_percent, l.subprice,';
2725 $sql .= ' l.date_start_fill, l.date_end_fill, l.info_bits, l.total_ht, l.total_tva, l.total_ttc,';
2726 $sql .= ' l.rang, l.special_code,';
2727 $sql .= ' l.fk_unit, l.fk_contract_line,';
2728 $sql .= ' l.import_key, l.fk_multicurrency,';
2729 $sql .= ' l.multicurrency_code, l.multicurrency_subprice, l.multicurrency_total_ht, l.multicurrency_total_tva, l.multicurrency_total_ttc,';
2730 $sql .= ' l.buy_price_ht, l.fk_product_fournisseur_price,';
2731 $sql .= ' l.fk_user_author, l.fk_user_modif, l.extraparams,';
2732 $sql .= ' p.ref as product_ref, p.fk_product_type as fk_product_type, p.label as product_label, p.description as product_desc';
2733 $sql .= ' FROM '.MAIN_DB_PREFIX.'facturedet_rec as l';
2734 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON l.fk_product = p.rowid';
2735 $sql .= ' WHERE l.rowid = '.((int) $rowid);
2736 $sql .= ' ORDER BY l.rang';
2737
2738 dol_syslog('FactureRec::fetch', LOG_DEBUG);
2739 $result = $this->db->query($sql);
2740 if ($result) {
2741 $objp = $this->db->fetch_object($result);
2742
2743 $this->id = $objp->rowid;
2744 $this->fk_facture = $objp->fk_facture;
2745 $this->fk_parent_line = $objp->fk_parent_line;
2746 $this->label = $objp->custom_label; // Label line
2747 $this->desc = $objp->description; // Description line
2748 $this->description = $objp->description; // Description line
2749 $this->product_type = $objp->product_type; // Type of line
2750 $this->ref = $objp->product_ref; // Ref product
2751 $this->product_ref = $objp->product_ref; // Ref product
2752 $this->libelle = $objp->product_label; // deprecated
2753 $this->product_label = $objp->product_label; // Label product
2754 $this->product_desc = $objp->product_desc; // Description product
2755 $this->fk_product_type = $objp->fk_product_type; // Type of product
2756 $this->qty = $objp->qty;
2757 $this->price = $objp->price;
2758 $this->subprice = $objp->subprice;
2759 $this->vat_src_code = $objp->vat_src_code;
2760 $this->tva_tx = $objp->tva_tx;
2761 $this->localtax1_tx = $objp->localtax1_tx;
2762 $this->localtax2_tx = $objp->localtax2_tx;
2763 $this->localtax1_type = $objp->localtax1_type;
2764 $this->localtax2_type = $objp->localtax2_type;
2765 $this->remise_percent = $objp->remise_percent;
2766 //$this->fk_remise_except = $objp->fk_remise_except;
2767 $this->fk_product = $objp->fk_product;
2768 $this->date_start_fill = $objp->date_start_fill;
2769 $this->date_end_fill = $objp->date_end_fill;
2770 $this->info_bits = $objp->info_bits;
2771 $this->total_ht = $objp->total_ht;
2772 $this->total_tva = $objp->total_tva;
2773 $this->total_ttc = $objp->total_ttc;
2774
2775 $this->rang = $objp->rang;
2776 $this->special_code = $objp->special_code;
2777 $this->fk_unit = $objp->fk_unit;
2778 $this->fk_contract_line = $objp->fk_contract_line;
2779 $this->import_key = $objp->import_key;
2780 $this->fk_multicurrency = $objp->fk_multicurrency;
2781 $this->multicurrency_code = $objp->multicurrency_code;
2782 $this->multicurrency_subprice = $objp->multicurrency_subprice;
2783 $this->multicurrency_total_ht = $objp->multicurrency_total_ht;
2784 $this->multicurrency_total_tva = $objp->multicurrency_total_tva;
2785 $this->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
2786
2787 $this->buy_price_ht = $objp->buy_price_ht;
2788
2789 $this->fk_product_fournisseur_price = $objp->fk_product_fournisseur_price;
2790 $this->fk_user_author = $objp->fk_user_author;
2791 $this->fk_user_modif = $objp->fk_user_modif;
2792
2793 $this->db->free($result);
2794 return 1;
2795 } else {
2796 $this->error = $this->db->lasterror();
2797 return -3;
2798 }
2799 }
2800
2801
2809 public function update(User $user, $notrigger = 0)
2810 {
2811 $error = 0;
2812
2813 // Clean parameters
2814 if (empty($this->fk_parent_line)) {
2815 $this->fk_parent_line = 0;
2816 }
2817
2818 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2819
2820 $sql = "UPDATE ".MAIN_DB_PREFIX."facturedet_rec SET";
2821 $sql .= " fk_facture = ".((int) $this->fk_facture);
2822 $sql .= ", fk_parent_line=".($this->fk_parent_line > 0 ? ((int) $this->fk_parent_line) : "null");
2823 $sql .= ", label=".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
2824 $sql .= ", description='".$this->db->escape($this->desc)."'";
2825 $sql .= ", price=".price2num($this->price);
2826 $sql .= ", qty=".price2num($this->qty);
2827 $sql .= ", tva_tx=".price2num($this->tva_tx);
2828 $sql .= ", vat_src_code='".$this->db->escape($this->vat_src_code)."'";
2829 $sql .= ", localtax1_tx=".price2num($this->localtax1_tx);
2830 $sql .= ", localtax1_type='".$this->db->escape((string) $this->localtax1_type)."'";
2831 $sql .= ", localtax2_tx=".price2num($this->localtax2_tx);
2832 $sql .= ", localtax2_type='".$this->db->escape((string) $this->localtax2_type)."'";
2833 $sql .= ", fk_product=".($this->fk_product > 0 ? ((int) $this->fk_product) : "null");
2834 $sql .= ", product_type=".((int) $this->product_type);
2835 $sql .= ", remise_percent=".price2num($this->remise_percent);
2836 $sql .= ", subprice=".price2num($this->subprice);
2837 $sql .= ", info_bits=".price2num($this->info_bits);
2838 $sql .= ", date_start_fill=".(int) $this->date_start_fill;
2839 $sql .= ", date_end_fill=".(int) $this->date_end_fill;
2840 if (empty($this->skip_update_total)) {
2841 $sql .= ", total_ht=".price2num($this->total_ht);
2842 $sql .= ", total_tva=".price2num($this->total_tva);
2843 $sql .= ", total_localtax1=".price2num($this->total_localtax1);
2844 $sql .= ", total_localtax2=".price2num($this->total_localtax2);
2845 $sql .= ", total_ttc=".price2num($this->total_ttc);
2846 }
2847 $sql .= ", rang=".((int) $this->rang);
2848 $sql .= ", special_code=".((int) $this->special_code);
2849 $sql .= ", fk_unit=".($this->fk_unit ? "'".$this->db->escape((string) $this->fk_unit)."'" : "null");
2850 $sql .= ", fk_contract_line=".($this->fk_contract_line ? ((int) $this->fk_contract_line) : "null");
2851 $sql .= " WHERE rowid = ".((int) $this->id);
2852
2853 $this->db->begin();
2854
2855 dol_syslog(get_class($this)."::updateline", LOG_DEBUG);
2856
2857 $resql = $this->db->query($sql);
2858 if ($resql) {
2859 if (!$error) {
2860 $result = $this->insertExtraFields();
2861 if ($result < 0) {
2862 $error++;
2863 }
2864 }
2865
2866 if (!$error && !$notrigger) {
2867 // Call trigger
2868 $result = $this->call_trigger('LINEBILLREC_MODIFY', $user);
2869 if ($result < 0) {
2870 $error++;
2871 }
2872 // End call triggers
2873 }
2874
2875 if ($error) {
2876 $this->db->rollback();
2877 return -2;
2878 } else {
2879 $this->db->commit();
2880 return 1;
2881 }
2882 } else {
2883 $this->error = $this->db->lasterror();
2884 $this->db->rollback();
2885 return -2;
2886 }
2887 }
2888}
$propal type
'integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]',...
Definition propal.php:280
$object ref
Definition info.php:90
Class to manage agenda events (actions)
Class to send emails (with attachments or not) Usage: $mailfile = new CMailFile($subject,...
Superclass for invoice classes.
const TYPE_STANDARD
Standard invoice.
calculate_date_lim_reglement($cond_reglement=0)
Returns an invoice payment deadline based on the invoice settlement conditions and billing date.
const STATUS_DRAFT
Draft status.
Parent class of all other business classes for details of elements (invoices, contracts,...
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...
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.
deleteObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $rowid=0, $f_user=null, $notrigger=0)
Delete all links between an object $this.
setErrorsFromObject($object)
setErrorsFromObject
isLineOfObject($lineid)
Check that a line belongs to this object, using $this->table_element_line and $this->fk_element.
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.
setValueFrom($field, $value, $table='', $id=null, $format='', $id_field='', $fuser=null, $trigkey='', $fk_user_field='fk_user_modif')
Setter generic.
static commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a product id with another one.
line_max($fk_parent_line=0)
Get max value used for position of line (rang)
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
Class to manage Dolibarr database access.
Class to manage invoices.
const STATUS_VALIDATED
Validated (need to be paid)
Class to manage invoice lines.
Class to manage invoice lines of templates.
fetch($rowid)
Get line of template invoice.
update(User $user, $notrigger=0)
Update a line to invoice_rec.
Class to manage invoice templates.
setFrequencyAndUnit($frequency, $unit, $notrigger=0)
Update frequency and unit.
getLibStatut($mode=0, $alreadypaid=-1)
Return label of object status.
updateline($rowid, $desc, $pu_ht, $qty, $txtva, $txlocaltax1=0, $txlocaltax2=0, $fk_product=0, $remise_percent=0, $price_base_type='HT', $info_bits=0, $fk_remise_except=0, $pu_ttc=0, $type=0, $rang=-1, $special_code=0, $label='', $fk_unit=null, $pu_ht_devise=0, $notrigger=0, $date_start_fill=0, $date_end_fill=0, $fk_fournprice=null, $pa_ht=0, $fk_parent_line=0)
Update a line to invoice.
LibStatut($recur, $status, $mode=0, $alreadypaid=-1, $type=0, $nbofopendirectdebitorcredittransfer=0)
Return label of a status.
fetch_lines()
Get lines of template invoices into this->lines.
info($id)
Load miscellaneous information for tab "Info".
getLinesArray()
Create an array of invoice lines.
static replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
getNomUrl($withpicto=0, $option='', $max=0, $short=0, $moretitle='', $notooltip=0, $save_lastsearch_value=-1)
Return clickable name (with picto eventually)
isMaxNbGenReached()
Return if maximum number of generation is reached.
createRecurringInvoices($restrictioninvoiceid=0, $forcevalidation=0, $notrigger=0, $forcebuilddoc=0)
Create all recurrents invoices (for all entities if multicompany is used).
setModelPdf($model, $notrigger=0)
Update the model for documents.
setMailTemplate($idEmailTemplate, $notrigger=0)
Update the email template.
getNextNumRef($soc, $mode='next')
Return next reference of invoice not already used (or last reference)
__construct(DoliDB $db)
Constructor.
addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1=0, $txlocaltax2=0, $fk_product=0, $remise_percent=0, $price_base_type='HT', $info_bits=0, $fk_remise_except=0, $pu_ttc=0, $type=0, $rang=-1, $special_code=0, $label='', $fk_unit=null, $pu_ht_devise=0, $date_start_fill=0, $date_end_fill=0, $fk_fournprice=null, $pa_ht=0, $fk_parent_line=0, $array_options=array())
Add a line to invoice.
setNextDate($date, $increment_nb_gen_done=0, $notrigger=0)
Update the next date of execution.
setAutoValidate($validate, $notrigger=0)
Update the auto validate flag of invoice.
initAsSpecimen($option='')
Initialise an instance with random values.
setMaxPeriod($nb, $notrigger=0)
Update the maximum period.
update(User $user, $notrigger=0)
Update a line invoice_rec.
create($user, $facid, $notrigger=0, $onlylines=array())
Create a predefined invoice.
strikeIfMaxNbGenReached($ret)
Format string to output with by striking the string if max number of generations was reached.
fetch($rowid, $ref='', $ref_ext='', $noextrafields=0, $nolines=0)
Load object and lines.
setGeneratePdf($validate, $notrigger=0)
Update the auto generate documents.
static replaceProduct(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a product id with another one.
getNextDate()
Return the next date of execution Handles end-of-month scenarios when day >= 28 for monthly recurring...
Class to manage a HTML form to send a unitary email Usage: $formail = new FormMail($db) $formmail->pr...
Class to manage products or services.
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:169
global $mysoc
dol_time_plus_duree($time, $duration_value, $duration_unit, $ruleforendofmonth=0)
Add a delay to a date.
Definition date.lib.php:126
print $script_file $mode $langs defaultlang(is_numeric($duration_value) ? " delay=". $duration_value :"").(is_numeric($duration_value2) ? " after cd cd cd description as description
Only used if Module[ID]Desc translation string is not found.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
dol_now($mode='gmt')
Return date for now.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
dol_mimetype($file, $default='application/octet-stream', $mode=0)
Return MIME type of a file from its name with extension.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid=0)
Get type and rate of localtaxes for a particular vat rate/country of a thirdparty.
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...
GETPOSTINT($paramname, $method=0, $nodefault=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
dol_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.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
dol_getdate($timestamp, $fast=false, $forcetimezone='')
Return an array with locale date info.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Definition html.lib.php:181
getMarginInfos($pv_ht, $remise_percent, $tva_tx, $localtax1_tx, $localtax2_tx, $fk_pa, $pa_ht)
Return an array with margins information of a line.
calcul_price_total($qty, $pu, $remise_percent_ligne, $txtva, $uselocaltax1_rate, $uselocaltax2_rate, $notused, $price_base_type, $info_bits, $type, $seller=null, $localtaxes_array=[], $progress=100, $multicurrency_tx=1, $pu_devise=0, $multicurrency_code='')
Calculate totals (net, vat, ...) of a line.
Definition price.lib.php:90
print $langs trans('Date')." left Ref Label right Qty right Price right TotalHT right TotalTTC right right right right right right right right right centpercent right TotalHT right n right VAT right n right TotalVAT right n No sujeto a RE IRPF right TotalLT1 right n right TotalLT2 right n right TotalTTC right n takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency right TotalTTC takeposcustomercurrency right takeposcustomercurrency n right Paid right PaymentTypeShortLIQ right SELECT p pos_change as p datep as date
Definition receipt.php:492