dolibarr 25.0.0-alpha
supplier_proposal.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2002-2004 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2004 Eric Seigne <eric.seigne@ryxeo.com>
4 * Copyright (C) 2004-2011 Laurent Destailleur <eldy@users.sourceforge.net>
5 * Copyright (C) 2005 Marc Barilley <marc@ocebo.com>
6 * Copyright (C) 2005-2013 Regis Houssin <regis.houssin@inodbox.com>
7 * Copyright (C) 2006 Andre Cianfarani <acianfa@free.fr>
8 * Copyright (C) 2008 Raphael Bertrand <raphael.bertrand@resultic.fr>
9 * Copyright (C) 2010-2020 Juanjo Menent <jmenent@2byte.es>
10 * Copyright (C) 2010-2018 Philippe Grand <philippe.grand@atoo-net.com>
11 * Copyright (C) 2012-2014 Christophe Battarel <christophe.battarel@altairis.fr>
12 * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
13 * Copyright (C) 2014 Marcos García <marcosgdf@gmail.com>
14 * Copyright (C) 2016 Ferran Marcet <fmarcet@2byte.es>
15 * Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
16 * Copyright (C) 2019-2026 Frédéric France <frederic.france@free.fr>
17 * Copyright (C) 2020 Tobias Sekan <tobias.sekan@startmail.com>
18 * Copyright (C) 2022 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
19 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
20 * Copyright (C) 2026 Vincent de Grandpré <vincent@de-grandpre.quebec>
21 * Copyright (C) 2026 Lionel Vessiller <lvessiller@open-dsi.fr>
22 * Copyright (C) 2026 Jose Martinez <jose.martinez@pichinov.com>
23 * Copyright (C) 2026 Nick Fragoulis
24 *
25 * This program is free software; you can redistribute it and/or modify
26 * it under the terms of the GNU General Public License as published by
27 * the Free Software Foundation; either version 3 of the License, or
28 * (at your option) any later version.
29 *
30 * This program is distributed in the hope that it will be useful,
31 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 * GNU General Public License for more details.
34 *
35 * You should have received a copy of the GNU General Public License
36 * along with this program. If not, see <https://www.gnu.org/licenses/>.
37 */
38
44require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
45require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
46require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
47require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
48require_once DOL_DOCUMENT_ROOT.'/margin/lib/margins.lib.php';
49require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php';
50require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php';
51require_once DOL_DOCUMENT_ROOT.'/subtotals/class/commonsubtotal.class.php';
56{
57 use CommonIncoterm;
58 use CommonSubtotal;
59
63 public $element = 'supplier_proposal';
64
68 public $table_element = 'supplier_proposal';
69
73 public $table_element_line = 'supplier_proposaldet';
74
78 public $class_element_line = 'SupplierProposalLine';
82 public $fk_element = 'fk_supplier_proposal';
83
87 public $picto = 'supplier_proposal';
88
93 public $restrictiononfksoc = 1;
94
98 protected $table_ref_field = 'ref';
99
103 public $socid; // Id client
104
110 public $author;
111
115 public $ref_fourn;
119 public $ref_supplier;
120
125 public $statut; // 0 (draft), 1 (validated), 2 (signed), 3 (not signed), 4 (processed/billed)
126
130 public $date;
131
135 public $delivery_date;
136
142 public $datec;
143
149 public $datev;
150
154 public $user_author_id;
155
161 public $price;
162
168 public $tva;
169
175 public $total;
176
180 public $cond_reglement_code;
184 public $cond_reglement_doc; // label doc
185
189 public $mode_reglement_code;
194 public $mode_reglement;
195
201 public $deposit_percent;
202
203
207 public $extraparams = array();
208
212 public $lines = array();
213
217 public $line;
218
219 public $labelStatus = array();
220 public $labelStatusShort = array();
221
225 public $nbtodo;
229 public $nbtodolate;
230
231 // Multicurrency
235 public $fk_multicurrency;
236
240 public $multicurrency_code;
244 public $multicurrency_tx;
248 public $multicurrency_total_ht;
252 public $multicurrency_total_tva;
256 public $multicurrency_total_ttc;
257
258 public $fields = array(
259 'ref' => array('type' => 'varchar(255)', 'label' => 'Ref', 'enabled' => 1, 'visible' => 1, 'showoncombobox' => 1, 'position' => 25, 'searchall' => 1),
260 'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 750, 'searchall' => 1),
261 );
262
266 const STATUS_DRAFT = 0;
267
272
276 const STATUS_SIGNED = 2;
277
282
286 const STATUS_CLOSE = 4;
287
288
289
297 public function __construct($db, $socid = 0, $supplier_proposalid = 0)
298 {
299 $this->db = $db;
300
301 $this->ismultientitymanaged = 1;
302 $this->socid = $socid;
303 $this->id = $supplier_proposalid;
304 }
305
306
307 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
318 public function add_product($idproduct, $qty, $remise_percent = 0)
319 {
320 // phpcs:enable
321 global $mysoc;
322
323 if (!$qty) {
324 $qty = 1;
325 }
326
327 dol_syslog(get_class($this)."::add_product $idproduct, $qty, $remise_percent");
328 if ($idproduct > 0) {
329 $prod = new Product($this->db);
330 $prod->fetch($idproduct);
331
332 $productdesc = $prod->description;
333
334 $tva_tx = get_default_tva($mysoc, $this->thirdparty, $prod->id);
335 $tva_npr = get_default_npr($mysoc, $this->thirdparty, $prod->id);
336 if (empty($tva_tx)) {
337 $tva_npr = 0;
338 }
339 $localtax1_tx = get_localtax($tva_tx, 1, $mysoc, $this->thirdparty, $tva_npr);
340 $localtax2_tx = get_localtax($tva_tx, 2, $mysoc, $this->thirdparty, $tva_npr);
341
342 // multiprix
343 if (getDolGlobalString('PRODUIT_MULTIPRICES') && $this->thirdparty->price_level) {
344 $price = $prod->multiprices[$this->thirdparty->price_level];
345 } else {
346 $price = $prod->price;
347 }
348
349 $line = new SupplierProposalLine($this->db);
350
351 $line->fk_product = $idproduct;
352 $line->desc = $productdesc;
353 $line->qty = $qty;
354 $line->subprice = $price;
355 $line->remise_percent = $remise_percent;
356 $line->tva_tx = $tva_tx;
357
358 $this->lines[] = $line;
359 return 1;
360 }
361 return -1;
362 }
363
364 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
371 public function insert_discount($idremise)
372 {
373 // phpcs:enable
374 global $langs;
375
376 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
377 include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
378
379 $this->db->begin();
380
381 $remise = new DiscountAbsolute($this->db);
382 $result = $remise->fetch($idremise);
383
384 if ($result > 0) {
385 if ($remise->fk_facture) { // Protection against multiple submission
386 $this->error = $langs->trans("ErrorDiscountAlreadyUsed");
387 $this->db->rollback();
388 return -5;
389 }
390
391 $supplier_proposalligne = new SupplierProposalLine($this->db);
392 $supplier_proposalligne->fk_supplier_proposal = $this->id;
393 $supplier_proposalligne->fk_remise_except = $remise->id;
394 $supplier_proposalligne->desc = $remise->description; // Description of the proposal line
395 $supplier_proposalligne->tva_tx = $remise->tva_tx;
396 $supplier_proposalligne->localtax1_tx = $remise->localtax1_tx;
397 $supplier_proposalligne->localtax1_type = $remise->localtax1_type;
398 $supplier_proposalligne->localtax2_tx = $remise->localtax1_tx;
399 $supplier_proposalligne->localtax2_type = $remise->localtax1_type;
400 $supplier_proposalligne->subprice = -(float) $remise->amount_ht;
401 $supplier_proposalligne->fk_product = 0; // Predefined Product ID
402 $supplier_proposalligne->qty = 1;
403 $supplier_proposalligne->remise_percent = 0;
404 $supplier_proposalligne->rang = -1;
405 $supplier_proposalligne->info_bits = 2;
406
407 $supplier_proposalligne->total_ht = -(float) $remise->amount_ht;
408 $supplier_proposalligne->total_tva = -(float) $remise->amount_tva;
409 $supplier_proposalligne->total_ttc = -(float) $remise->amount_ttc;
410 $supplier_proposalligne->total_localtax1 = -(float) $remise->total_localtax1;
411 $supplier_proposalligne->total_localtax2 = -(float) $remise->total_localtax2;
412
413 $result = $supplier_proposalligne->insert();
414 if ($result > 0) {
415 $result = $this->update_price(1);
416 if ($result > 0) {
417 $this->db->commit();
418 return 1;
419 } else {
420 $this->db->rollback();
421 return -1;
422 }
423 } else {
424 $this->setErrorsFromObject($supplier_proposalligne);
425 $this->db->rollback();
426 return -2;
427 }
428 } else {
429 $this->db->rollback();
430 return -2;
431 }
432 }
433
470 public function addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1 = 0, $txlocaltax2 = 0, $fk_product = 0, $remise_percent = 0, $price_base_type = 'HT', $pu_ttc = 0, $info_bits = 0, $type = 0, $rang = -1, $special_code = 0, $fk_parent_line = 0, $fk_fournprice = 0, $pa_ht = 0, $label = '', $array_options = [], $ref_supplier = '', $fk_unit = 0, $origin = '', $origin_id = 0, $pu_ht_devise = 0, $date_start = 0, $date_end = 0)
471 {
472 global $mysoc, $langs;
473
474 dol_syslog(get_class($this)."::addline supplier_proposalid=$this->id, desc=$desc, pu_ht=$pu_ht, qty=$qty, txtva=$txtva, fk_product=$fk_product, remise_except=$remise_percent, price_base_type=$price_base_type, pu_ttc=$pu_ttc, info_bits=$info_bits, type=$type");
475 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
476
477 // Clean parameters
478 if (empty($remise_percent)) {
479 $remise_percent = 0;
480 }
481 if (empty($qty)) {
482 $qty = 0;
483 }
484 if (empty($info_bits)) {
485 $info_bits = 0;
486 }
487 if (empty($rang)) {
488 $rang = 0;
489 }
490 if (empty($fk_parent_line) || $fk_parent_line < 0) {
491 $fk_parent_line = 0;
492 }
493 if (empty($pu_ht)) {
494 $pu_ht = 0;
495 }
496
497 $remise_percent = price2num($remise_percent);
498 $qty = (float) price2num($qty);
499 $pu_ht = price2num($pu_ht);
500 $pu_ttc = price2num($pu_ttc);
501 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
502 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
503 }
504 $txlocaltax1 = price2num($txlocaltax1);
505 $txlocaltax2 = price2num($txlocaltax2);
506 $pa_ht = price2num($pa_ht);
507 if ($price_base_type == 'HT') {
508 $pu = $pu_ht;
509 } else {
510 $pu = $pu_ttc;
511 }
512
513 // Check parameters
514 if ($type < 0) {
515 return -1;
516 }
517
518 if ($this->status == self::STATUS_DRAFT) {
519 $this->db->begin();
520
521 if ($fk_product > 0) {
522 if (getDolGlobalInt('SUPPLIER_PROPOSAL_WITH_PREDEFINED_PRICES_ONLY') == 1) { // Not the common case
523 // Check quantity is enough
524 dol_syslog(get_class($this)."::addline we check supplier prices fk_product=".$fk_product." fk_fournprice=".$fk_fournprice." qty=".$qty." ref_supplier=".$ref_supplier);
525 $productsupplier = new ProductFournisseur($this->db);
526 if ($productsupplier->fetch($fk_product) > 0) {
527 $product_type = $productsupplier->type;
528 $label = $productsupplier->label;
529 $fk_prod_fourn_price = $fk_fournprice;
530
531 // We use 'none' instead of $ref_supplier, because fourn_ref may not exists anymore. So we will take the first supplier price ok.
532 // If we want a dedicated supplier price, we must provide $fk_prod_fourn_price.
533 // @phan-suppress-next-line PhanPluginSuspiciousParamOrder
534 $result = $productsupplier->get_buyprice($fk_prod_fourn_price, $qty, $fk_product, 'none', $this->socid); // Search on couple $fk_prod_fourn_price/$qty first, then on triplet $qty/$fk_product/$ref_supplier/$this->socid
535 if ($result > 0) {
536 $pu = $productsupplier->fourn_pu; // Unit price supplier price set by get_buyprice
537 $ref_supplier = $productsupplier->ref_supplier; // Ref supplier price set by get_buyprice
538 // is remise percent not keyed but present for the product we add it
539 if ($remise_percent == 0 && $productsupplier->remise_percent != 0) {
540 $remise_percent = $productsupplier->remise_percent;
541 }
542 }
543 if ($result == 0) { // If result == 0, we failed to found the supplier reference price
544 $langs->load("errors");
545 $this->error = "Ref ".$productsupplier->ref." ".$langs->trans("ErrorQtyTooLowForThisSupplier");
546 $this->db->rollback();
547 dol_syslog(get_class($this)."::addline we did not found supplier price, so we can't guess unit price");
548 //$pu = $productsupplier->fourn_pu; // We do not overwrite unit price
549 //$ref = $productsupplier_fourn; // We do not overwrite ref supplier price
550 return -1;
551 }
552 if ($result == -1) {
553 $langs->load("errors");
554 $this->error = "Ref ".$productsupplier->ref." ".$langs->trans("ErrorQtyTooLowForThisSupplier");
555 $this->db->rollback();
556 dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_DEBUG);
557 return -1;
558 }
559 if ($result < -1) {
560 $this->setErrorsFromObject($productsupplier);
561 $this->db->rollback();
562 dol_syslog(get_class($this)."::addline result=".$result." - ".$this->error, LOG_ERR);
563 return -1;
564 }
565 } else {
566 $this->setErrorsFromObject($productsupplier);
567 $this->db->rollback();
568 return -1;
569 }
570 }
571
572 // Predefine quantity according to packaging (same rule as for supplier orders)
573 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
574 $prod = new Product($this->db);
575 // @phan-suppress-next-line PhanPluginSuspiciousParamOrder
576 $prod->get_buyprice(($fk_fournprice > 0 ? (int) $fk_fournprice : 0), (float) $qty, $fk_product, 'none', $this->socid);
577
578 $newqty = $this->roundQtyToPackaging($qty, $prod->packaging);
579 if ($newqty != $qty) {
580 $qty = $newqty;
581 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
582 }
583
584 // Enforce the supplier minimum purchase quantity on top of packaging rounding: if the line is below
585 // the supplier minimum, round the minimum itself up to the next packaging multiple.
586 if (!empty($prod->fourn_qty) && (float) $qty > 0 && (float) $qty < (float) $prod->fourn_qty) { // A negative quantity (return) is not concerned by the minimum purchase quantity
587 if (!empty($prod->packaging) && (float) price2num(fmod((float) $prod->fourn_qty, (float) $prod->packaging), 'MS')) {
588 $coeff = intval((float) $prod->fourn_qty / (float) $prod->packaging) + 1;
589 $qty = (float) price2num((float) $prod->packaging * $coeff, 'MS');
590 } else {
591 $qty = (float) $prod->fourn_qty;
592 }
593 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'mesgs');
594 }
595 }
596 } else {
597 $product_type = $type;
598 }
599
600 // Calculation of the gross total (TTC) and VAT for the line from qty, pu, remise_percent and txtva
601 // VERY IMPORTANT: It's at the time of line insertion that we must store the net, VAT, and gross amounts,
602 // and this is done at the line level, which has its own VAT rate
603
604 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
605
606 // Clean vat code
607 $reg = array();
608 $vat_src_code = '';
609 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
610 $vat_src_code = $reg[1];
611 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
612 }
613
614 if (isModEnabled("multicurrency") && $pu_ht_devise > 0) {
615 $pu = 0;
616 }
617
618 $tabprice = calcul_price_total(
619 $qty,
620 $pu,
621 (float) $remise_percent,
622 $txtva,
623 (float) $txlocaltax1,
624 (float) $txlocaltax2,
625 0,
626 $price_base_type,
627 $info_bits,
628 $type,
629 $this->thirdparty,
630 $localtaxes_type,
631 100,
632 (float) $this->multicurrency_tx,
633 $pu_ht_devise
634 );
635 $total_ht = $tabprice[0];
636 $total_tva = $tabprice[1];
637 $total_ttc = $tabprice[2];
638 $total_localtax1 = $tabprice[9];
639 $total_localtax2 = $tabprice[10];
640 $pu = $pu_ht = $tabprice[3];
641
642 // MultiCurrency
643 $multicurrency_total_ht = $tabprice[16];
644 $multicurrency_total_tva = $tabprice[17];
645 $multicurrency_total_ttc = $tabprice[18];
646 $pu_ht_devise = $tabprice[19];
647
648 // Rang to use
649 $ranktouse = $rang;
650 if ($ranktouse == -1) {
651 $rangmax = $this->line_max($fk_parent_line);
652 $ranktouse = $rangmax + 1;
653 }
654
655 // Insert line
656 $this->line = new SupplierProposalLine($this->db);
657
658 $this->line->fk_supplier_proposal = $this->id;
659 $this->line->label = $label;
660 $this->line->desc = $desc;
661 $this->line->qty = $qty;
662
663 $this->line->vat_src_code = $vat_src_code;
664 $this->line->tva_tx = $txtva;
665 $this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
666 $this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
667 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
668 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
669 $this->line->fk_product = $fk_product;
670 $this->line->remise_percent = $remise_percent;
671 $this->line->subprice = (float) $pu_ht;
672 // Persist the original entry mode of the line so updateline() can preserve it later.
673 $this->line->subprice_ttc = ($price_base_type === 'TTC') ? (float) $pu_ttc : 0;
674 $this->line->rang = $ranktouse;
675 $this->line->info_bits = $info_bits;
676 $this->line->total_ht = (float) $total_ht;
677 $this->line->total_tva = (float) $total_tva;
678 $this->line->total_localtax1 = (float) $total_localtax1;
679 $this->line->total_localtax2 = (float) $total_localtax2;
680 $this->line->total_ttc = (float) $total_ttc;
681 $this->line->product_type = $type;
682 $this->line->special_code = $special_code;
683 $this->line->fk_parent_line = $fk_parent_line;
684 $this->line->fk_unit = $fk_unit;
685 $this->line->origin = $origin;
686 $this->line->origin_type = $origin;
687 $this->line->origin_id = $origin_id;
688 $this->line->ref_fourn = $this->db->escape($ref_supplier);
689 $this->line->date_start = $date_start;
690 $this->line->date_end = $date_end;
691
692 // infos merge
693 if (!empty($fk_product) && $fk_product > 0 && empty($fk_fournprice) && empty($pa_ht)) {
694 // When fk_fournprice is 0, we take the lowest buying price
695 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
696 $productFournisseur = new ProductFournisseur($this->db);
697 $productFournisseur->find_min_price_product_fournisseur($fk_product);
698 $this->line->fk_fournprice = $productFournisseur->product_fourn_price_id;
699 } else {
700 $this->line->fk_fournprice = ($fk_fournprice > 0 ? $fk_fournprice : 0); // If fk_fournprice is -1, we will not use fk_fournprice
701 }
702 $this->line->pa_ht = $pa_ht;
703 //var_dump($this->line->fk_fournprice);exit;
704
705 // Multicurrency
706 $this->line->fk_multicurrency = $this->fk_multicurrency;
707 $this->line->multicurrency_code = $this->multicurrency_code;
708 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
709 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
710 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
711 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
712
713 // Set the proposal line as optional
714 if (empty($qty) && empty($special_code)) {
715 $this->line->special_code = 3;
716 }
717
718 if (is_array($array_options) && count($array_options) > 0) {
719 $this->line->array_options = $array_options;
720 }
721
722 $result = $this->line->insert();
723 if ($result > 0) {
724 // Reorder if child line
725 if (!empty($fk_parent_line)) {
726 $this->line_order(true, 'DESC');
727 } elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) { // Update all rank of all other lines
728 $linecount = count($this->lines);
729 for ($ii = $ranktouse; $ii <= $linecount; $ii++) {
730 $this->updateRangOfLine($this->lines[$ii - 1]->id, $ii + 1);
731 }
732 }
733
734 // Update denormalized information at the present proposal level
735 $result = $this->update_price(1, 'auto', 0, $this->thirdparty); // This method is designed to add line from user input so total calculation must be done using 'auto' mode.
736 if ($result > 0) {
737 $this->db->commit();
738 return $this->line->id;
739 } else {
740 $this->db->rollback();
741 return -1;
742 }
743 } else {
744 $this->setErrorsFromObject($this->line);
745 $this->db->rollback();
746 return -2;
747 }
748 } else {
749 $this->error = 'BadStatusOfObjectToAddLine';
750 return -5;
751 }
752 }
753
754
781 public function updateline($rowid, $pu, $qty, $remise_percent, $txtva, $txlocaltax1 = 0, $txlocaltax2 = 0, $desc = '', $price_base_type = 'HT', $info_bits = 0, $special_code = 0, $fk_parent_line = 0, $skip_update_total = 0, $fk_fournprice = 0, $pa_ht = 0, $label = '', $type = 0, $array_options = [], $ref_supplier = '', $fk_unit = 0, $pu_ht_devise = 0)
782 {
783 global $mysoc, $langs;
784
785 if (!$this->isLineOfObject($rowid)) {
786 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
787 return -1;
788 }
789
790 dol_syslog(get_class($this)."::updateLine $rowid, $pu, $qty, $remise_percent, $txtva, $desc, $price_base_type, $info_bits");
791 include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
792
793 // Clean parameters
794 $remise_percent = price2num($remise_percent);
795 $qty = (float) price2num($qty);
796 $pu = price2num($pu);
797 if (!preg_match('/\‍((.*)\‍)/', (string) $txtva)) {
798 $txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
799 }
800 $txlocaltax1 = price2num($txlocaltax1);
801 $txlocaltax2 = price2num($txlocaltax2);
802 $pa_ht = price2num($pa_ht);
803 if (empty($qty) && empty($special_code)) {
804 $special_code = 3; // Set option tag
805 }
806 if (!empty($qty) && $special_code == 3) {
807 $special_code = 0; // Remove option tag
808 }
809
810 if ($this->status == 0) {
811 $this->db->begin();
812
813 // Calculation of the gross total (TTC) and VAT for the line from qty, pu, remise_percent and txtva
814 // VERY IMPORTANT: It's at the time of line insertion that we must store the net, VAT, and gross amounts,
815 // and this is done at the line level, which has its own VAT rate
816
817 $localtaxes_type = getLocalTaxesFromRate($txtva, 0, $mysoc, $this->thirdparty);
818
819 // Clean vat code
820 $reg = array();
821 $vat_src_code = '';
822 if (preg_match('/\‍((.*)\‍)/', $txtva, $reg)) {
823 $vat_src_code = $reg[1];
824 $txtva = preg_replace('/\s*\‍(.*\‍)/', '', $txtva); // Remove code into vatrate.
825 }
826
827 if (isModEnabled("multicurrency") && $pu_ht_devise > 0) {
828 $pu = 0;
829 }
830
831 // Round the quantity to the packaging before computing the amounts of the line,
832 // else the line is saved with the rounded quantity but with the amounts of the quantity before rounding
833 if (getDolGlobalString('PRODUCT_USE_SUPPLIER_PACKAGING')) {
834 $tmpline = new SupplierProposalLine($this->db);
835 if ($tmpline->fetch($rowid) > 0 && $tmpline->fk_product > 0) {
836 $prod = new Product($this->db);
837 // @phan-suppress-next-line PhanPluginSuspiciousParamOrder
838 $prod->get_buyprice(($fk_fournprice > 0 ? (int) $fk_fournprice : (int) $tmpline->fk_fournprice), (float) $qty, $tmpline->fk_product, 'none', $this->socid);
839
840 $newqty = $this->roundQtyToPackaging($qty, $prod->packaging);
841 if ($newqty != $qty) {
842 $qty = $newqty;
843 setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
844 }
845 }
846 }
847
848 $tabprice = calcul_price_total(
849 $qty,
850 $pu,
851 (float) $remise_percent,
852 $txtva,
853 (float) $txlocaltax1,
854 (float) $txlocaltax2,
855 0,
856 $price_base_type,
857 $info_bits,
858 $type,
859 $this->thirdparty,
860 $localtaxes_type,
861 100,
862 (float) $this->multicurrency_tx,
863 $pu_ht_devise
864 );
865 $total_ht = $tabprice[0];
866 $total_tva = $tabprice[1];
867 $total_ttc = $tabprice[2];
868 $total_localtax1 = $tabprice[9];
869 $total_localtax2 = $tabprice[10];
870 $pu_ht = $tabprice[3];
871 $pu_tva = $tabprice[4];
872 $pu_ttc = $tabprice[5];
873
874 // MultiCurrency
875 $multicurrency_total_ht = $tabprice[16];
876 $multicurrency_total_tva = $tabprice[17];
877 $multicurrency_total_ttc = $tabprice[18];
878 $pu_ht_devise = $tabprice[19];
879
880 // Fetch current line from the database and then clone the object and set it in $oldline property
881 $line = new SupplierProposalLine($this->db);
882 $line->fetch($rowid);
883 $line->fetch_optionals();
884
885 $fk_product = $line->fk_product;
886
887 // Stock previous line records
888 $staticline = clone $line;
889
890 $line->oldline = $staticline;
891 $this->line = $line;
892 $this->line->context = $this->context;
893
894 // Reorder if fk_parent_line change
895 if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) {
896 $rangmax = $this->line_max($fk_parent_line);
897 $this->line->rang = $rangmax + 1;
898 }
899
900 $this->line->id = $rowid;
901 $this->line->label = $label;
902 $this->line->desc = $desc;
903 $this->line->qty = $qty;
904 $this->line->product_type = $type;
905
906 $this->line->vat_src_code = $vat_src_code;
907 $this->line->tva_tx = $txtva;
908 $this->line->localtax1_tx = $txlocaltax1;
909 $this->line->localtax2_tx = $txlocaltax2;
910 $this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
911 $this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
912 $this->line->remise_percent = $remise_percent;
913 $this->line->subprice = (float) $pu_ht;
914 // Persist the original entry mode of the line so a no-op edit can preserve it later.
915 $this->line->subprice_ttc = ($price_base_type === 'TTC') ? (float) $pu_ttc : 0;
916 $this->line->info_bits = $info_bits;
917 $this->line->total_ht = (float) $total_ht;
918 $this->line->total_tva = (float) $total_tva;
919 $this->line->total_localtax1 = (float) $total_localtax1;
920 $this->line->total_localtax2 = (float) $total_localtax2;
921 $this->line->total_ttc = (float) $total_ttc;
922 $this->line->special_code = $special_code;
923 $this->line->fk_parent_line = $fk_parent_line;
924 $this->line->skip_update_total = $skip_update_total;
925 $this->line->ref_fourn = $ref_supplier;
926 $this->line->fk_unit = $fk_unit;
927
928 // infos merge
929 if (!empty($fk_product) && $fk_product > 0 && empty($fk_fournprice) && empty($pa_ht)) {
930 // by external module, take lowest buying price
931 include_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
932 $productFournisseur = new ProductFournisseur($this->db);
933 $productFournisseur->find_min_price_product_fournisseur($fk_product);
934 $this->line->fk_fournprice = $productFournisseur->product_fourn_price_id;
935 } else {
936 $this->line->fk_fournprice = $fk_fournprice;
937 }
938 $this->line->pa_ht = $pa_ht;
939
940 if (is_array($array_options) && count($array_options) > 0) {
941 // We replace values in this->line->array_options only for entries defined into $array_options
942 foreach ($array_options as $key => $value) {
943 $this->line->array_options[$key] = $array_options[$key];
944 }
945 }
946
947 // Multicurrency
948 $this->line->multicurrency_subprice = (float) $pu_ht_devise;
949 $this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
950 $this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
951 $this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
952
953 $result = $this->line->update();
954 if ($result > 0) {
955 // Reorder if child line
956 if (!empty($fk_parent_line)) {
957 $this->line_order(true, 'DESC');
958 }
959
960 $this->update_price(1);
961
962 $this->db->commit();
963 return $result;
964 } else {
965 $this->error = $this->db->error();
966 $this->db->rollback();
967 return -1;
968 }
969 } else {
970 dol_syslog(get_class($this)."::updateline Erreur -2 SupplierProposal en mode incompatible pour cette action");
971 return -2;
972 }
973 }
974
975
982 public function deleteLine($lineid)
983 {
984 global $user;
985
986 if ($this->status == 0) {
987 $line = new SupplierProposalLine($this->db);
988
989 // For triggers
990 $line->fetch($lineid);
991
992 if ($this->id > 0 && (int) $line->fk_supplier_proposal !== (int) $this->id) {
993 $this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
994 return -1;
995 }
996
997 if ($line->delete($user) > 0) {
998 $this->update_price(1);
999
1000 return 1;
1001 } else {
1002 return -1;
1003 }
1004 } else {
1005 return -2;
1006 }
1007 }
1008
1009
1018 public function create($user, $notrigger = 0)
1019 {
1020 global $conf, $mysoc, $hookmanager;
1021 $error = 0;
1022
1023 $now = dol_now();
1024
1025 dol_syslog(get_class($this)."::create");
1026
1027 // Check parameters
1028 $result = $this->fetch_thirdparty();
1029 if ($result < 0) {
1030 $this->error = "Failed to fetch company";
1031 dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
1032 return -3;
1033 }
1034 if (!empty($this->ref)) { // We check that ref is not already used
1035 $result = self::isExistingObject($this->element, 0, $this->ref); // Check ref is not yet used
1036 if ($result > 0) {
1037 $this->error = 'ErrorRefAlreadyExists';
1038 dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING);
1039 $this->db->rollback();
1040 return -1;
1041 }
1042 }
1043
1044 // Set tmp vars
1045 $delivery_date = $this->delivery_date;
1046
1047 // Multicurrency
1048 if (!empty($this->multicurrency_code)) {
1049 list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $now);
1050 }
1051 if (empty($this->fk_multicurrency)) {
1052 $this->multicurrency_code = getDolCurrency();
1053 $this->fk_multicurrency = 0;
1054 $this->multicurrency_tx = 1;
1055 }
1056
1057 $this->db->begin();
1058
1059 // Insert into database
1060 $sql = "INSERT INTO ".MAIN_DB_PREFIX."supplier_proposal (";
1061 $sql .= "fk_soc";
1062 $sql .= ", price";
1063 $sql .= ", total_tva";
1064 $sql .= ", total_ttc";
1065 $sql .= ", datec";
1066 $sql .= ", ref";
1067 $sql .= ", fk_user_author";
1068 $sql .= ", note_private";
1069 $sql .= ", note_public";
1070 $sql .= ", model_pdf";
1071 $sql .= ", fk_cond_reglement";
1072 $sql .= ", deposit_percent";
1073 $sql .= ", fk_mode_reglement";
1074 $sql .= ", fk_account";
1075 $sql .= ", date_livraison";
1076 $sql .= ", fk_shipping_method";
1077 $sql .= ", fk_projet";
1078 $sql .= ", entity";
1079 $sql .= ", fk_multicurrency";
1080 $sql .= ", multicurrency_code";
1081 $sql .= ", multicurrency_tx";
1082 $sql .= ") ";
1083 $sql .= " VALUES (";
1084 $sql .= ((int) $this->socid);
1085 $sql .= ", 0";
1086 $sql .= ", 0";
1087 $sql .= ", 0";
1088 $sql .= ", '".$this->db->idate($now)."'";
1089 $sql .= ", '(PROV)'";
1090 $sql .= ", ".($user->id > 0 ? ((int) $user->id) : "null");
1091 $sql .= ", '".$this->db->escape($this->note_private)."'";
1092 $sql .= ", '".$this->db->escape($this->note_public)."'";
1093 $sql .= ", '".$this->db->escape($this->model_pdf)."'";
1094 $sql .= ", ".($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : 'NULL');
1095 $sql .= ", ".(!empty($this->deposit_percent) ? "'" . $this->db->escape($this->deposit_percent) . "'" : 'NULL');
1096 $sql .= ", ".($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : 'NULL');
1097 $sql .= ", ".($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
1098 $sql .= ", ".(isDolTms($delivery_date) ? "'".$this->db->idate($delivery_date)."'" : "null");
1099 $sql .= ", ".($this->shipping_method_id > 0 ? ((int) $this->shipping_method_id) : 'NULL');
1100 $sql .= ", ".($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
1101 $sql .= ", ".((int) $conf->entity);
1102 $sql .= ", ".((int) $this->fk_multicurrency);
1103 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
1104 $sql .= ", ".((float) $this->multicurrency_tx);
1105 $sql .= ")";
1106
1107 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1108 $resql = $this->db->query($sql);
1109 if ($resql) {
1110 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."supplier_proposal");
1111
1112 if ($this->id) {
1113 $this->ref = '(PROV'.$this->id.')';
1114 $sql = 'UPDATE '.MAIN_DB_PREFIX."supplier_proposal SET ref='".$this->db->escape($this->ref)."' WHERE rowid=".((int) $this->id);
1115
1116 dol_syslog(get_class($this)."::create", LOG_DEBUG);
1117 $resql = $this->db->query($sql);
1118 if (!$resql) {
1119 $error++;
1120 }
1121
1122 if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
1123 $this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
1124 }
1125
1126 // Add object linked
1127 if (!$error && $this->id && !empty($this->linked_objects) && is_array($this->linked_objects)) {
1128 foreach ($this->linked_objects as $origin => $tmp_origin_id) {
1129 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, ...))
1130 foreach ($tmp_origin_id as $origin_id) {
1131 $ret = $this->add_object_linked($origin, $origin_id);
1132 if (!$ret) {
1133 dol_print_error($this->db);
1134 $error++;
1135 }
1136 }
1137 }
1138 }
1139 }
1140
1141 /*
1142 * Insert products details into database
1143 */
1144 if (!$error) {
1145 $fk_parent_line = 0;
1146 $num = count($this->lines);
1147
1148 for ($i = 0; $i < $num; $i++) {
1149 if (!is_object($this->lines[$i])) { // Lines coming from the REST API are plain arrays
1150 // Build a real line object: the code below calls methods on it
1151 // (getPriceBaseType), which an array cannot answer.
1152 $lineobj = new SupplierProposalLine($this->db);
1153 foreach ($this->lines[$i] as $key => $val) {
1154 $lineobj->$key = $val;
1155 }
1156 $this->lines[$i] = $lineobj;
1157 }
1158
1159 // Reset fk_parent_line for no child products and special product
1160 if (($this->lines[$i]->product_type != 9 && empty($this->lines[$i]->fk_parent_line)) || $this->lines[$i]->product_type == 9) {
1161 $fk_parent_line = 0;
1162 }
1163
1164 // Preserve the original entry mode of the line so the total is computed from the typed value (no rounding drift).
1165 $line_price_base_type = $this->lines[$i]->getPriceBaseType();
1166 $result = $this->addline(
1167 $this->lines[$i]->desc,
1168 (float) $this->lines[$i]->subprice,
1169 $this->lines[$i]->qty,
1170 $this->lines[$i]->tva_tx,
1171 $this->lines[$i]->localtax1_tx,
1172 $this->lines[$i]->localtax2_tx,
1173 $this->lines[$i]->fk_product,
1174 $this->lines[$i]->remise_percent,
1175 $line_price_base_type,
1176 (float) $this->lines[$i]->subprice_ttc,
1177 0,
1178 $this->lines[$i]->product_type,
1179 $this->lines[$i]->rang,
1180 $this->lines[$i]->special_code,
1181 $fk_parent_line,
1182 $this->lines[$i]->fk_fournprice,
1183 $this->lines[$i]->pa_ht,
1184 empty($this->lines[$i]->label) ? '' : $this->lines[$i]->label, // deprecated
1185 $this->lines[$i]->array_options,
1186 $this->lines[$i]->ref_fourn,
1187 $this->lines[$i]->fk_unit,
1188 'supplier_proposal',
1189 $this->lines[$i]->rowid
1190 );
1191
1192 if ($result < 0) {
1193 $error++;
1194 $this->error = $this->db->error;
1195 dol_print_error($this->db);
1196 break;
1197 }
1198 // Defined the new fk_parent_line
1199 if ($result > 0 && $this->lines[$i]->product_type == 9) {
1200 $fk_parent_line = $result;
1201 }
1202 }
1203 }
1204
1205 if (!$error) {
1206 // Update denormalized data
1207 $resql = $this->update_price(1);
1208 if ($resql) {
1209 $action = 'update';
1210
1211 // Actions on extra fields
1212 if (!$error) {
1213 $result = $this->insertExtraFields();
1214 if ($result < 0) {
1215 $error++;
1216 }
1217 }
1218
1219 if (!$error && !$notrigger) {
1220 // Call trigger
1221 $result = $this->call_trigger('PROPOSAL_SUPPLIER_CREATE', $user);
1222 if ($result < 0) {
1223 $error++;
1224 }
1225 // End call triggers
1226 }
1227 } else {
1228 $this->error = $this->db->lasterror();
1229 $error++;
1230 }
1231 }
1232 } else {
1233 $this->error = $this->db->lasterror();
1234 $error++;
1235 }
1236
1237 if (!$error) {
1238 $this->db->commit();
1239 dol_syslog(get_class($this)."::create done id=".$this->id);
1240 return $this->id;
1241 } else {
1242 $this->db->rollback();
1243 return -2;
1244 }
1245 } else {
1246 $this->error = $this->db->lasterror();
1247 $this->db->rollback();
1248 return -1;
1249 }
1250 }
1251
1259 public function createFromClone(User $user, $fromid = 0)
1260 {
1261 global $hookmanager;
1262
1263 $error = 0;
1264 $now = dol_now();
1265
1266 $this->db->begin();
1267
1268 // get extrafields so they will be clone
1269 foreach ($this->lines as $line) {
1270 $line->fetch_optionals();
1271 }
1272
1273 // Load source object
1274 $objFrom = clone $this;
1275
1276 $objsoc = new Societe($this->db);
1277
1278 // Change socid if needed
1279 if (!empty($fromid) && $fromid != $this->socid) {
1280 if ($objsoc->fetch($fromid) > 0) {
1281 $this->socid = $objsoc->id;
1282 $this->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
1283 $this->deposit_percent = (!empty($objsoc->deposit_percent) ? $objsoc->deposit_percent : 0);
1284 $this->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
1285 unset($this->fk_project);
1286 }
1287
1288 // TODO Change product price if multi-prices
1289 } else {
1290 $objsoc->fetch($this->socid);
1291 }
1292
1293 $this->id = 0;
1294 $this->status = 0;
1295 $this->statut = 0;
1296
1297 if (!getDolGlobalString('SUPPLIER_PROPOSAL_ADDON') || !is_readable(DOL_DOCUMENT_ROOT."/core/modules/supplier_proposal/" . getDolGlobalString('SUPPLIER_PROPOSAL_ADDON').".php")) {
1298 $this->error = 'ErrorSetupNotComplete';
1299 return -1;
1300 }
1301
1302 // Clear fields
1303 $this->user_author_id = $user->id;
1304 $this->user_validation_id = 0;
1305 $this->date = $now;
1306
1307 // Set ref
1308 require_once DOL_DOCUMENT_ROOT."/core/modules/supplier_proposal/" . getDolGlobalString('SUPPLIER_PROPOSAL_ADDON').'.php';
1309 $obj = getDolGlobalString('SUPPLIER_PROPOSAL_ADDON');
1310 $modSupplierProposal = new $obj();
1311 '@phan-var-force ModeleNumRefSupplierProposal $modSupplierProposal';
1312 $this->ref = $modSupplierProposal->getNextValue($objsoc, $this);
1313
1314 // Create clone
1315 $this->context['createfromclone'] = 'createfromclone';
1316 $result = $this->create($user);
1317 if ($result < 0) {
1318 $error++;
1319 }
1320
1321 if (!$error) {
1322 // Hook of thirdparty module
1323 if (is_object($hookmanager)) {
1324 $parameters = array('objFrom' => $objFrom);
1325 $action = '';
1326 $reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1327 if ($reshook < 0) {
1328 $this->setErrorsFromObject($hookmanager);
1329 $error++;
1330 }
1331 }
1332 }
1333
1334 unset($this->context['createfromclone']);
1335
1336 // End
1337 if (!$error) {
1338 $this->db->commit();
1339 return $this->id;
1340 } else {
1341 $this->db->rollback();
1342 return -1;
1343 }
1344 }
1345
1353 public function fetch($rowid, $ref = '')
1354 {
1355 $sql = "SELECT p.rowid, p.entity, p.ref, p.fk_soc as socid";
1356 $sql .= ", p.total_ttc, p.total_tva, p.localtax1, p.localtax2, p.total_ht";
1357 $sql .= ", p.datec, GREATEST(p.tms, pef.tms) as date_modification";
1358 $sql .= ", p.date_valid as datev";
1359 $sql .= ", p.date_livraison as delivery_date";
1360 $sql .= ", p.model_pdf, p.extraparams";
1361 $sql .= ", p.note_private, p.note_public";
1362 $sql .= ", p.fk_projet as fk_project, p.fk_statut as status";
1363 $sql .= ", p.fk_user_author, p.fk_user_modif, p.fk_user_valid, p.fk_user_cloture";
1364 $sql .= ", p.fk_cond_reglement";
1365 $sql .= ", p.fk_mode_reglement";
1366 $sql .= ', p.fk_account';
1367 $sql .= ", p.fk_shipping_method";
1368 $sql .= ", p.last_main_doc";
1369 $sql .= ", p.fk_multicurrency, p.multicurrency_code, p.multicurrency_tx, p.multicurrency_total_ht, p.multicurrency_total_tva, p.multicurrency_total_ttc";
1370 $sql .= ", c.label as statut_label";
1371 $sql .= ", cr.code as cond_reglement_code, cr.libelle as cond_reglement, cr.libelle_facture as cond_reglement_libelle_doc, p.deposit_percent";
1372 $sql .= ", cp.code as mode_reglement_code, cp.libelle as mode_reglement";
1373 $sql .= " FROM ".MAIN_DB_PREFIX."c_propalst as c, ".MAIN_DB_PREFIX."supplier_proposal as p";
1374 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."supplier_proposal_extrafields as pef ON pef.fk_object=p.rowid";
1375 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as cp ON p.fk_mode_reglement = cp.id';
1376 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_payment_term as cr ON p.fk_cond_reglement = cr.rowid';
1377 $sql .= " WHERE p.fk_statut = c.id";
1378 $sql .= " AND p.entity IN (".getEntity('supplier_proposal').")";
1379 if ($ref) {
1380 $sql .= " AND p.ref = '".$this->db->escape($ref)."'";
1381 } else {
1382 $sql .= " AND p.rowid = ".((int) $rowid);
1383 }
1384
1385 dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
1386 $resql = $this->db->query($sql);
1387 if ($resql) {
1388 if ($this->db->num_rows($resql)) {
1389 $obj = $this->db->fetch_object($resql);
1390
1391 $this->id = $obj->rowid;
1392 $this->entity = $obj->entity;
1393
1394 $this->ref = $obj->ref;
1395 $this->total_ht = $obj->total_ht;
1396 $this->total_tva = $obj->total_tva;
1397 $this->total_localtax1 = $obj->localtax1;
1398 $this->total_localtax2 = $obj->localtax2;
1399 $this->total_ttc = $obj->total_ttc;
1400 $this->socid = $obj->socid;
1401 $this->fk_project = $obj->fk_project;
1402 $this->model_pdf = $obj->model_pdf;
1403 $this->note = $obj->note_private; // TODO deprecated
1404 $this->note_private = $obj->note_private;
1405 $this->note_public = $obj->note_public;
1406 $this->statut = (int) $obj->status;
1407 $this->status = (int) $obj->status;
1408 $this->datec = $this->db->jdate($obj->datec); // TODO deprecated
1409 $this->datev = $this->db->jdate($obj->datev); // TODO deprecated
1410 $this->date_creation = $this->db->jdate($obj->datec); // Creation date
1411 $this->date_modification = $this->db->jdate($obj->date_modification); // Modification date
1412 $this->date = $this->date_creation;
1413 $this->date_validation = $this->db->jdate($obj->datev); // Validation date
1414 $this->delivery_date = $this->db->jdate($obj->delivery_date);
1415 $this->shipping_method_id = ($obj->fk_shipping_method > 0) ? $obj->fk_shipping_method : null;
1416
1417 $this->last_main_doc = $obj->last_main_doc;
1418 $this->mode_reglement_id = $obj->fk_mode_reglement;
1419 $this->mode_reglement_code = $obj->mode_reglement_code;
1420 $this->mode_reglement = $obj->mode_reglement;
1421 $this->deposit_percent = $obj->deposit_percent;
1422 $this->fk_account = ($obj->fk_account > 0) ? $obj->fk_account : null;
1423 $this->cond_reglement_id = $obj->fk_cond_reglement;
1424 $this->cond_reglement_code = $obj->cond_reglement_code;
1425 $this->cond_reglement = $obj->cond_reglement;
1426 $this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
1427
1428 $this->extraparams = (array) (!empty($obj->extraparams) ? json_decode($obj->extraparams, true) : array());
1429
1430 $this->user_author_id = $obj->fk_user_author;
1431 $this->user_modification_id = $obj->fk_user_modif;
1432 $this->user_validation_id = $obj->fk_user_valid;
1433 $this->user_closing_id = $obj->fk_user_cloture;
1434
1435 // Multicurrency
1436 $this->fk_multicurrency = $obj->fk_multicurrency;
1437 $this->multicurrency_code = $obj->multicurrency_code;
1438 $this->multicurrency_tx = $obj->multicurrency_tx;
1439 $this->multicurrency_total_ht = $obj->multicurrency_total_ht;
1440 $this->multicurrency_total_tva = $obj->multicurrency_total_tva;
1441 $this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
1442
1443 $this->db->free($resql);
1444
1445 // Retrieve all extrafield
1446 // fetch optionals attributes and labels
1447 $this->fetch_optionals();
1448
1449 $this->lines = array();
1450
1451 // Lines of supplier proposals
1452 $sql = "SELECT d.rowid, d.fk_supplier_proposal, d.fk_parent_line, d.label as custom_label, d.description, d.price, d.tva_tx, d.localtax1_tx, d.localtax2_tx, d.qty, d.fk_remise_except, d.remise_percent, d.subprice, d.subprice_ttc, d.fk_product,";
1453 $sql .= " d.info_bits, d.total_ht, d.total_tva, d.total_localtax1, d.total_localtax2, d.total_ttc, d.fk_product_fournisseur_price as fk_fournprice, d.buy_price_ht as pa_ht, d.special_code, d.rang, d.product_type,";
1454 $sql .= ' p.ref as product_ref, p.description as product_desc, p.fk_product_type, p.label as product_label,';
1455 $sql .= ' d.ref_fourn as ref_produit_fourn, d.extraparams,';
1456 $sql .= ' d.fk_multicurrency, d.multicurrency_code, d.multicurrency_subprice, d.multicurrency_total_ht, d.multicurrency_total_tva, d.multicurrency_total_ttc, d.fk_unit';
1457 $sql .= " FROM ".MAIN_DB_PREFIX."supplier_proposaldet as d";
1458 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON d.fk_product = p.rowid";
1459 $sql .= " WHERE d.fk_supplier_proposal = ".((int) $this->id);
1460 $sql .= " ORDER by d.rang";
1461
1462 $result = $this->db->query($sql);
1463 if ($result) {
1464 $num = $this->db->num_rows($result);
1465 $i = 0;
1466
1467 while ($i < $num) {
1468 $objp = $this->db->fetch_object($result);
1469
1470 $line = new SupplierProposalLine($this->db);
1471
1472 $line->rowid = $objp->rowid; // deprecated
1473 $line->id = $objp->rowid;
1474 $line->fk_supplier_proposal = $objp->fk_supplier_proposal;
1475 $line->fk_parent_line = $objp->fk_parent_line;
1476 $line->product_type = $objp->product_type;
1477 $line->label = $objp->custom_label;
1478 $line->desc = $objp->description; // Description of the line
1479 $line->qty = $objp->qty;
1480 $line->tva_tx = $objp->tva_tx;
1481 $line->localtax1_tx = $objp->localtax1_tx;
1482 $line->localtax2_tx = $objp->localtax2_tx;
1483 $line->subprice = $objp->subprice;
1484 $line->subprice_ttc = $objp->subprice_ttc;
1485 $line->fk_remise_except = $objp->fk_remise_except;
1486 $line->remise_percent = $objp->remise_percent;
1487
1488 $line->info_bits = $objp->info_bits;
1489 $line->total_ht = $objp->total_ht;
1490 $line->total_tva = $objp->total_tva;
1491 $line->total_localtax1 = $objp->total_localtax1;
1492 $line->total_localtax2 = $objp->total_localtax2;
1493 $line->total_ttc = $objp->total_ttc;
1494 $line->fk_fournprice = $objp->fk_fournprice;
1495 $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
1496 $line->pa_ht = $marginInfos[0];
1497 $line->marge_tx = $marginInfos[1];
1498 $line->marque_tx = $marginInfos[2];
1499 $line->special_code = $objp->special_code;
1500 $line->rang = $objp->rang;
1501
1502 $line->fk_product = $objp->fk_product;
1503
1504 $line->ref = $objp->product_ref; // deprecated
1505 $line->product_ref = $objp->product_ref;
1506 $line->libelle = $objp->product_label; // deprecated
1507 $line->product_label = $objp->product_label;
1508 $line->product_desc = $objp->product_desc; // Description produit
1509 $line->fk_product_type = $objp->fk_product_type;
1510
1511 $line->ref_fourn = $objp->ref_produit_fourn;
1512
1513 $line->extraparams = !empty($objp->extraparams) ? (array) json_decode($objp->extraparams, true) : array();
1514
1515 // Multicurrency
1516 $line->fk_multicurrency = $objp->fk_multicurrency;
1517 $line->multicurrency_code = $objp->multicurrency_code;
1518 $line->multicurrency_subprice = $objp->multicurrency_subprice;
1519 $line->multicurrency_total_ht = $objp->multicurrency_total_ht;
1520 $line->multicurrency_total_tva = $objp->multicurrency_total_tva;
1521 $line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
1522 $line->fk_unit = $objp->fk_unit;
1523
1524 $line->fetch_optionals();
1525
1526 $this->lines[$i] = $line;
1527
1528 $i++;
1529 }
1530 $this->db->free($result);
1531 } else {
1532 $this->error = $this->db->error();
1533 return -1;
1534 }
1535
1536 return 1;
1537 }
1538
1539 $this->error = "Record Not Found";
1540 return 0;
1541 } else {
1542 $this->error = $this->db->error();
1543 return -1;
1544 }
1545 }
1546
1554 public function valid($user, $notrigger = 0)
1555 {
1556 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1557
1558 global $conf;
1559
1560 $error = 0;
1561 $now = dol_now();
1562
1563 if ((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('supplier_proposal', 'creer'))
1564 || (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('supplier_proposal', 'validate_advance'))) {
1565 $this->db->begin();
1566
1567 // Numbering module definition
1568 $soc = new Societe($this->db);
1569 $result = $soc->fetch($this->socid);
1570
1571 if ($result < 0) {
1572 return -1;
1573 }
1574 if (!getDolGlobalBool('SUPPLIER_PROPOSAL_NOCHECK_ONBUY_PRODUCTS_ONVALID') && !$this->checkActiveProductInLines('onbuy')) {
1575 dol_syslog(get_class($this)."::valid checkActiveProductInLines ".$this->error, LOG_INFO);
1576 return -1;
1577 }
1578 // Define new ref
1579 if (preg_match('/^[\‍(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
1580 $num = $this->getNextNumRef($soc);
1581 } else {
1582 $num = (string) $this->ref;
1583 }
1584 $this->newref = dol_sanitizeFileName($num);
1585
1586 $sql = "UPDATE ".MAIN_DB_PREFIX."supplier_proposal";
1587 $sql .= " SET ref = '".$this->db->escape($num)."',";
1588 $sql .= " fk_statut = 1, date_valid='".$this->db->idate($now)."', fk_user_valid=".((int) $user->id);
1589 $sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = 0";
1590
1591 dol_syslog(get_class($this)."::valid", LOG_DEBUG);
1592 $resql = $this->db->query($sql);
1593 if (!$resql) {
1594 dol_print_error($this->db);
1595 $error++;
1596 }
1597
1598 // Trigger calls
1599 if (!$error && !$notrigger) {
1600 // Call trigger
1601 $result = $this->call_trigger('PROPOSAL_SUPPLIER_VALIDATE', $user);
1602 if ($result < 0) {
1603 $error++;
1604 }
1605 // End call triggers
1606 }
1607
1608 if (!$error) {
1609 $this->oldref = $this->ref;
1610
1611 // Rename directory if dir was a temporary ref
1612 if (preg_match('/^[\‍(]?PROV/i', $this->ref)) {
1613 // Now we rename also files into index
1614 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'supplier_proposal/".$this->db->escape($this->newref)."'";
1615 $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'supplier_proposal/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
1616 $resql = $this->db->query($sql);
1617 if (!$resql) {
1618 $error++;
1619 $this->error = $this->db->lasterror();
1620 }
1621 $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'supplier_proposal/".$this->db->escape($this->newref)."'";
1622 $sql .= " WHERE filepath = 'supplier_proposal/".$this->db->escape($this->ref)."' and entity = ".((int) $conf->entity);
1623 $resql = $this->db->query($sql);
1624 if (!$resql) {
1625 $error++;
1626 $this->error = $this->db->lasterror();
1627 }
1628
1629 // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
1630 $oldref = dol_sanitizeFileName($this->ref);
1631 $newref = dol_sanitizeFileName($num);
1632 $dirsource = $conf->supplier_proposal->dir_output.'/'.$oldref;
1633 $dirdest = $conf->supplier_proposal->dir_output.'/'.$newref;
1634 if (!$error && file_exists($dirsource)) {
1635 dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
1636 if (@rename($dirsource, $dirdest)) {
1637 dol_syslog("Rename ok");
1638 // Rename docs starting with $oldref with $newref
1639 $listoffiles = dol_dir_list($conf->supplier_proposal->dir_output.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
1640 foreach ($listoffiles as $fileentry) {
1641 $dirsource = $fileentry['name'];
1642 $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
1643 $dirsource = $fileentry['path'].'/'.$dirsource;
1644 $dirdest = $fileentry['path'].'/'.$dirdest;
1645 @rename($dirsource, $dirdest);
1646 }
1647 }
1648 }
1649 }
1650
1651 $this->ref = $num;
1652 $this->statut = self::STATUS_VALIDATED;
1654 $this->user_validation_id = $user->id;
1655 $this->datev = $now;
1656 $this->date_validation = $now;
1657
1658 if (getDolGlobalString('SUPPLIER_PROPOSAL_AUTOADD_USER_CONTACT')) {
1659 $result = $this->add_contact($user->id, 'SALESREPFOLL', 'internal', 1);
1660 if ($result < 0 && $result != -2) { // -2 means already exists
1661 $error++;
1662 }
1663 }
1664
1665 $this->db->commit();
1666 return 1;
1667 } else {
1668 $this->db->rollback();
1669 return -1;
1670 }
1671 } else {
1672 dol_syslog("You don't have permission to validate supplier proposal", LOG_WARNING);
1673 return -2;
1674 }
1675 }
1676
1677 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1686 public function set_date_livraison($user, $delivery_date)
1687 {
1688 // phpcs:enable
1689 return $this->setDeliveryDate($user, $delivery_date);
1690 }
1691
1699 public function setDeliveryDate($user, $delivery_date)
1700 {
1701 if ($user->hasRight('supplier_proposal', 'creer')) {
1702 $sql = "UPDATE ".MAIN_DB_PREFIX."supplier_proposal ";
1703 $sql .= " SET date_livraison = ".(isDolTms($delivery_date) ? "'".$this->db->idate($delivery_date)."'" : 'null');
1704 $sql .= " WHERE rowid = ".((int) $this->id);
1705
1706 if ($this->db->query($sql)) {
1707 $this->delivery_date = $delivery_date;
1708 return 1;
1709 } else {
1710 $this->error = $this->db->error();
1711 dol_syslog(get_class($this)."::setDeliveryDate Erreur SQL");
1712 return -1;
1713 }
1714 }
1715 return 0;
1716 }
1717
1718
1728 public function reopen($user, $status, $note = '', $notrigger = 0)
1729 {
1730 $error = 0;
1731
1732 $sql = "UPDATE ".MAIN_DB_PREFIX."supplier_proposal";
1733 $sql .= " SET fk_statut = ".((int) $status).",";
1734 if (!empty($note)) {
1735 $sql .= " note_private = '".$this->db->escape($note)."',";
1736 }
1737 $sql .= " date_cloture = NULL, fk_user_cloture = NULL";
1738 $sql .= " WHERE rowid = ".((int) $this->id);
1739
1740 $this->db->begin();
1741
1742 dol_syslog(get_class($this)."::reopen", LOG_DEBUG);
1743 $resql = $this->db->query($sql);
1744 if (!$resql) {
1745 $error++;
1746 $this->errors[] = "Error ".$this->db->lasterror();
1747 }
1748 if (!$error) {
1749 if (!$notrigger) {
1750 // Call trigger
1751 $result = $this->call_trigger('PROPOSAL_SUPPLIER_REOPEN', $user);
1752 if ($result < 0) {
1753 $error++;
1754 }
1755 // End call triggers
1756 }
1757 }
1758
1759 // Commit or rollback
1760 if ($error) {
1761 if (!empty($this->errors)) {
1762 foreach ($this->errors as $errmsg) {
1763 dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1764 $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1765 }
1766 }
1767 $this->db->rollback();
1768 return -1 * $error;
1769 } else {
1770 $this->statut = $status;
1771 $this->status = $status;
1772
1773 $this->db->commit();
1774 return 1;
1775 }
1776 }
1777
1778
1787 public function cloture($user, $status, $note)
1788 {
1789 global $langs, $conf;
1790 $hidedetails = 0;
1791 $hidedesc = 0;
1792 $hideref = 0;
1793
1794 $error = 0;
1795 $now = dol_now();
1796
1797 $this->db->begin();
1798
1799 $sql = "UPDATE ".MAIN_DB_PREFIX."supplier_proposal";
1800 $sql .= " SET fk_statut = ".((int) $status).", note_private = '".$this->db->escape($note)."', date_cloture='".$this->db->idate($now)."', fk_user_cloture=".((int) $user->id);
1801 $sql .= " WHERE rowid = ".((int) $this->id);
1802
1803 $resql = $this->db->query($sql);
1804 if ($resql) {
1805 $this->statut = $status;
1806 $this->status = $status;
1807
1808 $modelpdf = getDolGlobalString('SUPPLIER_PROPOSAL_ADDON_PDF_ODT_CLOSED', (empty($this->model_pdf) ? '' : $this->model_pdf));
1809 $triggerName = 'PROPOSAL_SUPPLIER_CLOSE_REFUSED';
1810
1811 if ($status == 2) {
1812 $triggerName = 'PROPOSAL_SUPPLIER_CLOSE_SIGNED';
1813 $modelpdf = getDolGlobalString('SUPPLIER_PROPOSAL_ADDON_PDF_ODT_TOBILL', (empty($this->model_pdf) ? '' : $this->model_pdf));
1814
1815 if (getDolGlobalString('SUPPLIER_PROPOSAL_UPDATE_PRICE_ON_SUPPlIER_PROPOSAL')) { // TODO This option was not tested correctly. Error if product ref does not exists
1816 $result = $this->updateOrCreatePriceFournisseur($user);
1817 }
1818 }
1819 if ($status == 4) {
1820 $triggerName = 'PROPOSAL_SUPPLIER_CLASSIFY_BILLED';
1821 }
1822
1823 if (!getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) {
1824 // Define output language
1825 $outputlangs = $langs;
1826 if (getDolGlobalInt('MAIN_MULTILANGS')) {
1827 $outputlangs = new Translate("", $conf);
1828 $newlang = (GETPOST('lang_id', 'aZ09') ? GETPOST('lang_id', 'aZ09') : $this->thirdparty->default_lang);
1829 $outputlangs->setDefaultLang($newlang);
1830 }
1831
1832 $this->generateDocument($modelpdf, $outputlangs, $hidedetails, $hidedesc, $hideref);
1833 }
1834
1835 // Call trigger
1836 $result = $this->call_trigger($triggerName, $user);
1837 if ($result < 0) {
1838 $error++;
1839 }
1840 // End call triggers
1841
1842 if (!$error) {
1843 $this->db->commit();
1844 return 1;
1845 } else {
1846 $this->db->rollback();
1847 return -1;
1848 }
1849 } else {
1850 $this->error = $this->db->lasterror();
1851 $this->errors[] = $this->db->lasterror();
1852 $this->db->rollback();
1853 return -1;
1854 }
1855 }
1856
1863 public function updateOrCreatePriceFournisseur($user)
1864 {
1865 dol_syslog(get_class($this)."::updateOrCreatePriceFournisseur", LOG_DEBUG);
1866
1867 foreach ($this->lines as $product) {
1868 if ($product->subprice <= 0) {
1869 continue;
1870 }
1871 $productsupplier = new ProductFournisseur($this->db);
1872
1873 $multicurrency_tx = 1;
1874 $fk_multicurrency = 0;
1875
1876 if (empty($this->thirdparty)) {
1877 $this->fetch_thirdparty();
1878 }
1879
1880 $ref_fourn = $product->ref_fourn;
1881 if (empty($ref_fourn)) {
1882 $ref_fourn = $product->ref_supplier;
1883 }
1884 if (isModEnabled("multicurrency") && !empty($product->multicurrency_code)) {
1885 list($fk_multicurrency, $multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $product->multicurrency_code);
1886 }
1887 $productsupplier->id = $product->fk_product;
1888
1889 $productsupplier->update_buyprice($product->qty, $product->total_ht, $user, 'HT', $this->thirdparty, 0, $ref_fourn, $product->tva_tx, 0, 0, 0, $product->info_bits, 0, '', array(), '', $product->multicurrency_total_ht, 'HT', $multicurrency_tx, $product->multicurrency_code, '', '', 0);
1890 }
1891
1892 return 1;
1893 }
1894
1903 public function updatePriceFournisseur($idProductFournPrice, $product, $user)
1904 {
1905 $price = price2num($product->subprice * $product->qty, 'MU');
1906 $unitPrice = price2num($product->subprice, 'MU');
1907
1908 $sql = 'UPDATE '.MAIN_DB_PREFIX.'product_fournisseur_price SET '.(!empty($product->ref_fourn) ? 'ref_fourn = "'.$this->db->escape($product->ref_fourn).'", ' : '').' price ='.((float) $price).', unitprice ='.((float) $unitPrice).' WHERE rowid = '.((int) $idProductFournPrice);
1909
1910 $resql = $this->db->query($sql);
1911 if (!$resql) {
1912 $this->error = $this->db->error();
1913 $this->db->rollback();
1914 return -1;
1915 }
1916 return 1;
1917 }
1918
1926 public function createPriceFournisseur($product, $user)
1927 {
1928 $price = price2num($product->subprice * $product->qty, 'MU');
1929 $qty = price2num($product->qty);
1930 $unitPrice = price2num($product->subprice, 'MU');
1931
1932 $now = dol_now();
1933
1934 $sanitized_values = array(
1935 "'".$this->db->idate($now)."'",
1936 (int) $product->fk_product,
1937 (int) $this->thirdparty->id,
1938 "'".$this->db->escape($product->ref_fourn)."'",
1939 (float) $price,
1940 (float) $qty,
1941 (float) $unitPrice,
1942 (float) $product->tva_tx,
1943 (int) $user->id
1944 );
1945 if (isModEnabled("multicurrency")) {
1946 if (!empty($product->multicurrency_code)) {
1947 include_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php';
1948 $multicurrency = new MultiCurrency($this->db); //need to fetch because empty fk_multicurrency and rate
1949 $multicurrency->fetch(0, $product->multicurrency_code);
1950 if (!empty($multicurrency->id)) {
1951 $sanitized_values[] = (int) $multicurrency->id;
1952 $sanitized_values[] = "'".$this->db->escape($product->multicurrency_code)."'";
1953 $sanitized_values[] = (float) $product->multicurrency_subprice;
1954 $sanitized_values[] = (float) $product->multicurrency_total_ht;
1955 $sanitized_values[] = (float) $multicurrency->rate->rate;
1956 } else {
1957 for ($i = 0; $i < 5; $i++) {
1958 $sanitized_values[] = 'NULL';
1959 }
1960 }
1961 }
1962 }
1963
1964 $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'product_fournisseur_price ';
1965 $sql .= '(datec, fk_product, fk_soc, ref_fourn, price, quantity, unitprice, tva_tx, fk_user';
1966 if (isModEnabled("multicurrency") && !empty($product->multicurrency_code)) {
1967 $sql .= ',fk_multicurrency, multicurrency_code, multicurrency_unitprice, multicurrency_price, multicurrency_tx';
1968 }
1969 $sql .= ') VALUES ('.implode(',', $sanitized_values).')';
1970
1971 $resql = $this->db->query($sql);
1972 if (!$resql) {
1973 $this->error = $this->db->error();
1974 $this->db->rollback();
1975 return -1;
1976 }
1977 return 1;
1978 }
1979
1980 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1987 public function setDraft($user)
1988 {
1989 // phpcs:enable
1990 $error = 0;
1991
1992 if ($this->status == self::STATUS_DRAFT) {
1993 dol_syslog(get_class($this)."::setDraft already draft status", LOG_WARNING);
1994 return 0;
1995 }
1996
1997 $sql = "UPDATE ".MAIN_DB_PREFIX."supplier_proposal";
1998 $sql .= " SET fk_statut = ".self::STATUS_DRAFT;
1999 $sql .= " WHERE rowid = ".((int) $this->id);
2000
2001 if ($this->db->query($sql)) {
2002 if (!$error) {
2003 $this->oldcopy = clone $this;
2004 }
2005
2006 if (!$error) {
2007 // Call trigger
2008 $result = $this->call_trigger('PROPOSAL_SUPPLIER_UNVALIDATE', $user);
2009 if ($result < 0) {
2010 $error++;
2011 }
2012 }
2013
2014 if (!$error) {
2015 $this->status = self::STATUS_DRAFT;
2016 $this->statut = self::STATUS_DRAFT; // deprecated
2017 $this->db->commit();
2018 return 1;
2019 } else {
2020 $this->db->rollback();
2021 return -1;
2022 }
2023 } else {
2024 return -1;
2025 }
2026 }
2027
2028
2029 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2043 public function liste_array($shortlist = 0, $draft = 0, $notcurrentuser = 0, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'p.datec', $sortorder = 'DESC')
2044 {
2045 // phpcs:enable
2046 global $user;
2047
2048 $ga = array();
2049
2050 $search_sale = 0;
2051 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
2052 $search_sale = $user->id;
2053 }
2054
2055 $sql = "SELECT s.rowid, s.nom as name, s.client,";
2056 $sql .= " p.rowid as supplier_proposalid, p.fk_statut, p.total_ht, p.ref, p.remise, ";
2057 $sql .= " p.datep as dp, p.fin_validite as datelimite";
2058 $sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."supplier_proposal as p, ".MAIN_DB_PREFIX."c_propalst as c";
2059 $sql .= " WHERE p.entity IN (".getEntity('supplier_proposal').")";
2060 $sql .= " AND p.fk_soc = s.rowid";
2061 $sql .= " AND p.fk_statut = c.id";
2062 if ($socid) {
2063 $sql .= " AND s.rowid = ".((int) $socid);
2064 }
2065 if ($draft) {
2066 $sql .= " AND p.fk_statut = 0";
2067 }
2068 if ($notcurrentuser > 0) {
2069 $sql .= " AND p.fk_user_author <> ".((int) $user->id);
2070 }
2071 // Search on sale representative
2072 if ($search_sale && $search_sale != '-1') {
2073 if ($search_sale == -2) {
2074 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc)";
2075 } elseif ($search_sale > 0) {
2076 $sql .= " AND EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = p.fk_soc AND sc.fk_user = ".((int) $search_sale).")";
2077 }
2078 }
2079 $sql .= $this->db->order($sortfield, $sortorder);
2080 $sql .= $this->db->plimit($limit, $offset);
2081
2082 $result = $this->db->query($sql);
2083 if ($result) {
2084 $num = $this->db->num_rows($result);
2085 if ($num) {
2086 $i = 0;
2087 while ($i < $num) {
2088 $obj = $this->db->fetch_object($result);
2089
2090 if ($shortlist == 1) {
2091 $ga[$obj->supplier_proposalid] = $obj->ref;
2092 } elseif ($shortlist == 2) {
2093 $ga[$obj->supplier_proposalid] = $obj->ref.' ('.$obj->name.')';
2094 } else {
2095 $ga[$i]['id'] = $obj->supplier_proposalid;
2096 $ga[$i]['ref'] = $obj->ref;
2097 $ga[$i]['name'] = $obj->name;
2098 }
2099
2100 $i++;
2101 }
2102 }
2103 return $ga;
2104 } else {
2105 dol_print_error($this->db);
2106 return -1;
2107 }
2108 }
2109
2117 public function delete($user, $notrigger = 0)
2118 {
2119 global $conf;
2120 require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2121
2122 $error = 0;
2123
2124 $this->db->begin();
2125
2126 if (!$notrigger) {
2127 // Call trigger
2128 $result = $this->call_trigger('PROPOSAL_SUPPLIER_DELETE', $user);
2129 if ($result < 0) {
2130 $error++;
2131 }
2132 // End call triggers
2133 }
2134
2135 // Remove linked categories.
2136 if (!$error) {
2137 $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_supplier_proposal";
2138 $sql .= " WHERE fk_supplier_proposal = ".((int) $this->id);
2139
2140 $result = $this->db->query($sql);
2141 if (!$result) {
2142 $error++;
2143 $this->error = $this->db->lasterror();
2144 $this->errors[] = $this->error;
2145 }
2146 }
2147
2148 if (!$error) {
2149 $sanitized_main = MAIN_DB_PREFIX.'supplier_proposaldet';
2150 $sanitized_ef = $sanitized_main."_extrafields";
2151 $sqlef = "DELETE FROM $sanitized_ef WHERE fk_object IN (SELECT rowid FROM $sanitized_main WHERE fk_supplier_proposal = ".((int) $this->id).")";
2152 $sql = "DELETE FROM ".MAIN_DB_PREFIX."supplier_proposaldet WHERE fk_supplier_proposal = ".((int) $this->id);
2153 if ($this->db->query($sql)) {
2154 $sql = "DELETE FROM ".MAIN_DB_PREFIX."supplier_proposal WHERE rowid = ".((int) $this->id);
2155 if ($this->db->query($sqlef) && $this->db->query($sql)) {
2156 // Delete linked object
2157 $res = $this->deleteObjectLinked();
2158 if ($res < 0) {
2159 $error++;
2160 }
2161
2162 if (!$error) {
2163 // Delete record into ECM index (Note that delete is also done when deleting files with the dol_delete_dir_recursive
2164 $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive
2165 $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
2166
2167 // We remove directory
2168 $ref = dol_sanitizeFileName($this->ref);
2169 if ($conf->supplier_proposal->dir_output && !empty($this->ref)) {
2170 $dir = $conf->supplier_proposal->dir_output."/".$ref;
2171 $file = $dir."/".$ref.".pdf";
2172 if (file_exists($file)) {
2173 dol_delete_preview($this);
2174
2175 if (!dol_delete_file($file, 0, 0, 0, $this)) { // For triggers
2176 $this->error = 'ErrorFailToDeleteFile';
2177 $this->errors = array('ErrorFailToDeleteFile');
2178 $this->db->rollback();
2179 return 0;
2180 }
2181 }
2182 if (file_exists($dir)) {
2183 $res = @dol_delete_dir_recursive($dir);
2184 if (!$res) {
2185 $this->error = 'ErrorFailToDeleteDir';
2186 $this->errors = array('ErrorFailToDeleteDir');
2187 $this->db->rollback();
2188 return 0;
2189 }
2190 }
2191 }
2192 }
2193
2194 // Removed extrafields
2195 if (!$error) {
2196 $result = $this->deleteExtraFields();
2197 if ($result < 0) {
2198 $error++;
2199 $errorflag = -4;
2200 dol_syslog(get_class($this)."::delete erreur ".$errorflag." ".$this->error, LOG_ERR);
2201 }
2202 }
2203
2204 if (!$error) {
2205 dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
2206 $this->db->commit();
2207 return 1;
2208 } else {
2209 $this->error = $this->db->lasterror();
2210 $this->db->rollback();
2211 return 0;
2212 }
2213 } else {
2214 $this->error = $this->db->lasterror();
2215 $this->db->rollback();
2216 return -3;
2217 }
2218 } else {
2219 $this->error = $this->db->lasterror();
2220 $this->db->rollback();
2221 return -2;
2222 }
2223 } else {
2224 $this->db->rollback();
2225 return -1;
2226 }
2227 }
2228
2235 public function info($id)
2236 {
2237 $sql = "SELECT c.rowid, GREATEST(c.tms, cef.tms) as date_modification,";
2238 $sql .= " c.datec as date_creation, c.date_valid as date_validation, c.date_cloture as date_closure,";
2239 $sql .= " c.fk_user_author, c.fk_user_modif, c.fk_user_valid, c.fk_user_cloture";
2240 $sql .= " FROM ".MAIN_DB_PREFIX."supplier_proposal as c";
2241 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."supplier_proposal_extrafields as cef ON cef.fk_object=c.rowid";
2242 $sql .= " WHERE c.rowid = ".((int) $id);
2243
2244 $result = $this->db->query($sql);
2245
2246 if ($result) {
2247 if ($this->db->num_rows($result)) {
2248 $obj = $this->db->fetch_object($result);
2249
2250 $this->id = $obj->rowid;
2251
2252 $this->date_creation = $this->db->jdate($obj->date_creation);
2253 $this->date_modification = empty($obj->date_modification) ? '' : $this->db->jdate($obj->date_modification);
2254 $this->date_validation = $this->db->jdate($obj->date_validation);
2255 $this->date_cloture = $this->db->jdate($obj->date_closure);
2256
2257 $this->user_creation_id = $obj->fk_user_author;
2258 $this->user_modification_id = $obj->fk_user_modif;
2259 $this->user_validation_id = $obj->fk_user_valid;
2260 $this->user_closing_id = $obj->fk_user_cloture;
2261 }
2262 $this->db->free($result);
2263 } else {
2264 dol_print_error($this->db);
2265 }
2266 }
2267
2268
2275 public function getLibStatut($mode = 0)
2276 {
2277 return $this->LibStatut((isset($this->status) ? $this->status : $this->statut), $mode);
2278 }
2279
2280 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2288 public function LibStatut($status, $mode = 1)
2289 {
2290 // phpcs:enable
2291
2292 // Init/load array of translation of status
2293 if (empty($this->labelStatus) || empty($this->labelStatusShort)) {
2294 global $langs;
2295 $langs->load("supplier_proposal");
2296 $this->labelStatus[self::STATUS_DRAFT] = $langs->transnoentitiesnoconv("SupplierProposalStatusDraft");
2297 $this->labelStatus[self::STATUS_VALIDATED] = $langs->transnoentitiesnoconv("SupplierProposalStatusValidated");
2298 $this->labelStatus[self::STATUS_SIGNED] = $langs->transnoentitiesnoconv("SupplierProposalStatusSigned");
2299 $this->labelStatus[self::STATUS_NOTSIGNED] = $langs->transnoentitiesnoconv("SupplierProposalStatusNotSigned");
2300 $this->labelStatus[self::STATUS_CLOSE] = $langs->transnoentitiesnoconv("SupplierProposalStatusClosed");
2301 $this->labelStatusShort[self::STATUS_DRAFT] = $langs->transnoentitiesnoconv("SupplierProposalStatusDraftShort");
2302 $this->labelStatusShort[self::STATUS_VALIDATED] = $langs->transnoentitiesnoconv("SupplierProposalStatusValidatedShort");
2303 $this->labelStatusShort[self::STATUS_SIGNED] = $langs->transnoentitiesnoconv("SupplierProposalStatusSignedShort");
2304 $this->labelStatusShort[self::STATUS_NOTSIGNED] = $langs->transnoentitiesnoconv("SupplierProposalStatusNotSignedShort");
2305 $this->labelStatusShort[self::STATUS_CLOSE] = $langs->transnoentitiesnoconv("SupplierProposalStatusClosedShort");
2306 }
2307
2308 $statusnew = '';
2309 if ($status == self::STATUS_DRAFT) {
2310 $statusnew = 'status0';
2311 } elseif ($status == self::STATUS_VALIDATED) {
2312 $statusnew = 'status1';
2313 } elseif ($status == self::STATUS_SIGNED) {
2314 $statusnew = 'status4';
2315 } elseif ($status == self::STATUS_NOTSIGNED) {
2316 $statusnew = 'status9';
2317 } elseif ($status == self::STATUS_CLOSE) {
2318 $statusnew = 'status6';
2319 }
2320
2321 return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusnew, $mode);
2322 }
2323
2324
2325 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2333 public function load_board($user, $mode)
2334 {
2335 // phpcs:enable
2336 global $conf, $langs;
2337
2338 $now = dol_now();
2339
2340 $clause = " WHERE";
2341
2342 $delay_warning = 0;
2343 if ($mode == 'opened') {
2344 $delay_warning = !empty($conf->supplier_proposal->cloture->warning_delay) ? $conf->supplier_proposal->cloture->warning_delay : 0;
2345 }
2346 if ($mode == 'signed') {
2347 $delay_warning = !empty($conf->supplier_proposal->facturation->warning_delay) ? $conf->supplier_proposal->facturation->warning_delay : 0;
2348 }
2349
2350 // The count and the number of late proposals are computed by the database instead of reading every proposal. An open
2351 // proposal is late when its closing date is before now minus the warning delay (a proposal without closing date was
2352 // counted as late, this is kept); nothing is late in the 'signed' mode.
2353 $sql = "SELECT COUNT(p.rowid) as nb,";
2354 if ($mode == 'opened') {
2355 $sql .= " SUM(CASE WHEN p.date_cloture IS NULL OR p.date_cloture < '".$this->db->idate($now - $delay_warning)."' THEN 1 ELSE 0 END) as nblate";
2356 } else {
2357 $sql .= " 0 as nblate";
2358 }
2359 $sql .= " FROM ".MAIN_DB_PREFIX."supplier_proposal as p";
2360 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
2361 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON p.fk_soc = sc.fk_soc";
2362 $sql .= " WHERE sc.fk_user = ".((int) $user->id);
2363 $clause = " AND";
2364 }
2365 $sql .= $clause." p.entity IN (".getEntity('supplier_proposal').")";
2366 if ($mode == 'opened') {
2367 $sql .= " AND p.fk_statut = 1";
2368 }
2369 if ($mode == 'signed') {
2370 $sql .= " AND p.fk_statut = 2";
2371 }
2372 if ($user->socid) {
2373 $sql .= " AND p.fk_soc = ".((int) $user->socid);
2374 }
2375
2376 $resql = $this->db->query($sql);
2377 if ($resql) {
2378 $label = $labelShort = '';
2379 $status = '';
2380 if ($mode == 'opened') {
2381 $status = self::STATUS_VALIDATED;
2382 $label = $langs->trans("SupplierProposalsToClose");
2383 $labelShort = $langs->trans("ToAcceptRefuse");
2384 }
2385 if ($mode == 'signed') {
2386 $status = self::STATUS_SIGNED;
2387 $label = $langs->trans("SupplierProposalsToProcess"); // May be billed or ordered
2388 $labelShort = $langs->trans("ToClose");
2389 }
2390
2391 $response = new WorkboardResponse();
2392 $response->warning_delay = $delay_warning / 60 / 60 / 24;
2393 $response->label = $label;
2394 $response->labelShort = $labelShort;
2395 $response->url = DOL_URL_ROOT.'/supplier_proposal/list.php?search_status='.$status;
2396 $response->img = img_object('', "propal");
2397
2398 // This assignment in condition is not a bug. It allows walking the results.
2399 // TODO Definir regle des propales a facturer en retard (mode 'signed')
2400 $obj = $this->db->fetch_object($resql);
2401 if ($obj) {
2402 $response->nbtodo = (int) $obj->nb;
2403 $response->nbtodolate = (int) $obj->nblate;
2404 }
2405 return $response;
2406 } else {
2407 $this->error = $this->db->lasterror();
2408 return -1;
2409 }
2410 }
2411
2412
2421 public function initAsSpecimen($param = array())
2422 {
2423 global $conf, $langs;
2424
2425 // Load array of products prodids
2426 $num_prods = 0;
2427 $prodids = array();
2428 $sql = "SELECT rowid";
2429 $sql .= " FROM ".MAIN_DB_PREFIX."product";
2430 $sql .= " WHERE entity IN (".getEntity('product').")";
2431 if (array_key_exists('tobuy', $param)) {
2432 $sql .= " AND tobuy = ".((int) $param['tobuy']);
2433 }
2434 $sql .= $this->db->plimit(100);
2435
2436 $resql = $this->db->query($sql);
2437 if ($resql) {
2438 $num_prods = $this->db->num_rows($resql);
2439 $i = 0;
2440 while ($i < $num_prods) {
2441 $i++;
2442 $row = $this->db->fetch_row($resql);
2443 $prodids[$i] = $row[0];
2444 }
2445 }
2446
2447 // Initialise parameters
2448 $this->id = 0;
2449 $this->ref = 'SPECIMEN';
2450 $this->ref_supplier = 'NEMICEPS';
2451 $this->specimen = 1;
2452 $this->socid = 1;
2453 $this->date = dol_now();
2454 $this->cond_reglement_id = 1;
2455 $this->cond_reglement_code = 'RECEP';
2456 $this->mode_reglement_id = 7;
2457 $this->mode_reglement_code = 'CHQ';
2458 $this->note_public = 'This is a comment (public)';
2459 $this->note_private = 'This is a comment (private)';
2460
2461 $this->multicurrency_tx = 1;
2462 $this->multicurrency_code = getDolCurrency();
2463
2464 // Lines
2465 $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)
2466 $xnbp = 0;
2467 while ($xnbp < $nbp) {
2468 $line = new SupplierProposalLine($this->db);
2469 $line->desc = $langs->trans("Description")." ".$xnbp;
2470 $line->qty = 1;
2471 $line->subprice = 100;
2472 $line->tva_tx = 19.6;
2473 $line->localtax1_tx = 0;
2474 $line->localtax2_tx = 0;
2475 if ($xnbp == 2) {
2476 $line->total_ht = 50;
2477 $line->total_ttc = 59.8;
2478 $line->total_tva = 9.8;
2479 $line->remise_percent = 50;
2480 } else {
2481 $line->total_ht = 100;
2482 $line->total_ttc = 119.6;
2483 $line->total_tva = 19.6;
2484 $line->remise_percent = 00;
2485 }
2486
2487 if ($num_prods > 0) {
2488 $prodid = mt_rand(1, $num_prods);
2489 $line->fk_product = $prodids[$prodid];
2490 }
2491
2492 $this->lines[$xnbp] = $line;
2493
2494 $this->total_ht += $line->total_ht;
2495 $this->total_tva += $line->total_tva;
2496 $this->total_ttc += $line->total_ttc;
2497
2498 $xnbp++;
2499 }
2500
2501 return 1;
2502 }
2503
2509 public function loadStateBoard()
2510 {
2511 global $user;
2512
2513 $this->nb = array();
2514 $clause = "WHERE";
2515
2516 $sql = "SELECT count(p.rowid) as nb";
2517 $sql .= " FROM ".MAIN_DB_PREFIX."supplier_proposal as p";
2518 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
2519 if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
2520 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
2521 $sql .= " WHERE sc.fk_user = ".((int) $user->id);
2522 $clause = "AND";
2523 }
2524 $sql .= " ".$clause." p.entity IN (".getEntity('supplier_proposal').")";
2525
2526 $resql = $this->db->query($sql);
2527 if ($resql) {
2528 // This assignment in condition is not a bug. It allows walking the results.
2529 while ($obj = $this->db->fetch_object($resql)) {
2530 $this->nb["supplier_proposals"] = $obj->nb;
2531 }
2532 $this->db->free($resql);
2533 return 1;
2534 } else {
2535 dol_print_error($this->db);
2536 $this->error = $this->db->lasterror();
2537 return -1;
2538 }
2539 }
2540
2541
2549 public function getNextNumRef($soc)
2550 {
2551 global $conf, $db, $langs;
2552 $langs->load("supplier_proposal");
2553
2554 if (getDolGlobalString('SUPPLIER_PROPOSAL_ADDON')) {
2555 $mybool = false;
2556
2557 $file = getDolGlobalString('SUPPLIER_PROPOSAL_ADDON') . ".php";
2558 $classname = getDolGlobalString('SUPPLIER_PROPOSAL_ADDON');
2559
2560 // Include file with class
2561 $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
2562 foreach ($dirmodels as $reldir) {
2563 $dir = dol_buildpath($reldir."core/modules/supplier_proposal/");
2564
2565 // Load file with numbering class (if found)
2566 $mybool = ((bool) @include_once $dir.$file) || $mybool;
2567 }
2568
2569 if (!$mybool) {
2570 dol_print_error(null, "Failed to include file ".$file);
2571 return '';
2572 }
2573
2574 $obj = new $classname();
2575 '@phan-var-force ModeleNumRefSupplierProposal $obj';
2577 $numref = "";
2578 $numref = $obj->getNextValue($soc, $this);
2579
2580 if ($numref != "") {
2581 return $numref;
2582 } else {
2583 $this->error = $obj->error;
2584 return "";
2585 }
2586 } else {
2587 $langs->load("errors");
2588 print $langs->trans("Error")." ".$langs->trans("ErrorModuleSetupNotComplete", $langs->transnoentitiesnoconv("SupplierProposal"));
2589 return "";
2590 }
2591 }
2592
2599 public function getTooltipContentArray($params)
2600 {
2601 global $conf, $langs;
2602
2603 $langs->load('supplier_proposal');
2604
2605 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2606 return ['optimize' => $langs->trans("ShowSupplierProposal")];
2607 }
2608
2609 //$option = $params['option'] ?? '';
2610 $datas = [];
2611
2612 $datas['picto'] = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("SupplierProposal").'</u>';
2613 if (isset($this->status)) {
2614 $datas['picto'] .= ' '.$this->getLibStatut(5);
2615 }
2616 if (!empty($this->ref)) {
2617 $datas['ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
2618 }
2619 if (!empty($this->ref_fourn)) {
2620 $datas['ref_supplier'] = '<br><b>'.$langs->trans('RefSupplier').':</b> '.$this->ref_fourn;
2621 }
2622 if (empty($params['nofetch'])) {
2623 $langs->load('companies');
2624 if (empty($this->thirdparty)) {
2625 $this->fetch_thirdparty();
2626 }
2627 if (is_object($this->thirdparty)) {
2628 $datas['supplier'] = '<br><b>'.$langs->trans('Supplier').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
2629 }
2630 }
2631 if (!empty($this->total_ht)) {
2632 $datas['amount_ht'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, getDolCurrency());
2633 }
2634 if (!empty($this->total_tva)) {
2635 $datas['amount_vat'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, getDolCurrency());
2636 }
2637 if (!empty($this->total_ttc)) {
2638 $datas['amount_ttc'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, getDolCurrency());
2639 }
2640
2641 return $datas;
2642 }
2643
2655 public function getNomUrl($withpicto = 0, $option = '', $get_params = '', $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = 0)
2656 {
2657 global $langs, $conf, $user, $hookmanager;
2658
2659 if (!empty($conf->dol_no_mouse_hover)) {
2660 $notooltip = 1; // Force disable tooltips
2661 }
2662
2663 $url = '';
2664 $result = '';
2665 $params = [
2666 'id' => $this->id,
2667 'objecttype' => $this->element,
2668 'option' => $option,
2669 ];
2670 $classfortooltip = 'classfortooltip';
2671 $dataparams = '';
2672 if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
2673 $classfortooltip = 'classforajaxtooltip';
2674 $dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
2675 $label = '';
2676 } else {
2677 $label = $this->getTooltipContent($params);
2678 }
2679
2680 if ($option == '') {
2681 $url = DOL_URL_ROOT.'/supplier_proposal/card.php?id='.$this->id.$get_params;
2682 }
2683 if ($option == 'document') {
2684 $url = DOL_URL_ROOT.'/supplier_proposal/document.php?id='.$this->id.$get_params;
2685 }
2686
2687 if ($option !== 'nolink') {
2688 // Add param to save lastsearch_values or not
2689 $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
2690 if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
2691 $add_save_lastsearch_values = 1;
2692 }
2693 if ($add_save_lastsearch_values) {
2694 $url .= '&save_lastsearch_values=1';
2695 }
2696 }
2697
2698 $linkclose = '';
2699 if (empty($notooltip) && $user->hasRight('propal', 'lire')) {
2700 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2701 $label = $langs->trans("ShowSupplierProposal");
2702 $linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
2703 }
2704 $linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
2705 $linkclose .= $dataparams.' class="'.$classfortooltip.'"';
2706 }
2707
2708 $linkstart = '<a href="'.$url.'"';
2709 $linkstart .= $linkclose.'>';
2710 $linkend = '</a>';
2711
2712 $result .= $linkstart;
2713 if ($withpicto) {
2714 $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
2715 }
2716 if ($withpicto != 2) {
2717 $result .= $this->ref;
2718 }
2719 $result .= $linkend;
2720
2721 if ($addlinktonotes) {
2722 $txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
2723 if ($txttoshow) {
2724 $notetoshow = $langs->trans("ViewPrivateNote").':<br>'.dol_string_nohtmltag($txttoshow, 1);
2725 $result .= ' <span class="note inline-block">';
2726 $result .= '<a href="'.DOL_URL_ROOT.'/supplier_proposal/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($notetoshow).'">';
2727 $result .= img_picto('', 'note');
2728 $result .= '</a>';
2729 //$result.=img_picto($langs->trans("ViewNote"),'object_generic');
2730 //$result.='</a>';
2731 $result .= '</span>';
2732 }
2733 }
2734 global $action;
2735 $hookmanager->initHooks(array($this->element . 'dao'));
2736 $parameters = array('id' => $this->id, 'getnomurl' => &$result);
2737 $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2738 if ($reshook > 0) {
2739 $result = $hookmanager->resPrint;
2740 } else {
2741 $result .= $hookmanager->resPrint;
2742 }
2743 return $result;
2744 }
2745
2751 public function getLinesArray()
2752 {
2753 // For other object, here we call fetch_lines. But fetch_lines does not exists on supplier proposal
2754
2755 $sql = 'SELECT pt.rowid, pt.label as custom_label, pt.description, pt.fk_product, pt.fk_remise_except,';
2756 $sql .= ' pt.qty, pt.tva_tx, pt.vat_src_code, pt.remise_percent, pt.subprice, pt.info_bits,';
2757 $sql .= ' pt.total_ht, pt.total_tva, pt.total_ttc, pt.fk_product_fournisseur_price as fk_fournprice, pt.buy_price_ht as pa_ht, pt.special_code, pt.localtax1_tx, pt.localtax2_tx,';
2758 $sql .= ' pt.product_type, pt.rang, pt.fk_parent_line, pt.extraparams,';
2759 $sql .= ' p.label as product_label, p.ref, p.fk_product_type, p.rowid as prodid,';
2760 $sql .= ' p.description as product_desc, pt.ref_fourn as ref_supplier,';
2761 $sql .= ' pt.fk_multicurrency, pt.multicurrency_code, pt.multicurrency_subprice, pt.multicurrency_total_ht, pt.multicurrency_total_tva, pt.multicurrency_total_ttc, pt.fk_unit';
2762 $sql .= ' FROM '.MAIN_DB_PREFIX.'supplier_proposaldet as pt';
2763 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON pt.fk_product=p.rowid';
2764 $sql .= ' WHERE pt.fk_supplier_proposal = '.((int) $this->id);
2765 $sql .= ' ORDER BY pt.rang ASC, pt.rowid';
2766
2767 dol_syslog(get_class($this).'::getLinesArray', LOG_DEBUG);
2768 $resql = $this->db->query($sql);
2769 if ($resql) {
2770 $num = $this->db->num_rows($resql);
2771 $i = 0;
2772
2773 while ($i < $num) {
2774 $obj = $this->db->fetch_object($resql);
2775
2776 $this->lines[$i] = new SupplierProposalLine($this->db);
2777 $this->lines[$i]->id = $obj->rowid; // for backward compatibility
2778 $this->lines[$i]->rowid = $obj->rowid;
2779
2780 $this->lines[$i]->label = $obj->custom_label;
2781 $this->lines[$i]->description = $obj->description; // deprecated
2782 $this->lines[$i]->desc = $obj->description;
2783
2784 $this->lines[$i]->fk_product = $obj->fk_product;
2785 $this->lines[$i]->ref = $obj->ref;
2786 $this->lines[$i]->product_label = $obj->product_label;
2787 $this->lines[$i]->product_desc = $obj->product_desc;
2788 $this->lines[$i]->fk_product_type = $obj->fk_product_type; // deprecated
2789 $this->lines[$i]->product_type = $obj->product_type;
2790 $this->lines[$i]->qty = $obj->qty;
2791 $this->lines[$i]->subprice = $obj->subprice;
2792 $this->lines[$i]->fk_remise_except = $obj->fk_remise_except;
2793 $this->lines[$i]->remise_percent = $obj->remise_percent;
2794 $this->lines[$i]->tva_tx = $obj->tva_tx;
2795 $this->lines[$i]->vat_src_code = $obj->vat_src_code;
2796 $this->lines[$i]->info_bits = $obj->info_bits;
2797 $this->lines[$i]->total_ht = $obj->total_ht;
2798 $this->lines[$i]->total_tva = $obj->total_tva;
2799 $this->lines[$i]->total_ttc = $obj->total_ttc;
2800 $this->lines[$i]->fk_fournprice = $obj->fk_fournprice;
2801 $marginInfos = getMarginInfos($obj->subprice, $obj->remise_percent, $obj->tva_tx, $obj->localtax1_tx, $obj->localtax2_tx, $this->lines[$i]->fk_fournprice, $obj->pa_ht);
2802 $this->lines[$i]->pa_ht = $marginInfos[0];
2803 $this->lines[$i]->marge_tx = $marginInfos[1];
2804 $this->lines[$i]->marque_tx = $marginInfos[2];
2805 $this->lines[$i]->fk_parent_line = $obj->fk_parent_line;
2806 $this->lines[$i]->special_code = $obj->special_code;
2807 $this->lines[$i]->rang = $obj->rang;
2808
2809 $this->lines[$i]->ref_fourn = $obj->ref_supplier; // deprecated
2810 $this->lines[$i]->ref_supplier = $obj->ref_supplier;
2811
2812 // Multicurrency
2813 $this->lines[$i]->fk_multicurrency = $obj->fk_multicurrency;
2814 $this->lines[$i]->multicurrency_code = $obj->multicurrency_code;
2815 $this->lines[$i]->multicurrency_subprice = $obj->multicurrency_subprice;
2816 $this->lines[$i]->multicurrency_total_ht = $obj->multicurrency_total_ht;
2817 $this->lines[$i]->multicurrency_total_tva = $obj->multicurrency_total_tva;
2818 $this->lines[$i]->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
2819 $this->lines[$i]->fk_unit = $obj->fk_unit;
2820 $this->lines[$i]->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
2821
2822 $this->lines[$i]->fetch_optionals();
2823
2824 $i++;
2825 }
2826 $this->db->free($resql);
2827
2828 return 1;
2829 } else {
2830 $this->error = $this->db->error();
2831 return -1;
2832 }
2833 }
2834
2846 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
2847 {
2848 global $langs;
2849
2850 $langs->load("supplier_proposal");
2851 $outputlangs->load("products");
2852
2853 if (!dol_strlen($modele)) {
2854 $modele = ''; // On supplier documents, template can be empty( no doc generated in this case)
2855
2856 if ($this->model_pdf) {
2857 $modele = $this->model_pdf;
2858 } elseif (getDolGlobalString('SUPPLIER_PROPOSAL_ADDON_PDF')) {
2859 $modele = getDolGlobalString('SUPPLIER_PROPOSAL_ADDON_PDF');
2860 }
2861 }
2862
2863 if (empty($modele)) {
2864 return 1;
2865 }
2866
2867 $modelpath = "core/modules/supplier_proposal/doc/";
2868
2869 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
2870 }
2871
2872
2881 public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2882 {
2883 $tables = array(
2884 'supplier_proposal'
2885 );
2886
2887 return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
2888 }
2889
2898 public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
2899 {
2900 $tables = array(
2901 'supplier_proposaldet'
2902 );
2903
2904 return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
2905 }
2906
2907
2915 public function getKanbanView($option = '', $arraydata = null)
2916 {
2917 global $langs;
2918
2919 $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2920
2921 $return = '<div class="box-flex-item box-flex-grow-zero">';
2922 $return .= '<div class="info-box info-box-sm">';
2923 $return .= '<span class="info-box-icon bg-infobox-action">';
2924 $return .= img_picto('', $this->picto);
2925 //$return .= '<i class="fa fa-dol-action"></i>'; // Can be image
2926 $return .= '</span>';
2927 $return .= '<div class="info-box-content">';
2928 $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">'.(method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref).'</span>';
2929 if ($selected >= 0) {
2930 $return .= '<input id="cb'.$this->id.'" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="'.$this->id.'"'.($selected ? ' checked="checked"' : '').'>';
2931 }
2932 if (property_exists($this, 'socid')) {
2933 $return .= '<span class="info-box-ref"> | '.$this->socid.'</span>';
2934 }
2935 if (property_exists($this, 'delivery_date')) {
2936 $return .= '<br><span class="opacitymedium">'.$langs->trans("DateEnd").'</span> : <span class="info-box-label">'.dol_print_date($this->delivery_date).'</span>';
2937 }
2938 if (property_exists($this, 'total_ttc')) {
2939 $return .= '<br><span class="opacitymedium" >'.$langs->trans("AmountHT").' : </span><span class="info-box-label amount">'.price($this->total_ttc).'</span>';
2940 }
2941 if (method_exists($this, 'getLibStatut')) {
2942 $return .= '<br><div class="info-box-status">'.$this->getLibStatut(3).'</div>';
2943 }
2944 $return .= '</div>';
2945 $return .= '</div>';
2946 $return .= '</div>';
2947 return $return;
2948 }
2949
2960 public function setCategories($categories)
2961 {
2962 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
2963 return parent::setCategoriesCommon($categories, Categorie::TYPE_SUPPLIER_PROPOSAL);
2964 }
2965}
2966
2967
2972{
2976 public $db;
2977
2981 public $error = '';
2982
2986 public $element = 'supplier_proposaldet';
2987
2991 public $table_element = 'supplier_proposaldet';
2992
2996 public $parent_element = 'supplier_proposal';
2997
3001 public $fk_parent_attribute = 'fk_supplier_proposal';
3002
3006 public $oldline;
3007
3011 public $id;
3012
3016 public $fk_supplier_proposal;
3017
3021 public $fk_parent_line;
3022
3026 public $desc; // Description of the line
3027
3031 public $fk_product; // Predefined Product ID
3032
3038 public $fk_product_type;
3044 public $product_type = Product::TYPE_PRODUCT;
3045
3049 public $qty;
3053 public $tva_tx;
3057 public $vat_src_code;
3058
3062 public $remise_percent;
3063
3067 public $fk_remise_except;
3068
3072 public $rang = 0;
3073
3077 public $fk_fournprice;
3078
3082 public $pa_ht;
3086 public $marge_tx;
3090 public $marque_tx;
3091
3095 public $special_code; // Tag for special lines (exclusive tags)
3096 // 1: frais de port
3097 // 2: ecotaxe
3098 // 3: option line (when qty = 0)
3099
3103 public $info_bits = 0; // Liste d'options cumulables:
3104 // Bit 0: 0 if TVA normal - 1 if TVA NPR
3105 // Bit 1: 0 normal line - 1 if fixed reduction
3106
3110 public $total_ht; // Line Net Total (HT), including all quantity and the line discount
3114 public $total_tva; // Line Net Total (VAT), including all quantity and the line discount
3118 public $total_ttc; // Line Net Total (TTC), including all quantity and the line discount
3119
3123 public $date_start;
3127 public $date_end;
3128
3129 // From llx_product
3135 public $ref;
3136
3141 public $product_ref;
3142
3148 public $libelle;
3149
3154 public $product_label;
3155
3160 public $label;
3161
3166 public $product_desc;
3167
3171 public $localtax1_tx; // Local tax 1
3175 public $localtax2_tx; // Local tax 2
3179 public $localtax1_type; // Local tax 1 type
3183 public $localtax2_type; // Local tax 2 type
3187 public $total_localtax1; // Line total local tax 1
3191 public $total_localtax2; // Line total local tax 2
3192
3196 public $skip_update_total; // Skip update price total for special lines
3197
3201 public $ref_fourn;
3205 public $ref_supplier;
3206
3207 // Multicurrency
3211 public $fk_multicurrency;
3212
3216 public $multicurrency_code;
3220 public $multicurrency_subprice;
3224 public $multicurrency_total_ht;
3228 public $multicurrency_total_tva;
3232 public $multicurrency_total_ttc;
3233
3239 public function __construct($db)
3240 {
3241 $this->db = $db;
3242 }
3243
3250 public function fetch($rowid)
3251 {
3252 $sql = 'SELECT pd.rowid, pd.fk_supplier_proposal, pd.fk_parent_line, pd.fk_product, pd.label as custom_label, pd.description, pd.price, pd.qty, pd.tva_tx,';
3253 $sql .= ' pd.date_start, pd.date_end,';
3254 $sql .= ' pd.remise, pd.remise_percent, pd.fk_remise_except, pd.subprice, pd.subprice_ttc,';
3255 $sql .= ' pd.info_bits, pd.total_ht, pd.total_tva, pd.total_ttc, pd.fk_product_fournisseur_price as fk_fournprice, pd.buy_price_ht as pa_ht, pd.special_code, pd.rang,';
3256 $sql .= ' pd.localtax1_tx, pd.localtax2_tx, pd.total_localtax1, pd.total_localtax2,';
3257 $sql .= ' p.ref as product_ref, p.label as product_label, p.description as product_desc,';
3258 $sql .= ' pd.product_type, pd.ref_fourn as ref_produit_fourn, pd.extraparams,';
3259 $sql .= ' pd.fk_multicurrency, pd.multicurrency_code, pd.multicurrency_subprice, pd.multicurrency_subprice_ttc, pd.multicurrency_total_ht, pd.multicurrency_total_tva, pd.multicurrency_total_ttc, pd.fk_unit';
3260 $sql .= ' FROM '.MAIN_DB_PREFIX.'supplier_proposaldet as pd';
3261 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON pd.fk_product = p.rowid';
3262 $sql .= ' WHERE pd.rowid = '.((int) $rowid);
3263
3264 $result = $this->db->query($sql);
3265 if ($result) {
3266 if ($objp = $this->db->fetch_object($result)) {
3267 $this->id = $objp->rowid;
3268 $this->fk_supplier_proposal = $objp->fk_supplier_proposal;
3269 $this->fk_parent_line = $objp->fk_parent_line;
3270 $this->label = $objp->custom_label;
3271 $this->desc = $objp->description;
3272 $this->qty = $objp->qty;
3273
3274 $this->subprice = $objp->subprice;
3275 $this->subprice_ttc = $objp->subprice_ttc;
3276
3277 $this->tva_tx = $objp->tva_tx;
3278 $this->remise_percent = $objp->remise_percent;
3279 $this->fk_remise_except = $objp->fk_remise_except;
3280 $this->fk_product = $objp->fk_product;
3281 $this->info_bits = $objp->info_bits;
3282 $this->date_start = $this->db->jdate($objp->date_start);
3283 $this->date_end = $this->db->jdate($objp->date_end);
3284
3285 $this->total_ht = $objp->total_ht;
3286 $this->total_tva = $objp->total_tva;
3287 $this->total_ttc = $objp->total_ttc;
3288
3289 $this->fk_fournprice = $objp->fk_fournprice;
3290
3291 $marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $this->fk_fournprice, $objp->pa_ht);
3292 $this->pa_ht = $marginInfos[0];
3293 $this->marge_tx = $marginInfos[1];
3294 $this->marque_tx = $marginInfos[2];
3295
3296 $this->special_code = $objp->special_code;
3297 $this->product_type = $objp->product_type;
3298 $this->rang = $objp->rang;
3299
3300 $this->product_ref = $objp->product_ref;
3301 $this->product_label = $objp->product_label;
3302 $this->product_desc = $objp->product_desc;
3303
3304 $this->ref_fourn = $objp->ref_produit_fourn;
3305
3306 $this->extraparams = !empty($objp->extraparams) ? (array) json_decode($objp->extraparams, true) : array();
3307
3308 // Multicurrency
3309 $this->fk_multicurrency = $objp->fk_multicurrency;
3310 $this->multicurrency_code = $objp->multicurrency_code;
3311 $this->multicurrency_subprice = $objp->multicurrency_subprice;
3312 $this->multicurrency_subprice_ttc = $objp->multicurrency_subprice_ttc;
3313 $this->multicurrency_total_ht = $objp->multicurrency_total_ht;
3314 $this->multicurrency_total_tva = $objp->multicurrency_total_tva;
3315 $this->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
3316 $this->fk_unit = $objp->fk_unit;
3317
3318 $this->db->free($result);
3319 return 1;
3320 }
3321 return 0;
3322 } else {
3323 dol_print_error($this->db);
3324 return -1;
3325 }
3326 }
3327
3334 public function insert($notrigger = 0)
3335 {
3336 global $user;
3337
3338 $error = 0;
3339
3340 dol_syslog(get_class($this)."::insert rang=".$this->rang);
3341
3342 // Clean parameters
3343 if (empty($this->tva_tx)) {
3344 $this->tva_tx = 0;
3345 }
3346 if (empty($this->vat_src_code)) {
3347 $this->vat_src_code = '';
3348 }
3349 if (empty($this->localtax1_tx)) {
3350 $this->localtax1_tx = 0;
3351 }
3352 if (empty($this->localtax2_tx)) {
3353 $this->localtax2_tx = 0;
3354 }
3355 if (empty($this->localtax1_type)) {
3356 $this->localtax1_type = '';
3357 }
3358 if (empty($this->localtax2_type)) {
3359 $this->localtax2_type = '';
3360 }
3361 if (empty($this->total_localtax1)) {
3362 $this->total_localtax1 = 0;
3363 }
3364 if (empty($this->total_localtax2)) {
3365 $this->total_localtax2 = 0;
3366 }
3367 if (empty($this->rang)) {
3368 $this->rang = 0;
3369 }
3370 if (empty($this->remise_percent)) {
3371 $this->remise_percent = 0;
3372 }
3373 if (empty($this->info_bits)) {
3374 $this->info_bits = 0;
3375 }
3376 if (empty($this->special_code)) {
3377 $this->special_code = 0;
3378 }
3379 if (empty($this->fk_parent_line)) {
3380 $this->fk_parent_line = 0;
3381 }
3382 if (empty($this->fk_fournprice)) {
3383 $this->fk_fournprice = 0;
3384 }
3385 if (empty($this->fk_unit)) {
3386 $this->fk_unit = 0;
3387 }
3388 if (empty($this->subprice)) {
3389 $this->subprice = 0;
3390 }
3391
3392 if (empty($this->pa_ht)) {
3393 $this->pa_ht = 0;
3394 }
3395
3396 // if buy price not defined, define buyprice as configured in margin admin
3397 if ($this->pa_ht == 0) {
3398 $result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product, $this->qty);
3399 if ($result < 0) {
3400 return $result;
3401 } else {
3402 $this->pa_ht = $result;
3403 }
3404 }
3405
3406 // Check parameters
3407 if ($this->product_type < 0) {
3408 return -1;
3409 }
3410
3411 $this->db->begin();
3412
3413 // Insert line into database
3414 $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'supplier_proposaldet';
3415 $sql .= ' (fk_supplier_proposal, fk_parent_line, label, description, fk_product, product_type,';
3416 $sql .= ' date_start, date_end,';
3417 $sql .= ' fk_remise_except, qty, tva_tx, vat_src_code, localtax1_tx, localtax2_tx, localtax1_type, localtax2_type,';
3418 $sql .= ' subprice, subprice_ttc, remise_percent, ';
3419 $sql .= ' info_bits, ';
3420 $sql .= ' total_ht, total_tva, total_localtax1, total_localtax2, total_ttc, fk_product_fournisseur_price, buy_price_ht, special_code, rang,';
3421 $sql .= ' ref_fourn,';
3422 $sql .= ' fk_multicurrency, multicurrency_code, multicurrency_subprice, multicurrency_subprice_ttc, multicurrency_total_ht, multicurrency_total_tva, multicurrency_total_ttc, fk_unit)';
3423 $sql .= " VALUES (".((int) $this->fk_supplier_proposal).",";
3424 $sql .= " ".($this->fk_parent_line > 0 ? ((int) $this->fk_parent_line) : "null").",";
3425 $sql .= " ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null").",";
3426 $sql .= " '".$this->db->escape($this->desc)."',";
3427 $sql .= " ".($this->fk_product ? ((int) $this->fk_product) : "null").",";
3428 $sql .= " ".((int) $this->product_type).",";
3429 $sql .= " ".($this->date_start ? "'".$this->db->idate($this->date_start)."'" : "null").",";
3430 $sql .= " ".($this->date_end ? "'".$this->db->idate($this->date_end)."'" : "null").",";
3431 $sql .= " ".($this->fk_remise_except ? ((int) $this->fk_remise_except) : "null").",";
3432 $sql .= " ".(float) price2num($this->qty, 'MS').",";
3433 $sql .= " ".(float) price2num($this->tva_tx).",";
3434 $sql .= " '".$this->db->escape($this->vat_src_code)."',";
3435 $sql .= " ".(float) price2num($this->localtax1_tx).",";
3436 $sql .= " ".(float) price2num($this->localtax2_tx).",";
3437 $sql .= " '".$this->db->escape($this->localtax1_type)."',";
3438 $sql .= " '".$this->db->escape($this->localtax2_type)."',";
3439 $sql .= " ".(float) price2num($this->subprice, 'MU') .",";
3440 $sql .= " ".(float) price2num($this->subprice_ttc, 'MU') .",";
3441 $sql .= " ".((float) $this->remise_percent).",";
3442 $sql .= " ".(isset($this->info_bits) ? ((int) $this->info_bits) : "null").",";
3443 $sql .= " ".(float) price2num($this->total_ht, 'MT').",";
3444 $sql .= " ".(float) price2num($this->total_tva, 'MT').",";
3445 $sql .= " ".(float) price2num($this->total_localtax1, 'MT').",";
3446 $sql .= " ".(float) price2num($this->total_localtax2, 'MT').",";
3447 $sql .= " ".(float) price2num($this->total_ttc, 'MT').",";
3448 $sql .= " ".(!empty($this->fk_fournprice) ? ((int) $this->fk_fournprice) : "null").",";
3449 $sql .= " ".(isset($this->pa_ht) ? (float) price2num($this->pa_ht, 'MU') : "null").",";
3450 $sql .= ' '.((int) $this->special_code).',';
3451 $sql .= ' '.((int) $this->rang).',';
3452 $sql .= " '".$this->db->escape($this->ref_fourn)."'";
3453 $sql .= ", ".($this->fk_multicurrency > 0 ? ((int) $this->fk_multicurrency) : 'null');
3454 $sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
3455 $sql .= ", ".(float) price2num($this->multicurrency_subprice, 'CU');
3456 $sql .= ", ".(float) price2num($this->multicurrency_subprice_ttc, 'CU');
3457 $sql .= ", ".(float) price2num($this->multicurrency_total_ht, 'CT');
3458 $sql .= ", ".(float) price2num($this->multicurrency_total_tva, 'CT');
3459 $sql .= ", ".(float) price2num($this->multicurrency_total_ttc, 'CT');
3460 $sql .= ", ".($this->fk_unit ? ((int) $this->fk_unit) : 'null');
3461 $sql .= ')';
3462
3463 dol_syslog(get_class($this).'::insert', LOG_DEBUG);
3464 $resql = $this->db->query($sql);
3465 if ($resql) {
3466 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'supplier_proposaldet');
3467
3468 if (!$error) {
3469 $result = $this->insertExtraFields();
3470 if ($result < 0) {
3471 $error++;
3472 }
3473 }
3474
3475 if (!$error && !$notrigger) {
3476 // Call trigger
3477 $result = $this->call_trigger('LINESUPPLIER_PROPOSAL_INSERT', $user);
3478 if ($result < 0) {
3479 $this->db->rollback();
3480 return -1;
3481 }
3482 // End call triggers
3483 }
3484
3485 $this->db->commit();
3486 return 1;
3487 } else {
3488 $this->error = $this->db->error()." sql=".$sql;
3489 $this->db->rollback();
3490 return -1;
3491 }
3492 }
3493
3500 public function delete($user)
3501 {
3502 $error = 0;
3503
3504 $this->db->begin();
3505
3506 $sql = "DELETE FROM ".MAIN_DB_PREFIX."supplier_proposaldet";
3507 $sql .= " WHERE rowid = ".((int) $this->id);
3508
3509 if ($this->db->query($sql)) {
3510 // Remove extrafields
3511 if (!$error) {
3512 $result = $this->deleteExtraFields();
3513 if ($result < 0) {
3514 $error++;
3515 dol_syslog(get_class($this)."::delete error -4 ".$this->error, LOG_ERR);
3516 }
3517 }
3518
3519 // Call trigger
3520 $result = $this->call_trigger('LINESUPPLIER_PROPOSAL_DELETE', $user);
3521 if ($result < 0) {
3522 $this->db->rollback();
3523 return -1;
3524 }
3525 // End call triggers
3526
3527 $this->db->commit();
3528
3529 return 1;
3530 } else {
3531 $this->error = $this->db->error()." sql=".$sql;
3532 $this->db->rollback();
3533 return -1;
3534 }
3535 }
3536
3543 public function update($notrigger = 0)
3544 {
3545 global $user;
3546
3547 $error = 0;
3548
3549 // Clean parameters
3550 if (empty($this->tva_tx)) {
3551 $this->tva_tx = 0;
3552 }
3553 if (empty($this->localtax1_tx)) {
3554 $this->localtax1_tx = 0;
3555 }
3556 if (empty($this->localtax2_tx)) {
3557 $this->localtax2_tx = 0;
3558 }
3559 if (empty($this->total_localtax1)) {
3560 $this->total_localtax1 = 0;
3561 }
3562 if (empty($this->total_localtax2)) {
3563 $this->total_localtax2 = 0;
3564 }
3565 if (empty($this->localtax1_type)) {
3566 $this->localtax1_type = '';
3567 }
3568 if (empty($this->localtax2_type)) {
3569 $this->localtax2_type = '';
3570 }
3571 if (empty($this->marque_tx)) {
3572 $this->marque_tx = 0;
3573 }
3574 if (empty($this->marge_tx)) {
3575 $this->marge_tx = 0;
3576 }
3577 if (empty($this->remise_percent)) {
3578 $this->remise_percent = 0;
3579 }
3580 if (empty($this->info_bits)) {
3581 $this->info_bits = 0;
3582 }
3583 if (empty($this->special_code)) {
3584 $this->special_code = 0;
3585 }
3586 if (empty($this->fk_parent_line)) {
3587 $this->fk_parent_line = 0;
3588 }
3589 if (empty($this->fk_fournprice)) {
3590 $this->fk_fournprice = 0;
3591 }
3592 if (empty($this->fk_unit)) {
3593 $this->fk_unit = 0;
3594 }
3595 if (empty($this->subprice)) {
3596 $this->subprice = 0;
3597 }
3598
3599 if (empty($this->pa_ht)) {
3600 $this->pa_ht = 0;
3601 }
3602
3603 // if buy price not defined, define buyprice as configured in margin admin
3604 if ($this->pa_ht == 0) {
3605 $result = $this->defineBuyPrice($this->subprice, $this->remise_percent, $this->fk_product, $this->qty);
3606 if ($result < 0) {
3607 return $result;
3608 } else {
3609 $this->pa_ht = $result;
3610 }
3611 }
3612
3613 $this->db->begin();
3614
3615 // Update line in database
3616 $sql = "UPDATE ".MAIN_DB_PREFIX."supplier_proposaldet SET";
3617 $sql .= " description = '".$this->db->escape($this->desc)."'";
3618 $sql .= " , label = ".(!empty($this->label) ? "'".$this->db->escape($this->label)."'" : "null");
3619 $sql .= " , product_type = ".((int) $this->product_type);
3620 $sql .= " , date_start = ".($this->date_start ? "'".$this->db->idate($this->date_start)."'" : "null");
3621 $sql .= " , date_end = ".($this->date_end ? "'".$this->db->idate($this->date_end)."'" : "null");
3622 $sql .= " , tva_tx = '".price2num($this->tva_tx)."'";
3623 $sql .= " , localtax1_tx = ".(float) price2num($this->localtax1_tx);
3624 $sql .= " , localtax2_tx = ".(float) price2num($this->localtax2_tx);
3625 $sql .= " , localtax1_type = '".$this->db->escape($this->localtax1_type)."'";
3626 $sql .= " , localtax2_type = '".$this->db->escape($this->localtax2_type)."'";
3627 $sql .= " , qty = ".(float) price2num($this->qty);
3628 $sql .= " , subprice = ".(float) price2num($this->subprice);
3629 $sql .= " , subprice_ttc = ".(float) price2num($this->subprice_ttc);
3630 $sql .= " , remise_percent = ".(float) price2num($this->remise_percent);
3631 $sql .= " , info_bits = ".((int) $this->info_bits);
3632 if (empty($this->skip_update_total)) {
3633 $sql .= " , total_ht = ".(float) price2num($this->total_ht);
3634 $sql .= " , total_tva = ".(float) price2num($this->total_tva);
3635 $sql .= " , total_ttc = ".(float) price2num($this->total_ttc);
3636 $sql .= " , total_localtax1 = ".(float) price2num($this->total_localtax1);
3637 $sql .= " , total_localtax2 = ".(float) price2num($this->total_localtax2);
3638 }
3639 $sql .= " , fk_product_fournisseur_price = ".(!empty($this->fk_fournprice) ? "'".$this->db->escape((string) $this->fk_fournprice)."'" : "null");
3640 $sql .= " , buy_price_ht = ".(float) price2num($this->pa_ht);
3641 $sql .= " , special_code = ".((int) $this->special_code);
3642 $sql .= " , fk_parent_line = ".($this->fk_parent_line > 0 ? ((int) $this->fk_parent_line) : "null");
3643 if (!empty($this->rang)) {
3644 $sql .= ", rang = ".((int) $this->rang);
3645 }
3646 $sql .= " , ref_fourn = ".(!empty($this->ref_fourn) ? "'".$this->db->escape($this->ref_fourn)."'" : "null");
3647 $sql .= " , fk_unit = ".($this->fk_unit ? (int) $this->fk_unit : 'null');
3648
3649 // Multicurrency
3650 $sql .= " , multicurrency_subprice = ".(float) price2num($this->multicurrency_subprice);
3651 $sql .= " , multicurrency_subprice_ttc = ".(float) price2num($this->multicurrency_subprice_ttc);
3652 $sql .= " , multicurrency_total_ht = ".(float) price2num($this->multicurrency_total_ht);
3653 $sql .= " , multicurrency_total_tva = ".(float) price2num($this->multicurrency_total_tva);
3654 $sql .= " , multicurrency_total_ttc = ".(float) price2num($this->multicurrency_total_ttc);
3655
3656 $sql .= " WHERE rowid = ".((int) $this->id);
3657
3658 dol_syslog(get_class($this)."::update", LOG_DEBUG);
3659 $resql = $this->db->query($sql);
3660 if ($resql) {
3661 if (!$error) {
3662 $result = $this->insertExtraFields();
3663 if ($result < 0) {
3664 $error++;
3665 }
3666 }
3667
3668 if (!$error && !$notrigger) {
3669 // Call trigger
3670 $result = $this->call_trigger('LINESUPPLIER_PROPOSAL_MODIFY', $user);
3671 if ($result < 0) {
3672 $this->db->rollback();
3673 return -1;
3674 }
3675 // End call triggers
3676 }
3677
3678 $this->db->commit();
3679 return 1;
3680 } else {
3681 $this->error = $this->db->error();
3682 $this->db->rollback();
3683 return -2;
3684 }
3685 }
3686
3687 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3694 public function update_total()
3695 {
3696 // phpcs:enable
3697 $this->db->begin();
3698
3699 // Update line in database
3700 $sql = "UPDATE ".MAIN_DB_PREFIX."supplier_proposaldet SET";
3701 $sql .= " total_ht = ".(float) price2num($this->total_ht, 'MT');
3702 $sql .= ",total_tva = ".(float) price2num($this->total_tva, 'MT');
3703 $sql .= ",total_ttc = ".(float) price2num($this->total_ttc, 'MT');
3704 $sql .= " WHERE rowid = ".((int) $this->id);
3705
3706 dol_syslog("SupplierProposalLine::update_total", LOG_DEBUG);
3707
3708 $resql = $this->db->query($sql);
3709 if ($resql) {
3710 $this->db->commit();
3711 return 1;
3712 } else {
3713 $this->error = $this->db->error();
3714 $this->db->rollback();
3715 return -2;
3716 }
3717 }
3718}
$object ref
Definition info.php:90
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this->array_options This method is in most cases call...
line_order($renum=false, $rowidorder='ASC', $fk_parent_line=true)
Save a new position (field rang) for details lines.
deleteEcmFiles($mode=0)
Delete related files of object in database.
getTooltipContent($params)
getTooltipContent
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.
roundQtyToPackaging($qty, $packaging)
Round a quantity up to the next multiple of a packaging quantity (options PRODUCT_USE_CUSTOMER_PACKAG...
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
fetch_thirdparty($force_thirdparty_id=0)
Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty.
deleteObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $rowid=0, $f_user=null, $notrigger=0)
Delete all links between an object $this.
setErrorsFromObject($object)
setErrorsFromObject
isLineOfObject($lineid)
Check that a line belongs to this object, using $this->table_element_line and $this->fk_element.
static isExistingObject($element, $id, $ref='', $ref_ext='')
Check if an object id or ref exists If you don't need or want to instantiate the object and just need...
updateRangOfLine($rowid, $rang)
Update position of line (rang)
checkActiveProductInLines($status='onsale')
Check if all products have the right status (on sale, on buy) called during validation of propal,...
deleteExtraFields()
Delete all extra fields values for the current object.
static commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
static commonReplaceProduct(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a product id with another one.
line_max($fk_parent_line=0)
Get max value used for position of line (rang)
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
defineBuyPrice($unitPrice=0.0, $discountPercent=0.0, $fk_product=0, $qty=0)
Get buy price to use for margin calculation.
add_contact($fk_socpeople, $type_contact, $source='external', $notrigger=0)
Add a link between element $this->element and a contact.
Parent class for class inheritance lines of business objects This class is useless for the moment so ...
Class to manage absolute discounts.
Class to manage Dolibarr database access.
Class Currency.
static getIdAndTxFromCode($dbs, $code, $date_document=0)
Get id and rate of currency from code.
Class to manage predefined suppliers products.
Class to manage products or services.
const TYPE_PRODUCT
Regular product.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage price ask supplier.
updatePriceFournisseur($idProductFournPrice, $product, $user)
Update ProductFournisseur.
updateline($rowid, $pu, $qty, $remise_percent, $txtva, $txlocaltax1=0, $txlocaltax2=0, $desc='', $price_base_type='HT', $info_bits=0, $special_code=0, $fk_parent_line=0, $skip_update_total=0, $fk_fournprice=0, $pa_ht=0, $label='', $type=0, $array_options=[], $ref_supplier='', $fk_unit=0, $pu_ht_devise=0)
Update a proposal line.
cloture($user, $status, $note)
Close the askprice.
initAsSpecimen($param=array())
Initialise an instance with random values.
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0, $moreparams=null)
Create a document onto disk according to template module.
addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1=0, $txlocaltax2=0, $fk_product=0, $remise_percent=0, $price_base_type='HT', $pu_ttc=0, $info_bits=0, $type=0, $rang=-1, $special_code=0, $fk_parent_line=0, $fk_fournprice=0, $pa_ht=0, $label='', $array_options=[], $ref_supplier='', $fk_unit=0, $origin='', $origin_id=0, $pu_ht_devise=0, $date_start=0, $date_end=0)
Add a proposal line into database (linked to product/service or not) The parameters are already suppo...
info($id)
Object SupplierProposal Information.
create($user, $notrigger=0)
Create commercial proposal into database this->ref can be set or empty.
insert_discount($idremise)
Adding line of fixed discount in the proposal in DB.
reopen($user, $status, $note='', $notrigger=0)
Reopen the commercial proposal.
static replaceProduct(DoliDB $db, $origin_id, $dest_id)
Function used to replace a product id with another one.
fetch($rowid, $ref='')
Load a proposal from database and its lines array.
load_board($user, $mode)
Load indicators for dashboard (this->nbtodo and this->nbtodolate)
createFromClone(User $user, $fromid=0)
Load an object from its id and create a new one in database.
const STATUS_NOTSIGNED
Not signed quote, canceled.
const STATUS_DRAFT
Draft status.
liste_array($shortlist=0, $draft=0, $notcurrentuser=0, $socid=0, $limit=0, $offset=0, $sortfield='p.datec', $sortorder='DESC')
Return list of askprice (eventually filtered on user) into an array.
setDeliveryDate($user, $delivery_date)
Set delivery date.
__construct($db, $socid=0, $supplier_proposalid=0)
Constructor.
LibStatut($status, $mode=1)
Return label of a status (draft, validated, ...)
setCategories($categories)
Sets object to supplied categories.
static replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
updateOrCreatePriceFournisseur($user)
Add or update supplier price according to result of proposal.
loadStateBoard()
Load indicator this->nb of global stats widget.
deleteLine($lineid)
Delete detail line.
const STATUS_VALIDATED
Validated status.
createPriceFournisseur($product, $user)
Create Price Fournisseur.
getLibStatut($mode=0)
Return label of status of proposal (draft, validated, ...)
add_product($idproduct, $qty, $remise_percent=0)
Add line into array ->lines.
set_date_livraison($user, $delivery_date)
Set delivery date.
const STATUS_SIGNED
Signed quote.
getNomUrl($withpicto=0, $option='', $get_params='', $notooltip=0, $save_lastsearch_value=-1, $addlinktonotes=0)
Return clickable link of object (with eventually picto)
getKanbanView($option='', $arraydata=null)
Return clickable link of object (with eventually picto)
getTooltipContentArray($params)
getTooltipContentArray
valid($user, $notrigger=0)
Set status to validated.
const STATUS_CLOSE
Billed or closed/processed quote.
getLinesArray()
Retrieve an array of supplier proposal lines.
setDraft($user)
Set draft status.
Class to manage supplier_proposal lines.
fetch($rowid)
Retrieve the propal line object.
insert($notrigger=0)
Insert object line propal in database.
update_total()
Update DB line fields total_xxx Used by migration.
update($notrigger=0)
Update propal line object into DB.
__construct($db)
Class line Constructor.
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
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
dol_delete_file($file, $disableglob=0, $nophperrors=0, $nohook=0, $object=null, $allowdotdot=false, $indexdatabase=1, $nolog=0)
Remove a file or several files with a mask.
dol_delete_dir_recursive($dir, $count=0, $nophperrors=0, $onlysub=0, &$countdeleted=0, $indexdatabase=1, $nolog=0, $level=0)
Remove a directory $dir and its subdirectories (or only files and subdirectories)
dol_dir_list($utf8_path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0, $nbsecondsold=0)
Scan a directory and return a list of files/directories.
Definition files.lib.php:65
dol_delete_preview($object)
Delete all preview files linked to object instance.
$date_start
Variables from include:
dol_now($mode='gmt')
Return date for now.
dol_string_nohtmltag($stringtoclean, $removelinefeed=1, $pagecodeto='UTF-8', $strip_tags=0, $removedoublespaces=1)
Clean a string from all HTML tags and entities.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0, $allowdash=0)
Clean a string to use it as a file name.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
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.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0, $nodefault=0)
Return value of a param into GET or POST supervariable.
get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod=0, $idprodfournprice=0)
Function that returns whether VAT must be recoverable collected VAT (e.g.: VAT NPR in France)
getDolCurrency()
Return the main currency ('EUR', 'USD', ...)
GETPOSTINT($paramname, $method=0, $nodefault=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
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).
getDolGlobalBool($key, $default=false)
Return a Dolibarr global constant boolean value.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod=0, $idprodfournprice=0)
Function that return vat rate of a product line (according to seller, buyer and product vat rate) VAT...
get_localtax($vatrate, $local, $thirdparty_buyer=null, $thirdparty_seller=null, $vatnpr=0)
Return localtax rate for a particular VAT rate, when selling a product with vat $vatrate,...
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
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