dolibarr/htdocs/commande/class/commande.class.php

4511 lines
157 KiB
PHP
Raw Normal View History

<?php
/* Copyright (C) 2003-2006 Rodolphe Quiedeville <rodolphe@quiedeville.org>
2012-08-05 19:30:40 +00:00
* Copyright (C) 2004-2012 Laurent Destailleur <eldy@users.sourceforge.net>
2018-10-27 12:43:12 +00:00
* Copyright (C) 2005-2014 Regis Houssin <regis.houssin@inodbox.com>
2006-02-12 08:54:15 +00:00
* Copyright (C) 2006 Andre Cianfarani <acianfa@free.fr>
2020-09-03 10:43:38 +00:00
* Copyright (C) 2010-2020 Juanjo Menent <jmenent@2byte.es>
* Copyright (C) 2011 Jean Heimburger <jean@tiaris.info>
* Copyright (C) 2012-2014 Christophe Battarel <christophe.battarel@altairis.fr>
* Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr>
* Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
2015-02-15 14:07:24 +00:00
* Copyright (C) 2014-2015 Marcos García <marcosgdf@gmail.com>
* Copyright (C) 2018 Nicolas ZABOURI <info@inovea-conseil.com>
* Copyright (C) 2016-2022 Ferran Marcet <fmarcet@2byte.es>
2025-01-05 16:09:38 +00:00
* Copyright (C) 2021-2025 Frédéric France <frederic.france@free.fr>
* Copyright (C) 2022 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
* Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
2024-09-16 09:56:23 +00:00
* Copyright (C) 2024 William Mead <william.mead@manchenumerique.fr>
* Copyright (C) 2026 Vincent de Grandpré <vincent@de-grandpre.quebec>
*
* This program is free software; you can redistribute it and/or modify
2014-08-18 18:23:06 +00:00
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
2019-09-23 19:55:30 +00:00
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/**
2010-04-27 08:13:42 +00:00
* \file htdocs/commande/class/commande.class.php
2024-05-13 08:45:20 +00:00
* \ingroup order
2022-08-27 11:55:27 +00:00
* \brief class for orders
*/
2023-09-19 19:16:32 +00:00
2025-01-05 16:23:31 +00:00
require_once DOL_DOCUMENT_ROOT.'/core/class/commonorder.class.php';
require_once DOL_DOCUMENT_ROOT.'/commande/class/orderline.class.php';
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
require_once DOL_DOCUMENT_ROOT.'/margin/lib/margins.lib.php';
require_once DOL_DOCUMENT_ROOT.'/multicurrency/class/multicurrency.class.php';
require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
NEW Subtotal module (#33502) * Changes to follow mvc logic * Reworked admin page and form to add lines * Reworked adding line logic * Adding options when editing subtotal lines * Fix translations * Fixed errors/displays and started pdf * Color for subtotals pdf lines * FIX display of subtotal totht * Added pdf azur for propal * Fix duplicate translation * Added subtotal support for facture pdf * Added subtotal support for commande pdf * Improve UI/translations * Restored old pdf * Info to warn user for unsupported pdf * Added title lines VAT rate and discount support This is meant for future feature wich is block mass changing vat rate and discount percentage * Working on block apply vat and discount * Added buttons for block actions * Handle editing vat and discount for subtotal lines * Editing vat and discount for subtotal lines bloc working * Added possibility to move by block * Updated adding and updating a subtotal line * Improved vat and discount block update * Improvement for block vat/discount and line edition Editing a title line edits the corresponding subtotal line * Improvement for moving by block * Bad tile or st line placement managing * Improved bad title or st line placement managing * Adding subtotal line improved Adding a subtotal line adds it right under its corresponding title * Improved deleting subtotal line Added possibility to choose if you want to delete the corresponding subtotal line when deleting a title line * Preventing too high level titles to be created * Create and update line errors managing * Improved bad title or st line placement managing * Improved st line creation * improve headers * fix bad block placement managing * fix adding st line not working if duplicates titles * fix translations * Fix block update * FIX special chars bug If special char like " ' " was used in title it could be converted to special char entity. * Prepare for pdf options Pdf options like page break befor title should be shown as a picto if activated on a subtotal line on a document * Added option managing * Improved subtotal options and PDF integration * Code refactor * Action name/Error managment/PDF refreshing When adding or updating a subtotal line * Reformat * Reworked subtotal options database managment * Changed access to special code * Remove unecessary call to php trait * Changed definition of subtotals special code constant * Reworked align on PDF * Removed unecessary function and improved error managment * Typo fix and removed treated todos * Post typo changed for to better match subtotals names * Disabled edit if status is not draft * Changed way subtotals options are stored * Added view managing when creating a document form an other * Improved creating document from an other Can check subtotals lines with table head checkbox and removed highlight class for better UI. * Make include of subtotals tpl more clear * Manage centered or justified case If user chose to center or justify, we don't change nothing * Improved pdf * Removed unecessary code block * optimisation * Code sniffer fix * Code sniffer fix * Code sniffer fix * Code sniffer fix * Code sniffer fix and added missing translation * Fix php code sniffer * Reload page when setup saved on subtotals admin page * Fix php code sniffer * pjan fix * phan fix * phan fix * phan fix * phan fix * phan fix * phan fix * phan fix * phan fix * phan fix * phan fix * phan fix * php warning fix * php warning fix * php phan fix * php phan fix * php phan fix * Fix bug admin page not loading because of const not defined * php phan fix * php phan fix * FIX subtotals admin page display * php phan fix * FIX php phan * Fix bugs and langs * Fix bug pdf align * Replace include by require * Add headers to avoid refreshing and adding unwanted lines * Fix phan * Add GETPOST check for security * Fix phan * Fix phan * Fix phan * Subtotal option when creating a document from another * Fix phan * Add field subtotal options for subtotal lines * Fix phan * Fix phan * Fix phan * Update to follow mvc * Fix php phan * Fix php phan * Fix phpstan/phan * Fix phpstan * Fix phpstan * Fix phpstan * Fix phpstan * Fix phpstan * Update to switch to extraparams * Update to switch to extraparams * Retrieve extraparams from db to objectline * Modified last things to switch to extraparams * Cleaning unnecessary code lines * Fix php-stan * Section subtotal in extraparams to differentiate if needed for further devs * Fix phan * Keep extraparams when creating from another object * Change default value to false when creating a subtotal line * Fix clone would not keep extraparams in new object * Fix dark subtotal line background color Fix when a subtotal background color is too dark and edit pencil or delete trash could not be seen * Fix typo * Fix typo * Added subtotals for facturerec * Fix precommit * Added extraparams when creating rec from fac and other way * Fix phan * Fix objectline null * Desactivating block vat / discount update for facturerec * reformating code * Added expeditions for subtotal * Save extraparams for shipping lines * Display of subtotals lines in shipments * Display when creating facture from shipments * Improve display of lines and invoice creation from shipments * Fix error if missing line rang * Deleted duplicate * Added deletion of subtotal line in shipping documents * Not including subtotal lines if there is no product line in between * Update get subtotal lines in shipment docs to disable * Delete possibility to edit subtotal lines in shipments * Handle pdf for shipment * Handle conf stock or shipment supporting services * Fix precommit * Fix duplicate name creating bug * Fix bug where id could be changed by the line id and would create bug * Deleted subtotal lines when STOCK_SUPPORTS_SERVICES is enabled and block would only have service lines * Disable shipments in admin modules Shimpements subtotals lines are only created from commands * Fixing phan and stan * Deleted unused template and phan fix * Fix phan * Fix phan * Fix phan * FIX: phan * Fix template bug If document was not in subtotal scope and would use a template used by subtotal it would create an error. * Fix php-stan * Fix bad display when modules and confs were activated * Fix php codesniffer * Excluding subtotal lines when mass updating * Fix shipments service lines exluded + showing subtotal line with specific configs * Fix line display with situation invoices + bug block line update * Added ODT managment This works with invoices but has not been tested with other documents * Fix bug when editing VAT/discount by block * Fix error raised when subtotal line added Subtotal line had no fk_product and raised the error but we want this line to have no fk product * Fix precommit * Added ODT template for documents that uses subtotals This template can be usefull to understand how to create an odt template using subtotals module * Fix phan * Fix phan * fix: buttons showing in bad document status * clean: unwanted commited files * add: table examples to use with subtotals on ODT templates * fix: Unwanted print of value when creating a document from another * feat: renaming for better understanding * feat: Improved templates for documents related to subtotals --------- Co-authored-by: Marc de Lima Lucio <68746600+marc-dll@users.noreply.github.com> Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2025-05-10 00:44:31 +00:00
require_once DOL_DOCUMENT_ROOT.'/subtotals/class/commonsubtotal.class.php';
/**
2012-08-05 19:30:40 +00:00
* Class to manage customers orders
*/
class Commande extends CommonOrder
{
NEW Subtotal module (#33502) * Changes to follow mvc logic * Reworked admin page and form to add lines * Reworked adding line logic * Adding options when editing subtotal lines * Fix translations * Fixed errors/displays and started pdf * Color for subtotals pdf lines * FIX display of subtotal totht * Added pdf azur for propal * Fix duplicate translation * Added subtotal support for facture pdf * Added subtotal support for commande pdf * Improve UI/translations * Restored old pdf * Info to warn user for unsupported pdf * Added title lines VAT rate and discount support This is meant for future feature wich is block mass changing vat rate and discount percentage * Working on block apply vat and discount * Added buttons for block actions * Handle editing vat and discount for subtotal lines * Editing vat and discount for subtotal lines bloc working * Added possibility to move by block * Updated adding and updating a subtotal line * Improved vat and discount block update * Improvement for block vat/discount and line edition Editing a title line edits the corresponding subtotal line * Improvement for moving by block * Bad tile or st line placement managing * Improved bad title or st line placement managing * Adding subtotal line improved Adding a subtotal line adds it right under its corresponding title * Improved deleting subtotal line Added possibility to choose if you want to delete the corresponding subtotal line when deleting a title line * Preventing too high level titles to be created * Create and update line errors managing * Improved bad title or st line placement managing * Improved st line creation * improve headers * fix bad block placement managing * fix adding st line not working if duplicates titles * fix translations * Fix block update * FIX special chars bug If special char like " ' " was used in title it could be converted to special char entity. * Prepare for pdf options Pdf options like page break befor title should be shown as a picto if activated on a subtotal line on a document * Added option managing * Improved subtotal options and PDF integration * Code refactor * Action name/Error managment/PDF refreshing When adding or updating a subtotal line * Reformat * Reworked subtotal options database managment * Changed access to special code * Remove unecessary call to php trait * Changed definition of subtotals special code constant * Reworked align on PDF * Removed unecessary function and improved error managment * Typo fix and removed treated todos * Post typo changed for to better match subtotals names * Disabled edit if status is not draft * Changed way subtotals options are stored * Added view managing when creating a document form an other * Improved creating document from an other Can check subtotals lines with table head checkbox and removed highlight class for better UI. * Make include of subtotals tpl more clear * Manage centered or justified case If user chose to center or justify, we don't change nothing * Improved pdf * Removed unecessary code block * optimisation * Code sniffer fix * Code sniffer fix * Code sniffer fix * Code sniffer fix * Code sniffer fix and added missing translation * Fix php code sniffer * Reload page when setup saved on subtotals admin page * Fix php code sniffer * pjan fix * phan fix * phan fix * phan fix * phan fix * phan fix * phan fix * phan fix * phan fix * phan fix * phan fix * phan fix * php warning fix * php warning fix * php phan fix * php phan fix * php phan fix * Fix bug admin page not loading because of const not defined * php phan fix * php phan fix * FIX subtotals admin page display * php phan fix * FIX php phan * Fix bugs and langs * Fix bug pdf align * Replace include by require * Add headers to avoid refreshing and adding unwanted lines * Fix phan * Add GETPOST check for security * Fix phan * Fix phan * Fix phan * Subtotal option when creating a document from another * Fix phan * Add field subtotal options for subtotal lines * Fix phan * Fix phan * Fix phan * Update to follow mvc * Fix php phan * Fix php phan * Fix phpstan/phan * Fix phpstan * Fix phpstan * Fix phpstan * Fix phpstan * Fix phpstan * Update to switch to extraparams * Update to switch to extraparams * Retrieve extraparams from db to objectline * Modified last things to switch to extraparams * Cleaning unnecessary code lines * Fix php-stan * Section subtotal in extraparams to differentiate if needed for further devs * Fix phan * Keep extraparams when creating from another object * Change default value to false when creating a subtotal line * Fix clone would not keep extraparams in new object * Fix dark subtotal line background color Fix when a subtotal background color is too dark and edit pencil or delete trash could not be seen * Fix typo * Fix typo * Added subtotals for facturerec * Fix precommit * Added extraparams when creating rec from fac and other way * Fix phan * Fix objectline null * Desactivating block vat / discount update for facturerec * reformating code * Added expeditions for subtotal * Save extraparams for shipping lines * Display of subtotals lines in shipments * Display when creating facture from shipments * Improve display of lines and invoice creation from shipments * Fix error if missing line rang * Deleted duplicate * Added deletion of subtotal line in shipping documents * Not including subtotal lines if there is no product line in between * Update get subtotal lines in shipment docs to disable * Delete possibility to edit subtotal lines in shipments * Handle pdf for shipment * Handle conf stock or shipment supporting services * Fix precommit * Fix duplicate name creating bug * Fix bug where id could be changed by the line id and would create bug * Deleted subtotal lines when STOCK_SUPPORTS_SERVICES is enabled and block would only have service lines * Disable shipments in admin modules Shimpements subtotals lines are only created from commands * Fixing phan and stan * Deleted unused template and phan fix * Fix phan * Fix phan * Fix phan * FIX: phan * Fix template bug If document was not in subtotal scope and would use a template used by subtotal it would create an error. * Fix php-stan * Fix bad display when modules and confs were activated * Fix php codesniffer * Excluding subtotal lines when mass updating * Fix shipments service lines exluded + showing subtotal line with specific configs * Fix line display with situation invoices + bug block line update * Added ODT managment This works with invoices but has not been tested with other documents * Fix bug when editing VAT/discount by block * Fix error raised when subtotal line added Subtotal line had no fk_product and raised the error but we want this line to have no fk product * Fix precommit * Added ODT template for documents that uses subtotals This template can be usefull to understand how to create an odt template using subtotals module * Fix phan * Fix phan * fix: buttons showing in bad document status * clean: unwanted commited files * add: table examples to use with subtotals on ODT templates * fix: Unwanted print of value when creating a document from another * feat: renaming for better understanding * feat: Improved templates for documents related to subtotals --------- Co-authored-by: Marc de Lima Lucio <68746600+marc-dll@users.noreply.github.com> Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2025-05-10 00:44:31 +00:00
use CommonSubtotal;
2018-08-30 07:40:04 +00:00
/**
2018-08-23 15:07:27 +00:00
* @var string ID to identify managed object
*/
public $element = 'commande';
2018-08-30 07:40:04 +00:00
/**
* @var string Prefix to check for any trigger code of any business class to prevent bad value for trigger code.
* @see CommonTrigger::call_trigger()
*/
public $TRIGGER_PREFIX = 'ORDER';
2018-08-30 07:40:04 +00:00
/**
2018-08-22 16:12:44 +00:00
* @var string Name of table without prefix where object is stored
*/
public $table_element = 'commande';
2018-08-30 07:40:04 +00:00
2018-09-02 09:08:41 +00:00
/**
2019-11-02 08:27:06 +00:00
* @var string Name of subtable line
2018-09-02 09:08:41 +00:00
*/
2018-08-30 07:40:04 +00:00
public $table_element_line = 'commandedet';
2018-09-02 09:08:41 +00:00
2019-11-02 08:27:06 +00:00
/**
* @var string Name of class line
*/
2018-08-30 07:40:04 +00:00
public $class_element_line = 'OrderLine';
2018-09-01 13:13:59 +00:00
/**
2019-11-02 08:27:06 +00:00
* @var string Field name with ID of parent key if this field has a parent
2018-09-01 13:13:59 +00:00
*/
2018-08-30 07:40:04 +00:00
public $fk_element = 'fk_commande';
2018-09-01 13:13:59 +00:00
2018-09-05 08:31:12 +00:00
/**
* @var string String with name of icon for order class. Here is object_order.png
2018-09-05 08:31:12 +00:00
*/
2018-08-30 07:40:04 +00:00
public $picto = 'order';
2018-09-02 09:08:41 +00:00
2018-08-30 07:40:04 +00:00
/**
* 0=Default, 1=View may be restricted to sales representative only if no permission to see all or to company of external user if external user
* @var integer
*/
public $restrictiononfksoc = 1;
/**
* {@inheritdoc}
*/
protected $table_ref_field = 'ref';
/**
2026-01-23 17:37:58 +00:00
* @var string Ref of order
*/
public $ref;
/**
* @var ?int Thirdparty ID
*/
2018-08-30 07:40:04 +00:00
public $socid;
2019-11-02 08:27:06 +00:00
/**
* @var ?string Thirdparty ref of order
2019-11-02 08:27:06 +00:00
*/
2018-08-30 07:40:04 +00:00
public $ref_client;
2019-11-02 08:27:06 +00:00
2023-05-01 11:09:09 +00:00
/**
* @var ?string Thirdparty ref of order
2023-05-01 11:09:09 +00:00
*/
public $ref_customer;
/**
2026-01-23 17:37:58 +00:00
* @var int Contact ID
2019-11-02 08:27:06 +00:00
*/
2018-08-30 07:40:04 +00:00
public $contactid;
/**
* Status of the order
* @var int
2025-02-11 15:28:37 +00:00
* @deprecated Use status
*/
2018-08-30 07:40:04 +00:00
public $statut;
2018-09-02 09:08:41 +00:00
2025-02-11 15:28:37 +00:00
/**
* Status of the order
2025-02-18 09:24:00 +00:00
* @var int|null
2025-02-11 15:28:37 +00:00
*/
public $status;
/**
2019-11-02 08:27:06 +00:00
* @var int Status Billed or not
*/
2019-11-02 08:27:06 +00:00
public $billed;
2018-08-30 07:40:04 +00:00
/**
2024-01-18 22:28:42 +00:00
* @var int Deadline for payment
*/
public $date_lim_reglement;
2021-02-09 21:14:46 +00:00
/**
* @var string Condition payment code
*/
public $cond_reglement_code;
2018-08-30 07:40:04 +00:00
2023-08-05 10:50:09 +00:00
/**
* @var string Condition payment label
*/
public $cond_reglement_doc;
/**
* @var float Deposit percent for payment terms.
* Populated by $CommonObject->setPaymentTerms().
* @see setPaymentTerms()
*/
public $deposit_percent;
2018-10-09 07:40:00 +00:00
/**
* @var ?int bank account ID
*/
2018-08-30 07:40:04 +00:00
public $fk_account;
/**
2019-11-02 08:27:06 +00:00
* @var string It holds the label of the payment mode. Use it in case translation cannot be found.
2018-08-30 07:40:04 +00:00
*/
public $mode_reglement;
/**
* @var ?int Payment mode id
2018-08-30 07:40:04 +00:00
*/
public $mode_reglement_id;
2018-09-02 09:08:41 +00:00
2018-08-30 07:40:04 +00:00
/**
2019-11-02 08:27:06 +00:00
* @var string Payment mode code
2018-08-30 07:40:04 +00:00
*/
public $mode_reglement_code;
2018-09-02 09:08:41 +00:00
2018-08-30 07:40:04 +00:00
/**
* Availability delivery time id
* @var int
*/
public $availability_id;
2018-09-02 09:08:41 +00:00
2018-08-30 07:40:04 +00:00
/**
* Availability delivery time code
* @var string
*/
public $availability_code;
2018-09-02 09:08:41 +00:00
2018-08-30 07:40:04 +00:00
/**
* Label of availability delivery time. Use it in case translation cannot be found.
* @var string
*/
public $availability;
2021-02-09 21:14:46 +00:00
/**
* @var int Source demand reason Id
*/
public $demand_reason_id;
/**
* @var string Source reason code. Why we receive order (after a phone campaign, ...)
*/
2018-08-30 07:40:04 +00:00
public $demand_reason_code;
/**
* @var null|int|'' Date of order
2019-11-02 08:27:06 +00:00
*/
public $date;
/**
* @var null|int|'' Date of order
* @deprecated
2019-04-08 14:04:15 +00:00
* @see $date
*/
2018-08-30 07:40:04 +00:00
public $date_commande;
2018-09-02 09:08:41 +00:00
2020-11-11 17:08:40 +00:00
/**
* @var null|int|'' Expected shipment date (date of start of shipment, not the reception that occurs some days after)
2021-02-09 21:14:46 +00:00
*/
public $delivery_date;
2018-10-09 07:40:00 +00:00
/**
* @var int ID
*/
2018-08-30 07:40:04 +00:00
public $fk_remise_except;
2018-10-09 07:40:00 +00:00
/**
* @var string
* @deprecated See $fk_remise_except
*/
2018-08-30 07:40:04 +00:00
public $remise_percent;
/**
2025-05-15 08:23:09 +00:00
* @var ?int Order mode. How we received the sale order (by phone, by email, ...)
*/
2025-05-15 08:23:09 +00:00
public $source;
2020-11-28 13:47:39 +00:00
/**
* Status of the contract (0=NoSignature, 1=SignedBySender, 2=SignedByReceiver, 9=SignedByAll)
* @var int
*/
public $signed_status = 0;
2021-02-09 21:14:46 +00:00
/**
* @var int Warehouse Id
*/
2021-01-12 09:34:15 +00:00
public $warehouse_id;
/**
* @var array<string,string> (Encoded as JSON in database)
*/
public $extraparams = array();
2018-08-30 07:40:04 +00:00
2019-11-02 08:27:06 +00:00
/**
* @var int User author ID
*/
2018-08-30 07:40:04 +00:00
public $user_author_id;
2019-11-02 08:27:06 +00:00
2023-01-18 21:37:08 +00:00
/**
* @var OrderLine one line of an order
*/
public $line;
2015-03-23 00:39:12 +00:00
/**
* @var OrderLine[]
*/
2016-07-08 19:28:21 +00:00
public $lines = array();
2011-09-12 17:08:02 +00:00
/**
2025-06-18 13:59:28 +00:00
* @var ?string key of module source when order generated from a dedicated module ('cashdesk', 'takepos', ...)
*/
2019-02-20 11:29:47 +00:00
public $module_source;
/**
2025-06-18 13:59:28 +00:00
* @var ?string key of pos source ('0', '1', ...)
*/
2019-02-20 11:29:47 +00:00
public $pos_source;
2019-02-24 23:56:48 +00:00
2022-01-24 13:09:53 +00:00
/**
* @var array<int,float> Array with lines of all shipments (qty)
2022-01-24 13:09:53 +00:00
*/
public $expeditions;
/**
* @var string payment url
*/
public $online_payment_url;
2020-03-03 23:47:03 +00:00
2020-03-03 23:47:03 +00:00
/**
* 'type' if the field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter]]', 'varchar(x)', 'double(24,8)', 'real', 'price', 'text', 'html', 'date', 'datetime', 'timestamp', 'duration', 'mail', 'phone', 'url', 'password')
2026-05-29 10:16:42 +00:00
* Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:>:'20160101') or (t.nature:is:NULL)"
2020-03-03 23:47:03 +00:00
* 'label' the translation key.
2025-06-11 10:45:15 +00:00
* 'langfile' the key of the language file for translation.
2020-03-03 23:47:03 +00:00
* 'enabled' is a condition when the field must be managed.
* 'position' is the sort order of field.
* 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
* 'visible' says if field is visible in list (Examples: 0=Not visible, 1=Visible on list and create/update/view forms, 2=Visible on list only, 3=Visible on create/update/view form only (not list), 4=Visible on list and update/view form only (not create). 5=Visible on list and view only (not create/not update). Using a negative value means field is not shown by default on list but can be selected for viewing)
* 'noteditable' says if field is not editable (1 or 0)
2025-09-03 10:54:17 +00:00
* 'default' is a default value for creation (can still be overwritten by the Setup of Default Values if the field is editable in creation form). Note: If default is set to '(PROV)' and field is 'ref', the default value will be set to '(PROVid)' where id is rowid when a new record is created.
2020-03-03 23:47:03 +00:00
* 'index' if we want an index in database.
* 'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommended to name the field fk_...).
2020-03-03 23:47:03 +00:00
* 'searchall' is 1 if we want to search in this field when making a search from the quick search button.
* 'isameasure' must be set to 1 if you want to have a total on list for this field. Field type must be summable like integer or double(24,8).
* 'css' is the CSS style to use on field. For example: 'maxwidth200'
* 'help' is a string visible as a tooltip on field
* 'showoncombobox' if value of the field must be visible into the label of the combobox that list record
* 'disabled' is 1 if we want to have the field locked by a 'disabled' attribute. In most cases, this is never set into the definition of $fields into class, but is set dynamically by some part of code.
2021-04-29 10:10:55 +00:00
* 'arrayofkeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel")
2020-03-03 23:47:03 +00:00
* 'comment' is not used. You can store here any text of your choice. It is not used by application.
*
* Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
*/
// BEGIN MODULEBUILDER PROPERTIES
/**
* @var array<string,array{type:string,label:string,enabled:int<0,2>|string,position:int,visible:int<-6,6>|string,langfile?:string,notnull?:int<-1,1>,noteditable?:int<0,1>,alwayseditable?:int<0,1>|string,default?:string|int,index?:int<0,1>,foreignkey?:string,searchall?:int<0,1>,isameasure?:int<0,1>,css?:string,cssview?:string,csslist?:string,help?:string,helplist?:string,showoncombobox?:int<0,4>|string,disabled?:int<0,1>|string,arrayofkeyval?:array<int|string,string>,autofocusoncreate?:int<0,1>,comment?:string,copytoclipboard?:int<1,2>,validate?:int<0,1>|string,showonheader?:int<0,1>,searchmulti?:int<0,1>,picto?:string,required?:int<0,1>,placeholder?:string}> Array with all fields and their property. Do not use it as a static var. It may be modified by constructor.
2020-03-03 23:47:03 +00:00
*/
public $fields = array(
'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 10),
2024-03-14 23:10:48 +00:00
'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 20, 'index' => 1),
'ref' => array('type' => 'varchar(30)', 'label' => 'Ref', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'showoncombobox' => 1, 'position' => 25),
'ref_ext' => array('type' => 'varchar(255)', 'label' => 'RefExt', 'enabled' => 1, 'visible' => 0, 'position' => 26),
'ref_client' => array('type' => 'varchar(255)', 'label' => 'RefCustomer', 'enabled' => 1, 'visible' => -1, 'position' => 28),
'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'enabled' => 'isModEnabled("societe")', 'visible' => -1, 'notnull' => 1, 'position' => 20),
'fk_projet' => array('type' => 'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label' => 'Project', 'enabled' => "isModEnabled('project')", 'visible' => -1, 'position' => 25),
'date_commande' => array('type' => 'date', 'label' => 'Date', 'enabled' => 1, 'visible' => 1, 'position' => 60, 'csslist' => 'nowraponall'),
'date_valid' => array('type' => 'datetime', 'label' => 'DateValidation', 'enabled' => 1, 'visible' => -1, 'position' => 62, 'csslist' => 'nowraponall'),
'date_cloture' => array('type' => 'datetime', 'label' => 'DateClosing', 'enabled' => 1, 'visible' => -1, 'position' => 65, 'csslist' => 'nowraponall'),
'fk_user_valid' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserValidation', 'enabled' => 1, 'visible' => -1, 'position' => 85),
'fk_user_cloture' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserClosing', 'enabled' => 1, 'visible' => -1, 'position' => 90),
'source' => array('type' => 'smallint(6)', 'label' => 'Source', 'enabled' => 1, 'visible' => -1, 'position' => 95),
'total_tva' => array('type' => 'double(24,8)', 'label' => 'VAT', 'enabled' => 1, 'visible' => -1, 'position' => 125, 'isameasure' => 1),
'localtax1' => array('type' => 'double(24,8)', 'label' => 'LocalTax1', 'enabled' => 1, 'visible' => -1, 'position' => 130, 'isameasure' => 1),
'localtax2' => array('type' => 'double(24,8)', 'label' => 'LocalTax2', 'enabled' => 1, 'visible' => -1, 'position' => 135, 'isameasure' => 1),
'total_ht' => array('type' => 'double(24,8)', 'label' => 'TotalHT', 'enabled' => 1, 'visible' => -1, 'position' => 140, 'isameasure' => 1),
'total_ttc' => array('type' => 'double(24,8)', 'label' => 'TotalTTC', 'enabled' => 1, 'visible' => -1, 'position' => 145, 'isameasure' => 1),
'signed_status' => array('type' => 'smallint(6)', 'label' => 'SignedStatus', 'enabled' => 1, 'visible' => -1, 'position' => 146, 'arrayofkeyval' => array(0 => 'NoSignature', 1 => 'SignedSender', 2 => 'SignedReceiver', 9 => 'SignedAll')),
'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 150),
'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 155),
'model_pdf' => array('type' => 'varchar(255)', 'label' => 'PDFTemplate', 'enabled' => 1, 'visible' => 0, 'position' => 160),
'fk_account' => array('type' => 'integer', 'label' => 'BankAccount', 'enabled' => 'isModEnabled("bank")', 'visible' => -1, 'position' => 170),
'fk_currency' => array('type' => 'varchar(3)', 'label' => 'MulticurrencyID', 'enabled' => 1, 'visible' => -1, 'position' => 175),
'fk_cond_reglement' => array('type' => 'integer', 'label' => 'PaymentTerm', 'enabled' => 1, 'visible' => -1, 'position' => 180),
'deposit_percent' => array('type' => 'varchar(63)', 'label' => 'DepositPercent', 'enabled' => 1, 'visible' => -1, 'position' => 181),
'fk_mode_reglement' => array('type' => 'integer', 'label' => 'PaymentMode', 'enabled' => 1, 'visible' => -1, 'position' => 185),
'date_livraison' => array('type' => 'date', 'label' => 'DateDeliveryPlanned', 'enabled' => 1, 'visible' => -1, 'position' => 190, 'csslist' => 'nowraponall'),
'fk_shipping_method' => array('type' => 'integer', 'label' => 'ShippingMethod', 'enabled' => 1, 'visible' => -1, 'position' => 195),
2025-01-14 21:05:08 +00:00
'fk_warehouse' => array('type' => 'integer:Entrepot:product/stock/class/entrepot.class.php', 'label' => 'DefaultWarehouse', 'enabled' => 'isModEnabled("stock")', 'visible' => -1, 'position' => 200, 'nodepth' => 1),
'fk_availability' => array('type' => 'integer', 'label' => 'Availability', 'enabled' => 1, 'visible' => -1, 'position' => 205),
'fk_input_reason' => array('type' => 'integer', 'label' => 'InputReason', 'enabled' => 1, 'visible' => -1, 'position' => 210),
2020-03-08 18:25:00 +00:00
//'fk_delivery_address' =>array('type'=>'integer', 'label'=>'DeliveryAddress', 'enabled'=>1, 'visible'=>-1, 'position'=>215),
'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => -1, 'position' => 225),
2025-12-05 11:55:20 +00:00
'fk_incoterms' => array('type' => 'integer', 'label' => 'IncotermCode', 'enabled' => 'isModEnabled("incoterm")', 'visible' => -1, 'position' => 230),
'location_incoterms' => array('type' => 'varchar(255)', 'label' => 'IncotermLabel', 'enabled' => 'isModEnabled("incoterm")', 'visible' => -1, 'position' => 235),
'fk_multicurrency' => array('type' => 'integer', 'label' => 'Fk multicurrency', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 240),
'multicurrency_code' => array('type' => 'varchar(255)', 'label' => 'MulticurrencyCurrency', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 245),
'multicurrency_tx' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyRate', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 250, 'isameasure' => 1),
'multicurrency_total_ht' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountHT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 255, 'isameasure' => 1),
'multicurrency_total_tva' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountVAT', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 260, 'isameasure' => 1),
'multicurrency_total_ttc' => array('type' => 'double(24,8)', 'label' => 'MulticurrencyAmountTTC', 'enabled' => 'isModEnabled("multicurrency")', 'visible' => -1, 'position' => 265, 'isameasure' => 1),
'last_main_doc' => array('type' => 'varchar(255)', 'label' => 'LastMainDoc', 'enabled' => 1, 'visible' => -1, 'position' => 270),
'module_source' => array('type' => 'varchar(32)', 'label' => 'POSModule', 'enabled' => 1, 'visible' => -1, 'position' => 275),
'pos_source' => array('type' => 'varchar(32)', 'label' => 'POSTerminal', 'enabled' => 1, 'visible' => -1, 'position' => 280),
'fk_user_author' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserAuthor', 'enabled' => 1, 'visible' => -1, 'position' => 300),
'fk_user_modif' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'UserModif', 'enabled' => 1, 'visible' => -2, 'notnull' => -1, 'position' => 302),
'date_creation' => array('type' => 'datetime', 'label' => 'DateCreation', 'enabled' => 1, 'visible' => -2, 'position' => 304, 'csslist' => 'nowraponall'),
'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 306),
'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'position' => 400),
'fk_statut' => array('type' => 'smallint(6)', 'label' => 'Status', 'enabled' => 1, 'visible' => -1, 'position' => 500),
2020-03-03 23:47:03 +00:00
);
// END MODULEBUILDER PROPERTIES
2018-08-30 07:40:04 +00:00
/**
* ERR Not enough stock
*/
const STOCK_NOT_ENOUGH_FOR_ORDER = -3;
2011-09-12 17:08:02 +00:00
/**
* Canceled status
*/
const STATUS_CANCELED = -1;
/**
* Draft status
*/
const STATUS_DRAFT = 0;
/**
* Validated status
*/
const STATUS_VALIDATED = 1;
/**
* Shipment on process
*/
2023-09-19 18:29:24 +00:00
const STATUS_SHIPMENTONPROCESS = 2; // We set this status when a shipment is validated
/**
* For backward compatibility. Use key STATUS_SHIPMENTONPROCESS instead.
* @deprecated
*/
const STATUS_ACCEPTED = 2;
2017-10-11 09:36:24 +00:00
/**
* Closed (Sent, billed or not)
*/
const STATUS_CLOSED = 3;
/*
* No signature
*/
const STATUS_NO_SIGNATURE = 0;
/*
* Signed by sender
*/
const STATUS_SIGNED_SENDER = 1;
/*
* Signed by receiver
*/
const STATUS_SIGNED_RECEIVER = 2;
/*
* Signed by all
*/
const STATUS_SIGNED_ALL = 9; // To handle future kind of signature (ex: tripartite contract)
2011-09-12 17:08:02 +00:00
2018-08-30 07:40:04 +00:00
/**
* Constructor
*
* @param DoliDB $db Database handler
*/
2019-02-24 23:56:48 +00:00
public function __construct($db)
2018-08-30 07:40:04 +00:00
{
$this->db = $db;
$this->ismultientitymanaged = 1;
$this->isextrafieldmanaged = 1;
$this->fields['ref_ext']['visible'] = getDolGlobalInt('MAIN_LIST_SHOW_REF_EXT');
2018-08-30 07:40:04 +00:00
}
2018-08-30 07:40:04 +00:00
/**
2011-12-16 11:47:23 +00:00
* Returns the reference to the following non used Order depending on the active numbering module
* defined into COMMANDE_ADDON
*
* @param Societe $soc Object thirdparty
* @return string Order free reference
*/
2019-02-24 23:56:48 +00:00
public function getNextNumRef($soc)
2018-08-30 07:40:04 +00:00
{
global $langs, $conf;
$langs->load("order");
2011-09-12 17:08:02 +00:00
2023-11-27 10:39:32 +00:00
if (getDolGlobalString('COMMANDE_ADDON')) {
$mybool = false;
2011-09-12 17:08:02 +00:00
2023-10-15 13:32:35 +00:00
$file = getDolGlobalString('COMMANDE_ADDON') . ".php";
2024-01-05 03:18:53 +00:00
$classname = getDolGlobalString('COMMANDE_ADDON');
// Include file with class
$dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
2021-02-23 19:46:19 +00:00
foreach ($dirmodels as $reldir) {
2018-08-30 07:40:04 +00:00
$dir = dol_buildpath($reldir."core/modules/commande/");
// Load file with numbering class (if found)
$mybool = ((bool) @include_once $dir.$file) || $mybool;
2018-08-30 07:40:04 +00:00
}
Qual: Enable Phan Simplify rule and fix notices (#30537) * Qual: Phan - Enable Simplyfy expression * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Qual: Simplify expression (phan notice) * Update functions2.lib.php --------- Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2024-08-07 01:05:02 +00:00
if (!$mybool) {
2024-01-20 08:22:38 +00:00
dol_print_error(null, "Failed to include file ".$file);
return '';
}
2018-08-30 07:40:04 +00:00
$obj = new $classname();
2025-01-05 16:23:31 +00:00
/** @var ModeleNumRefCommandes $obj */
'@phan-var-force ModeleNumRefCommandes $obj';
$numref = $obj->getNextValue($soc, $this);
2018-08-30 07:40:04 +00:00
2021-02-23 19:46:19 +00:00
if ($numref != "") {
2018-08-30 07:40:04 +00:00
return $numref;
2020-05-21 13:05:19 +00:00
} else {
$this->error = $obj->error;
2018-08-30 07:40:04 +00:00
//dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error);
return "";
}
2020-05-21 13:05:19 +00:00
} else {
2018-08-30 07:40:04 +00:00
print $langs->trans("Error")." ".$langs->trans("Error_COMMANDE_ADDON_NotDefined");
return "";
}
}
/**
* Validate order
*
* @param User $user User making status change
* @param int $idwarehouse Id of warehouse to use for stock decrease
* @param int $notrigger 1=Does not execute triggers, 0= execute triggers
2023-12-06 14:46:39 +00:00
* @return int Return integer <0 if KO, 0=Nothing done, >0 if OK
2018-08-30 07:40:04 +00:00
*/
2019-02-24 23:56:48 +00:00
public function valid($user, $idwarehouse = 0, $notrigger = 0)
2012-07-06 11:43:59 +00:00
{
global $conf, $langs;
2018-08-30 07:40:04 +00:00
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2012-07-06 11:43:59 +00:00
$error = 0;
2012-07-06 11:43:59 +00:00
2018-08-30 07:40:04 +00:00
// Protection
if ($this->status == self::STATUS_VALIDATED) {
dol_syslog(get_class($this)."::valid action abandoned: already validated", LOG_WARNING);
2018-08-30 07:40:04 +00:00
return 0;
}
2023-11-27 10:39:32 +00:00
if (!((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'creer'))
|| (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'order_advance', 'validate')))) {
$this->error = 'NotEnoughPermissions';
2018-08-30 07:40:04 +00:00
dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
return -1;
}
if (empty($this->socid)) {
$this->error = 'ErrorWrongParameters';
return -1;
}
if (!getDolGlobalBool('ORDER_NOCHECK_ONSALE_PRODUCTS_ONVALID') && !$this->checkActiveProductInLines()) {
dol_syslog(get_class($this)."::valid checkActiveProductInLines ".$this->error, LOG_INFO);
return -1;
}
$now = dol_now();
2018-08-30 07:40:04 +00:00
2012-07-06 11:43:59 +00:00
$this->db->begin();
2018-08-30 07:40:04 +00:00
// Definition du nom de module de numerotation de commande
$soc = new Societe($this->db);
$soc->fetch($this->socid);
// Class of company linked to order
2023-09-06 11:58:08 +00:00
$result = $soc->setAsCustomer();
2018-08-30 07:40:04 +00:00
// Define new ref
if (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref)) { // empty should not happened, but when it occurs, the test save life
2018-08-30 07:40:04 +00:00
$num = $this->getNextNumRef($soc);
2020-05-21 13:05:19 +00:00
} else {
$num = (string) $this->ref;
2018-08-30 07:40:04 +00:00
}
$this->newref = dol_sanitizeFileName($num);
2018-08-30 07:40:04 +00:00
// Validate
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
$sql .= " SET ref = '".$this->db->escape($num)."',";
$sql .= " fk_statut = ".self::STATUS_VALIDATED.",";
2025-01-19 16:38:34 +00:00
$sql .= " date_valid = '".$this->db->idate($now)."',";
2022-07-03 10:54:58 +00:00
$sql .= " fk_user_valid = ".($user->id > 0 ? (int) $user->id : "null").",";
2022-01-15 23:13:14 +00:00
$sql .= " fk_user_modif = ".((int) $user->id);
2021-08-23 17:33:24 +00:00
$sql .= " WHERE rowid = ".((int) $this->id);
2012-07-06 11:43:59 +00:00
2020-10-27 17:30:03 +00:00
dol_syslog(get_class($this)."::valid", LOG_DEBUG);
$resql = $this->db->query($sql);
2021-02-23 19:46:19 +00:00
if (!$resql) {
2018-08-30 07:40:04 +00:00
dol_print_error($this->db);
$this->error = $this->db->lasterror();
2018-08-30 07:40:04 +00:00
$error++;
}
2020-12-16 01:44:48 +00:00
if (!$error) {
2018-08-30 07:40:04 +00:00
// If stock is incremented on validate order, we must increment it
if ($result >= 0 && isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_VALIDATE_ORDER') == 1) {
require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
2012-07-06 11:43:59 +00:00
$langs->load("agenda");
2018-08-30 07:40:04 +00:00
// Loop on each line
$cpt = count($this->lines);
2020-12-16 01:44:48 +00:00
for ($i = 0; $i < $cpt; $i++) {
if ($this->lines[$i]->fk_product > 0) {
2012-07-06 11:43:59 +00:00
$mouvP = new MouvementStock($this->db);
2018-08-30 07:40:04 +00:00
$mouvP->origin = &$this;
$mouvP->setOrigin($this->element, $this->id);
2018-08-30 07:40:04 +00:00
// We decrement stock of product (and sub-products)
$result = $mouvP->livraison($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, $this->lines[$i]->subprice, $langs->trans("OrderValidatedInDolibarr", $num));
2020-12-16 01:44:48 +00:00
if ($result < 0) {
2012-07-06 11:43:59 +00:00
$error++;
$this->setErrorsFromObject($mouvP);
2012-07-06 11:43:59 +00:00
}
}
2021-02-23 19:46:19 +00:00
if ($error) {
break;
}
2012-07-06 11:43:59 +00:00
}
}
2018-08-30 07:40:04 +00:00
}
2012-07-06 11:43:59 +00:00
2020-12-16 01:44:48 +00:00
if (!$error && !$notrigger) {
2018-08-30 07:40:04 +00:00
// Call trigger
$result = $this->call_trigger('ORDER_VALIDATE', $user);
2021-02-23 19:46:19 +00:00
if ($result < 0) {
$error++;
}
2018-08-30 07:40:04 +00:00
// End call triggers
}
2012-07-06 11:43:59 +00:00
2020-12-16 01:44:48 +00:00
if (!$error) {
2018-08-30 07:40:04 +00:00
$this->oldref = $this->ref;
// Rename directory if dir was a temporary ref
2021-02-23 19:46:19 +00:00
if (preg_match('/^[\(]?PROV/i', $this->ref)) {
// Now we rename also files into index
$sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'commande/".$this->db->escape($this->newref)."'";
$sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'commande/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
$resql = $this->db->query($sql);
2021-02-23 19:46:19 +00:00
if (!$resql) {
2023-12-04 11:01:45 +00:00
$error++;
$this->error = $this->db->lasterror();
2021-02-23 19:46:19 +00:00
}
$sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filepath = 'commande/".$this->db->escape($this->newref)."'";
$sql .= " WHERE filepath = 'commande/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
$resql = $this->db->query($sql);
if (!$resql) {
2023-12-04 11:01:45 +00:00
$error++;
$this->error = $this->db->lasterror();
}
// We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
2018-08-30 07:40:04 +00:00
$oldref = dol_sanitizeFileName($this->ref);
$newref = dol_sanitizeFileName($num);
$dirsource = getMultidirOutput($this).'/'.$oldref;
$dirdest = getMultidirOutput($this).'/'.$newref;
2021-02-23 19:46:19 +00:00
if (!$error && file_exists($dirsource)) {
2020-10-27 17:30:03 +00:00
dol_syslog(get_class($this)."::valid rename dir ".$dirsource." into ".$dirdest);
2018-08-30 07:40:04 +00:00
2021-02-23 19:46:19 +00:00
if (@rename($dirsource, $dirdest)) {
2018-08-30 07:40:04 +00:00
dol_syslog("Rename ok");
// Rename docs starting with $oldref with $newref
$listoffiles = dol_dir_list($dirdest.'/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
2021-02-23 19:46:19 +00:00
foreach ($listoffiles as $fileentry) {
$dirsource = $fileentry['name'];
$dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
$dirsource = $fileentry['path'].'/'.$dirsource;
$dirdest = $fileentry['path'].'/'.$dirdest;
2018-08-30 07:40:04 +00:00
@rename($dirsource, $dirdest);
}
}
}
2012-07-06 11:43:59 +00:00
}
}
2018-08-30 07:40:04 +00:00
// Set new ref and current status
2021-02-23 19:46:19 +00:00
if (!$error) {
2018-08-30 07:40:04 +00:00
$this->ref = $num;
$this->statut = self::STATUS_VALIDATED; // deprecated
$this->status = self::STATUS_VALIDATED;
2018-08-30 07:40:04 +00:00
}
2021-02-23 19:46:19 +00:00
if (!$error) {
2018-08-30 07:40:04 +00:00
$this->db->commit();
return 1;
2020-05-21 13:05:19 +00:00
} else {
2012-07-06 11:43:59 +00:00
$this->db->rollback();
return -1;
}
}
2011-09-12 17:08:02 +00:00
2019-02-24 23:56:48 +00:00
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2018-08-30 07:40:04 +00:00
/**
* Set draft status
*
* @param User $user Object user that modify
* @param int $idwarehouse Warehouse ID to use for stock change (Used only if option STOCK_CALCULATE_ON_VALIDATE_ORDER is on)
2023-12-01 18:51:32 +00:00
* @return int Return integer <0 if KO, >0 if OK
2018-08-30 07:40:04 +00:00
*/
public function setDraft($user, $idwarehouse = -1)
{
//phpcs:enable
2025-01-19 16:38:34 +00:00
global $langs;
2017-06-09 07:25:15 +00:00
$error = 0;
2017-06-09 07:25:15 +00:00
2018-08-30 07:40:04 +00:00
// Protection
if ($this->status <= self::STATUS_DRAFT && !getDolGlobalInt('ORDER_REOPEN_TO_DRAFT')) {
2018-08-30 07:40:04 +00:00
return 0;
2016-01-22 23:38:17 +00:00
}
2023-11-27 10:39:32 +00:00
if (!((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'creer'))
|| (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'order_advance', 'validate')))) {
$this->error = 'Permission denied';
2018-08-30 07:40:04 +00:00
return -1;
}
2011-09-12 17:08:02 +00:00
2019-05-01 07:38:27 +00:00
dol_syslog(__METHOD__, LOG_DEBUG);
2018-08-30 07:40:04 +00:00
$this->db->begin();
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2025-03-16 19:15:52 +00:00
$sql .= " SET fk_statut = ".((int) self::STATUS_DRAFT).",";
2022-01-15 23:13:14 +00:00
$sql .= " fk_user_modif = ".((int) $user->id);
2021-08-23 17:33:24 +00:00
$sql .= " WHERE rowid = ".((int) $this->id);
2018-08-30 07:40:04 +00:00
2021-02-23 19:46:19 +00:00
if ($this->db->query($sql)) {
$this->oldcopy = clone $this;
// If stock is decremented on validate order, we must reincrement it
if (isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_VALIDATE_ORDER') == 1) {
2018-08-30 07:40:04 +00:00
$result = 0;
require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
$langs->load("agenda");
$num = count($this->lines);
2021-02-23 19:46:19 +00:00
for ($i = 0; $i < $num; $i++) {
if ($this->lines[$i]->fk_product > 0) {
2018-08-30 07:40:04 +00:00
$mouvP = new MouvementStock($this->db);
$mouvP->origin = &$this;
$mouvP->setOrigin($this->element, $this->id);
2018-08-30 07:40:04 +00:00
// We increment stock of product (and sub-products)
$result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, 0, $langs->trans("OrderBackToDraftInDolibarr", $this->ref));
2021-02-23 19:46:19 +00:00
if ($result < 0) {
2023-12-04 11:01:45 +00:00
$error++;
$this->setErrorsFromObject($mouvP);
2023-12-04 11:01:45 +00:00
break;
2021-02-23 19:46:19 +00:00
}
}
2018-08-30 07:40:04 +00:00
}
}
2011-09-12 17:08:02 +00:00
2018-08-30 07:40:04 +00:00
if (!$error) {
// Call trigger
$result = $this->call_trigger('ORDER_UNVALIDATE', $user);
2021-02-23 19:46:19 +00:00
if ($result < 0) {
$error++;
}
2018-08-30 07:40:04 +00:00
}
2011-09-12 17:08:02 +00:00
2018-08-30 07:40:04 +00:00
if (!$error) {
$this->statut = self::STATUS_DRAFT; // deprecated
2025-03-16 19:15:52 +00:00
$this->status = self::STATUS_DRAFT;
2018-08-30 07:40:04 +00:00
$this->db->commit();
return 1;
} else {
2018-08-30 07:40:04 +00:00
$this->db->rollback();
return -1;
}
2020-05-21 13:05:19 +00:00
} else {
$this->error = $this->db->error();
2018-08-30 07:40:04 +00:00
$this->db->rollback();
return -1;
}
}
2011-09-12 17:08:02 +00:00
2019-02-28 22:19:58 +00:00
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2018-08-30 07:40:04 +00:00
/**
* Tag the order as validated (opened)
* Function used when order is reopend after being closed.
*
* @param User $user Object user that change status
2023-12-06 14:46:39 +00:00
* @return int Return integer <0 if KO, 0 if nothing is done, >0 if OK
2018-08-30 07:40:04 +00:00
*/
2019-02-24 23:56:48 +00:00
public function set_reopen($user)
2018-08-30 07:40:04 +00:00
{
// phpcs:enable
$error = 0;
if ($this->status != self::STATUS_CANCELED && $this->status != self::STATUS_CLOSED) {
2018-08-30 07:40:04 +00:00
dol_syslog(get_class($this)."::set_reopen order has not status closed", LOG_WARNING);
return 0;
}
2011-09-12 17:08:02 +00:00
2018-08-30 07:40:04 +00:00
$this->db->begin();
$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2022-01-15 23:13:14 +00:00
$sql .= ' SET fk_statut='.self::STATUS_VALIDATED.', facture=0,';
$sql .= " fk_user_modif = ".((int) $user->id);
2021-08-27 21:36:06 +00:00
$sql .= " WHERE rowid = ".((int) $this->id);
2011-09-12 17:08:02 +00:00
2018-08-30 07:40:04 +00:00
dol_syslog(get_class($this)."::set_reopen", LOG_DEBUG);
$resql = $this->db->query($sql);
2021-02-23 19:46:19 +00:00
if ($resql) {
2018-08-30 07:40:04 +00:00
// Call trigger
$result = $this->call_trigger('ORDER_REOPEN', $user);
2021-02-23 19:46:19 +00:00
if ($result < 0) {
$error++;
}
2018-08-30 07:40:04 +00:00
// End call triggers
2020-05-21 13:05:19 +00:00
} else {
2018-08-30 07:40:04 +00:00
$error++;
$this->error = $this->db->lasterror();
2018-08-30 07:40:04 +00:00
dol_print_error($this->db);
}
2011-09-12 17:08:02 +00:00
2021-02-23 19:46:19 +00:00
if (!$error) {
$this->statut = self::STATUS_VALIDATED; // deprecated
$this->status = self::STATUS_VALIDATED;
2018-08-30 07:40:04 +00:00
$this->billed = 0;
2011-09-12 17:08:02 +00:00
2018-08-30 07:40:04 +00:00
$this->db->commit();
return 1;
2020-05-21 13:05:19 +00:00
} else {
2021-02-23 19:46:19 +00:00
foreach ($this->errors as $errmsg) {
2018-08-30 07:40:04 +00:00
dol_syslog(get_class($this)."::set_reopen ".$errmsg, LOG_ERR);
$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2018-08-30 07:40:04 +00:00
}
$this->db->rollback();
return -1 * $error;
2018-08-30 07:40:04 +00:00
}
}
2011-09-12 17:08:02 +00:00
2018-08-30 07:40:04 +00:00
/**
* Close order
*
* @param User $user Object user that close
2018-08-30 07:40:04 +00:00
* @param int $notrigger 1=Does not execute triggers, 0=Execute triggers
2025-02-11 15:28:37 +00:00
* @return int Return integer <0 if KO, 0=Nothing done, >0 if OK
2018-08-30 07:40:04 +00:00
*/
2019-02-24 23:56:48 +00:00
public function cloture($user, $notrigger = 0)
2018-08-30 07:40:04 +00:00
{
$error = 0;
Fix #28071 - New branch to fix bad merge (#28083) * Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop * Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop * Qual: Introduce getDataToShowPhoto to prepare generic code * Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop * Fix missing trans * Fix langs * Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop * Remove useless files in web templates * Clean code * Fix duplicate translation key * Fix duplicate translation key * Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop * Fix duplicate key * Fix $object * Debug v19 * WIP SMSing * Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop * WIP EMAILINGS_SUPPORT_ALSO_SMS * Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop * Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop * WIP SMSing * Debug the "validate" feature * Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop * Clean code * Move rights->x->y into hasRight('x', 'y') * Move rights->x->y into hasRight('x', 'y') * Move rights->x->y into hasRight('x', 'y') * Move rights->x->y into hasRight('x', 'y') * Move rights->x->y into hasRight('x', 'y') * Move rights->x->y into hasRight('x', 'y') * Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop * Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop * Enhance rector to fix empty($user->rights->modulex->perm1) * Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop * Fix template to use v19 dev rules * Fix use v19 dev rules * Fix phpunit * Debug v19 * Clean code * Use rector to convert user->rights into user->hasRight * Clean code * Use rector to convert user->rights into user->hasRight * Use rector to convert user->rights into user->hasRight * Clean code * Fix phpcs * add editorconfig for sql files (#27999) Co-authored-by: Laurent Destailleur <eldy@destailleur.fr> * add model_pdf field in llx_ticket-ticket.sql (#27996) * add model_pdf field in llx_ticket-ticket.sql * Update 19.0.0-20.0.0.sql * Update 19.0.0-20.0.0.sql * Improve wording in README (#27994) * fix phpstan (#27989) * fix phpstan * Update UserRightsToFunction.php --------- Co-authored-by: Laurent Destailleur <eldy@destailleur.fr> * Qual: Fix spelling/working in datapolicy translations (#28006) # Qual: Fix spelling/wording in datapolicy translations Fixed some spelling and wording in datapolicy translations. * qual: phpstan for htdocs/ticket/class/ticketstats.class.php (#27986) htdocs/ticket/class/ticketstats.class.php 98 Parameter #1 $year (string) of method TicketStats::getNbByMonth() should be compatible with parameter $year (int) of method Stats::getNbByMonth() * Merge branch '19.0' of git@github.com:Dolibarr/dolibarr.git into develop * Fix user with readonly perm on email template must be able to read. * Fix doc * Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop * Better message * Add missing fields in merge of thirdparty * Debug v19 selection of ticket printer per terminal * Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop * Use constant * NEW: Adding a recipient on emails sent, change status to sent partialy. * fix travis (#28052) * fix travis * Update partnership.class.php * fix php doc (#28047) * fix undefined array key (#28048) * Add region and departament for Cuba (#28046) * Update llx_10_c_regions.sql Add Cuba Regions (id_country=77) * Update llx_20_c_departements.sql Add Provinces Cuba (id country=77) * Find the typo (#28050) * Find the typo * clean code * add last_main_doc field to product (#28045) * add las_main_doc field to product * add field fetch * NEW Add Categorie filter for ActionComm (#28041) * New Add Categorie filter for ActionComm New Add Categorie filter for ActionComm * Fix space errors Fix space errors * Fix space errors 2 Fix space errors 2 * Update cunits.class.php (#28056) FIX: error SQL when creating a Cunit * Update codespell-lines-ignore.txt to avoid PR merge conflict --------- Co-authored-by: Laurent Destailleur <eldy@destailleur.fr> Co-authored-by: Frédéric FRANCE <frederic34@users.noreply.github.com> Co-authored-by: thibdrev <thibault.drevet@gmail.com> Co-authored-by: sonikf <93765174+sonikf@users.noreply.github.com> Co-authored-by: Ikarus <44511582+LeKarSol@users.noreply.github.com> Co-authored-by: Anthony Damhet <73399671+EchoLoGeek@users.noreply.github.com> Co-authored-by: Quentin-Seekness <72733832+Quentin-Seekness@users.noreply.github.com>
2024-02-09 14:58:49 +00:00
$usercanclose = ((!getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'creer'))
|| (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && $user->hasRight('commande', 'order_advance', 'close')));
2021-02-23 19:46:19 +00:00
if ($usercanclose) {
if ($this->status == self::STATUS_CLOSED) {
return 0;
}
2018-08-30 07:40:04 +00:00
$this->db->begin();
2011-09-12 17:08:02 +00:00
$now = dol_now();
2015-10-15 10:17:57 +00:00
$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
$sql .= ' SET fk_statut = '.self::STATUS_CLOSED.',';
2021-08-27 21:36:06 +00:00
$sql .= ' fk_user_cloture = '.((int) $user->id).',';
2022-01-15 23:13:14 +00:00
$sql .= " date_cloture = '".$this->db->idate($now)."',";
$sql .= " fk_user_modif = ".((int) $user->id);
2021-08-27 21:36:06 +00:00
$sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > '.self::STATUS_DRAFT;
2012-07-02 17:30:37 +00:00
2021-02-23 19:46:19 +00:00
if ($this->db->query($sql)) {
if (!$notrigger) {
2018-08-30 07:40:04 +00:00
// Call trigger
$result = $this->call_trigger('ORDER_CLOSE', $user);
2021-02-23 19:46:19 +00:00
if ($result < 0) {
$error++;
}
2018-08-30 07:40:04 +00:00
// End call triggers
}
2021-02-23 19:46:19 +00:00
if (!$error) {
$this->statut = self::STATUS_CLOSED; // deprecated
$this->status = self::STATUS_CLOSED;
2018-08-30 07:40:04 +00:00
$this->db->commit();
return 1;
2020-05-21 13:05:19 +00:00
} else {
2018-08-30 07:40:04 +00:00
$this->db->rollback();
return -1;
}
2020-05-21 13:05:19 +00:00
} else {
$this->error = $this->db->lasterror();
2018-08-30 07:40:04 +00:00
$this->db->rollback();
return -1;
}
}
return 0;
}
2025-02-14 13:29:49 +00:00
/**
2024-08-26 13:45:38 +00:00
* Sets object to given categories.
2025-02-14 13:29:49 +00:00
*
* Adds it to non existing supplied categories.
2024-08-26 13:45:38 +00:00
* Deletes object from existing categories not supplied.
2025-02-14 13:29:49 +00:00
* Existing categories are left untouch.
*
2024-08-26 13:45:38 +00:00
* @param int[]|int $categories Category ID or array of Categories IDs
*
* @return int Return integer <0 if KO, >0 if OK
2025-02-14 13:29:49 +00:00
*/
public function setCategories($categories)
{
require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
return parent::setCategoriesCommon($categories, Categorie::TYPE_ORDER);
}
2018-08-30 07:40:04 +00:00
/**
* Cancel an order
* If stock is decremented on order validation, we must reincrement it
*
* @param User $user User making action
2018-08-30 07:40:04 +00:00
* @param int $idwarehouse Id warehouse to use for stock change.
2023-12-01 18:51:32 +00:00
* @return int Return integer <0 if KO, >0 if OK
2018-08-30 07:40:04 +00:00
*/
public function cancel($user, $idwarehouse = -1)
2018-08-30 07:40:04 +00:00
{
2025-01-14 21:05:08 +00:00
global $user, $langs;
2018-08-30 07:40:04 +00:00
$error = 0;
2018-08-30 07:40:04 +00:00
$this->db->begin();
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2022-01-15 23:13:14 +00:00
$sql .= " SET fk_statut = ".self::STATUS_CANCELED.",";
$sql .= " fk_user_modif = ".((int) $user->id);
2021-08-23 17:33:24 +00:00
$sql .= " WHERE rowid = ".((int) $this->id);
$sql .= " AND fk_statut = ".self::STATUS_VALIDATED;
2018-08-30 07:40:04 +00:00
dol_syslog(get_class($this)."::cancel", LOG_DEBUG);
2021-02-23 19:46:19 +00:00
if ($this->db->query($sql)) {
2018-08-30 07:40:04 +00:00
// If stock is decremented on validate order, we must reincrement it
if (isModEnabled('stock') && getDolGlobalInt('STOCK_CALCULATE_ON_VALIDATE_ORDER') == 1) {
2018-08-30 07:40:04 +00:00
require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
$langs->load("agenda");
$num = count($this->lines);
2021-02-23 19:46:19 +00:00
for ($i = 0; $i < $num; $i++) {
if ($this->lines[$i]->fk_product > 0) {
2018-08-30 07:40:04 +00:00
$mouvP = new MouvementStock($this->db);
$mouvP->setOrigin($this->element, $this->id);
2018-08-30 07:40:04 +00:00
// We increment stock of product (and sub-products)
$result = $mouvP->reception($user, $this->lines[$i]->fk_product, $idwarehouse, $this->lines[$i]->qty, 0, $langs->trans("OrderCanceledInDolibarr", $this->ref)); // price is 0, we don't want WAP to be changed
2021-02-23 19:46:19 +00:00
if ($result < 0) {
2018-08-30 07:40:04 +00:00
$error++;
$this->setErrorsFromObject($mouvP);
2018-08-30 07:40:04 +00:00
break;
}
}
}
}
2021-02-23 19:46:19 +00:00
if (!$error) {
2018-08-30 07:40:04 +00:00
// Call trigger
$result = $this->call_trigger('ORDER_CANCEL', $user);
2021-02-23 19:46:19 +00:00
if ($result < 0) {
$error++;
}
2018-08-30 07:40:04 +00:00
// End call triggers
}
2021-02-23 19:46:19 +00:00
if (!$error) {
$this->statut = self::STATUS_CANCELED; // deprecated
$this->status = self::STATUS_CANCELED;
2018-08-30 07:40:04 +00:00
$this->db->commit();
return 1;
2020-05-21 13:05:19 +00:00
} else {
2021-02-23 19:46:19 +00:00
foreach ($this->errors as $errmsg) {
2018-08-30 07:40:04 +00:00
dol_syslog(get_class($this)."::cancel ".$errmsg, LOG_ERR);
$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2018-08-30 07:40:04 +00:00
}
$this->db->rollback();
return -1 * $error;
2018-08-30 07:40:04 +00:00
}
2020-05-21 13:05:19 +00:00
} else {
$this->error = $this->db->error();
2018-08-30 07:40:04 +00:00
$this->db->rollback();
return -1;
}
}
/**
2025-04-24 15:17:05 +00:00
* Create sale order
2018-08-30 07:40:04 +00:00
* Note that this->ref can be set or empty. If empty, we will use "(PROV)"
*
* @param User $user Object user that make creation
2018-08-30 07:40:04 +00:00
* @param int $notrigger Disable all triggers
2023-12-01 18:51:32 +00:00
* @return int Return integer <0 if KO, >0 if OK
2018-08-30 07:40:04 +00:00
*/
2019-02-24 23:56:48 +00:00
public function create($user, $notrigger = 0)
2018-08-30 07:40:04 +00:00
{
global $conf, $langs, $mysoc;
$error = 0;
2018-08-30 07:40:04 +00:00
// Clean parameters
if (empty($this->socid)) {
$this->error = 'ErrorWrongParameters';
return -1;
}
2020-11-16 11:20:18 +00:00
// Set tmp vars
2018-08-30 07:40:04 +00:00
$date = ($this->date_commande ? $this->date_commande : $this->date);
$this->import_key = trim((string) $this->import_key);
$delivery_date = $this->delivery_date;
2018-08-30 07:40:04 +00:00
// Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
2021-02-23 19:46:19 +00:00
if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
list($this->fk_multicurrency, $this->multicurrency_tx) = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $date);
} else {
$this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
}
if (empty($this->fk_multicurrency)) {
2018-08-30 07:40:04 +00:00
$this->multicurrency_code = $conf->currency;
$this->fk_multicurrency = 0;
$this->multicurrency_tx = 1;
}
// setEntity will set entity with the right value if empty or change it for the right value if multicompany module is active
$this->entity = setEntity($this);
2018-08-30 07:40:04 +00:00
dol_syslog(get_class($this)."::create user=".$user->id);
// Check parameters
2021-02-23 19:46:19 +00:00
if (!empty($this->ref)) { // We check that ref is not already used
$result = self::isExistingObject($this->element, 0, $this->ref); // Check ref is not yet used
2021-02-23 19:46:19 +00:00
if ($result > 0) {
$this->error = 'ErrorRefAlreadyExists';
dol_syslog(get_class($this)."::create ".$this->error, LOG_WARNING);
2018-08-30 07:40:04 +00:00
$this->db->rollback();
return -1;
}
}
$soc = new Societe($this->db);
$result = $soc->fetch($this->socid);
2021-02-23 19:46:19 +00:00
if ($result < 0) {
$this->error = "Failed to fetch company";
2018-08-30 07:40:04 +00:00
dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
return -2;
}
2023-11-27 10:39:32 +00:00
if (getDolGlobalString('ORDER_REQUIRE_SOURCE') && $this->source < 0) {
$this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Source"));
2018-08-30 07:40:04 +00:00
dol_syslog(get_class($this)."::create ".$this->error, LOG_ERR);
return -1;
}
$this->date_creation = dol_now();
2018-08-30 07:40:04 +00:00
$this->db->begin();
$sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
2022-09-23 07:35:17 +00:00
$sql .= " ref, fk_soc, date_creation, fk_user_author, fk_projet, date_commande, source, note_private, note_public, ref_ext, ref_client";
$sql .= ", model_pdf, fk_cond_reglement, deposit_percent, fk_mode_reglement, fk_account, fk_availability, fk_input_reason, date_livraison, fk_delivery_address";
$sql .= ", fk_shipping_method";
$sql .= ", fk_warehouse";
$sql .= ", fk_incoterms, location_incoterms";
$sql .= ", entity, module_source, pos_source";
$sql .= ", fk_multicurrency";
$sql .= ", multicurrency_code";
$sql .= ", multicurrency_tx";
$sql .= ", import_key";
$sql .= ")";
$sql .= " VALUES ('(PROV)', ".((int) $this->socid).", '".$this->db->idate($this->date_creation)."', ".($user->id > 0 ? ((int) $user->id) : "null");
2021-08-27 20:42:04 +00:00
$sql .= ", ".($this->fk_project > 0 ? ((int) $this->fk_project) : "null");
$sql .= ", '".$this->db->idate($date)."'";
2025-03-04 17:02:29 +00:00
$sql .= ", ".($this->source >= 0 && $this->source != '' ? $this->db->escape((string) $this->source) : 'null');
$sql .= ", '".$this->db->escape($this->note_private)."'";
$sql .= ", '".$this->db->escape($this->note_public)."'";
$sql .= ", ".($this->ref_ext ? "'".$this->db->escape($this->ref_ext)."'" : "null");
$sql .= ", ".($this->ref_client ? "'".$this->db->escape($this->ref_client)."'" : "null");
2020-09-09 23:49:09 +00:00
$sql .= ", '".$this->db->escape($this->model_pdf)."'";
2021-08-27 20:42:04 +00:00
$sql .= ", ".($this->cond_reglement_id > 0 ? ((int) $this->cond_reglement_id) : "null");
2025-03-04 17:02:29 +00:00
$sql .= ", ".(!empty($this->deposit_percent) ? "'".$this->db->escape((string) $this->deposit_percent)."'" : "null");
2021-08-27 20:42:04 +00:00
$sql .= ", ".($this->mode_reglement_id > 0 ? ((int) $this->mode_reglement_id) : "null");
$sql .= ", ".($this->fk_account > 0 ? ((int) $this->fk_account) : 'NULL');
$sql .= ", ".($this->availability_id > 0 ? ((int) $this->availability_id) : "null");
$sql .= ", ".($this->demand_reason_id > 0 ? ((int) $this->demand_reason_id) : "null");
2020-11-16 11:56:13 +00:00
$sql .= ", ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : "null");
2021-08-27 20:42:04 +00:00
$sql .= ", ".($this->fk_delivery_address > 0 ? ((int) $this->fk_delivery_address) : 'NULL');
$sql .= ", ".(!empty($this->shipping_method_id) && $this->shipping_method_id > 0 ? ((int) $this->shipping_method_id) : 'NULL');
$sql .= ", ".(!empty($this->warehouse_id) && $this->warehouse_id > 0 ? ((int) $this->warehouse_id) : 'NULL');
$sql .= ", ".(int) $this->fk_incoterms;
$sql .= ", '".$this->db->escape($this->location_incoterms)."'";
$sql .= ", ".(int) $this->entity;
$sql .= ", ".($this->module_source ? "'".$this->db->escape($this->module_source)."'" : "null");
2025-06-18 14:09:34 +00:00
$sql .= ", ".((!is_null($this->pos_source) && $this->pos_source != '') ? "'".$this->db->escape($this->pos_source)."'" : "null"); // Can be null, '', '0', '1'
$sql .= ", ".(int) $this->fk_multicurrency;
$sql .= ", '".$this->db->escape($this->multicurrency_code)."'";
2021-08-27 20:42:04 +00:00
$sql .= ", ".(float) $this->multicurrency_tx;
$sql .= ", '".$this->db->escape($this->import_key)."'";
$sql .= ")";
2018-08-30 07:40:04 +00:00
dol_syslog(get_class($this)."::create", LOG_DEBUG);
$resql = $this->db->query($sql);
2021-02-23 19:46:19 +00:00
if ($resql) {
2018-08-30 07:40:04 +00:00
$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'commande');
2021-02-23 19:46:19 +00:00
if ($this->id) {
$fk_parent_line = 0;
$num = count($this->lines);
2018-08-30 07:40:04 +00:00
/*
* Insert products details into db
*/
2021-02-23 19:46:19 +00:00
for ($i = 0; $i < $num; $i++) {
2018-08-30 07:40:04 +00:00
$line = $this->lines[$i];
// Test and convert into OrderLine object this->lines[$i]. When coming from REST API, we may still have an array
2018-08-30 07:40:04 +00:00
//if (! is_object($line)) $line=json_decode(json_encode($line), false); // convert recursively array into object.
2021-02-23 19:46:19 +00:00
if (!is_object($line)) {
$lineobj = new OrderLine($this->db);
foreach ($line as $key => $val) {
$lineobj->$key = $val;
}
$line = $lineobj;
$this->lines[$i] = $line;
2021-02-23 19:46:19 +00:00
}
2018-08-30 07:40:04 +00:00
// Reset fk_parent_line for no child products and special product
if (($line->product_type != 9 && empty($line->fk_parent_line)) || $line->product_type == 9) {
$fk_parent_line = 0;
}
// Complete vat rate with code
$vatrate = $line->tva_tx;
if ($line->vat_src_code && !preg_match('/\(.*\)/', (string) $vatrate)) {
2021-02-23 19:46:19 +00:00
$vatrate .= ' ('.$line->vat_src_code.')';
}
2018-08-30 07:40:04 +00:00
2023-11-27 10:39:32 +00:00
if (getDolGlobalString('MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION')) {
$originid = $line->origin_id;
2025-04-24 15:17:05 +00:00
$origintype = empty($line->origin_type) ? $line->origin : $line->origin_type;
} else { // old but bugged version (we store id of line and type of parent object)
$originid = $line->id;
$origintype = $this->element;
}
2018-08-30 07:40:04 +00:00
2020-09-04 19:11:58 +00:00
// ref_ext
if (empty($line->ref_ext)) {
$line->ref_ext = '';
}
$result = $this->addline(
2018-08-30 07:40:04 +00:00
$line->desc,
$line->subprice,
$line->qty,
$vatrate,
$line->localtax1_tx,
$line->localtax2_tx,
$line->fk_product,
$line->remise_percent,
$line->info_bits,
$line->fk_remise_except,
'HT',
0,
$line->date_start,
$line->date_end,
$line->product_type,
$line->rang,
$line->special_code,
$fk_parent_line,
$line->fk_fournprice,
$line->pa_ht,
$line->label,
$line->array_options,
$line->fk_unit,
2025-03-04 17:02:29 +00:00
(string) $origintype,
$originid,
2020-09-04 19:26:57 +00:00
0,
$line->ref_ext,
1
);
2025-02-10 17:09:32 +00:00
2021-02-23 19:46:19 +00:00
if ($result < 0) {
if ($result != self::STOCK_NOT_ENOUGH_FOR_ORDER) {
$this->error = $this->db->lasterror();
$this->errors[] = $this->error;
2018-08-30 07:40:04 +00:00
dol_print_error($this->db);
}
$this->db->rollback();
return -1;
}
// Defined the new fk_parent_line
if ($result > 0 && $line->product_type == 9) {
2018-08-30 07:40:04 +00:00
$fk_parent_line = $result;
}
}
$result = $this->update_price(1, 'auto', 0, $mysoc); // This method is designed to add line from user input so total calculation must be done using 'auto' mode.
2018-08-30 07:40:04 +00:00
// update ref
$initialref = '(PROV'.$this->id.')';
2021-02-23 19:46:19 +00:00
if (!empty($this->ref)) {
$initialref = $this->ref;
}
2018-08-30 07:40:04 +00:00
$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element." SET ref='".$this->db->escape($initialref)."' WHERE rowid=".((int) $this->id);
2021-02-23 19:46:19 +00:00
if ($this->db->query($sql)) {
$this->ref = $initialref;
2018-08-30 07:40:04 +00:00
2021-02-23 19:46:19 +00:00
if (!empty($this->linkedObjectsIds) && empty($this->linked_objects)) { // To use new linkedObjectsIds instead of old linked_objects
$this->linked_objects = $this->linkedObjectsIds; // TODO Replace linked_objects with linkedObjectsIds
}
2018-08-30 07:40:04 +00:00
// Add object linked
if (!empty($this->linked_objects) && is_array($this->linked_objects)) {
2021-02-23 19:46:19 +00:00
foreach ($this->linked_objects as $origin => $tmp_origin_id) {
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, ...))
foreach ($tmp_origin_id as $origin_id) {
2018-08-30 07:40:04 +00:00
$ret = $this->add_object_linked($origin, $origin_id);
2021-02-23 19:46:19 +00:00
if (!$ret) {
$this->error = $this->db->lasterror();
2018-08-30 07:40:04 +00:00
$error++;
}
2018-08-30 08:30:52 +00:00
}
2023-12-04 11:01:45 +00:00
} else { // Old behaviour, if linked_object has only one link per type, so is something like array('contract'=>id1))
$origin_id = $tmp_origin_id;
$ret = $this->add_object_linked($origin, $origin_id);
2021-02-23 19:46:19 +00:00
if (!$ret) {
$this->error = $this->db->lasterror();
$error++;
}
2018-08-30 07:40:04 +00:00
}
}
}
2018-08-30 07:40:04 +00:00
if (!$error && getDolGlobalString('MAIN_PROPAGATE_CONTACTS_FROM_ORIGIN') && !empty($this->origin) && !empty($this->origin_id)) { // Get contact from origin object
2025-04-24 15:17:05 +00:00
$originforcontact = empty($this->origin_type) ? $this->origin : $this->origin_type;
$originidforcontact = $this->origin_id;
2021-02-23 19:46:19 +00:00
if ($originforcontact == 'shipping') { // shipment and order share the same contacts. If creating from shipment we take data of order
require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
$exp = new Expedition($this->db);
$exp->fetch($this->origin_id);
$exp->fetchObjectLinked();
2021-02-23 19:46:19 +00:00
if (count($exp->linkedObjectsIds['commande']) > 0) {
foreach ($exp->linkedObjectsIds['commande'] as $key => $value) {
$originforcontact = 'commande';
2021-02-23 19:46:19 +00:00
if (is_object($value)) {
$originidforcontact = $value->id;
} else {
$originidforcontact = $value;
2018-08-30 07:40:04 +00:00
}
break; // We take first one
2018-08-30 07:40:04 +00:00
}
}
}
2018-08-30 07:40:04 +00:00
$sqlcontact = "SELECT ctc.code, ctc.source, ec.fk_socpeople FROM ".MAIN_DB_PREFIX."element_contact as ec, ".MAIN_DB_PREFIX."c_type_contact as ctc";
$sqlcontact .= " WHERE element_id = ".((int) $originidforcontact)." AND ec.fk_c_type_contact = ctc.rowid AND ctc.element = '".$this->db->escape($originforcontact)."'";
2018-08-30 07:40:04 +00:00
$resqlcontact = $this->db->query($sqlcontact);
2021-02-23 19:46:19 +00:00
if ($resqlcontact) {
while ($objcontact = $this->db->fetch_object($resqlcontact)) {
//print $objcontact->code.'-'.$objcontact->source.'-'.$objcontact->fk_socpeople."\n";
$this->add_contact($objcontact->fk_socpeople, $objcontact->code, $objcontact->source); // May failed because of duplicate key or because code of contact type does not exists for new object
2018-08-30 07:40:04 +00:00
}
2021-02-23 19:46:19 +00:00
} else {
dol_print_error($this->db);
2018-08-30 07:40:04 +00:00
}
}
2021-02-23 19:46:19 +00:00
if (!$error) {
$result = $this->insertExtraFields();
2021-02-23 19:46:19 +00:00
if ($result < 0) {
$error++;
}
2018-08-30 07:40:04 +00:00
}
2021-02-23 19:46:19 +00:00
if (!$error && !$notrigger) {
2018-08-30 07:40:04 +00:00
// Call trigger
$result = $this->call_trigger('ORDER_CREATE', $user);
2021-02-23 19:46:19 +00:00
if ($result < 0) {
$error++;
}
2018-08-30 07:40:04 +00:00
// End call triggers
}
2021-02-23 19:46:19 +00:00
if (!$error) {
2018-08-30 07:40:04 +00:00
$this->db->commit();
return $this->id;
2020-05-21 13:05:19 +00:00
} else {
2018-08-30 07:40:04 +00:00
$this->db->rollback();
return -1 * $error;
2018-08-30 07:40:04 +00:00
}
2020-05-21 13:05:19 +00:00
} else {
$this->error = $this->db->lasterror();
2018-08-30 07:40:04 +00:00
$this->db->rollback();
return -1;
}
}
2023-01-06 18:24:57 +00:00
return 0;
2020-05-21 13:05:19 +00:00
} else {
$this->error = $this->db->lasterror();
2018-08-30 07:40:04 +00:00
$this->db->rollback();
return -1;
}
}
/**
* Load an object from its id and create a new one in database
*
* @param User $user User making the clone
* @param int $socid Id of thirdparty
* @return int New id of clone
2018-08-30 07:40:04 +00:00
*/
public function createFromClone(User $user, $socid = 0)
2018-08-30 07:40:04 +00:00
{
2025-01-19 16:38:34 +00:00
global $user, $hookmanager;
2018-08-30 07:40:04 +00:00
$error = 0;
2018-08-30 07:40:04 +00:00
$this->db->begin();
// get lines so they will be clone
2021-02-23 19:46:19 +00:00
foreach ($this->lines as $line) {
2018-08-30 07:40:04 +00:00
$line->fetch_optionals();
2021-02-23 19:46:19 +00:00
}
2018-08-30 07:40:04 +00:00
// Load source object
$objFrom = clone $this;
2018-08-30 08:30:52 +00:00
// Change socid if needed
2021-02-23 19:46:19 +00:00
if (!empty($socid) && $socid != $this->socid) {
$objsoc = new Societe($this->db);
2018-08-30 08:30:52 +00:00
2021-02-23 19:46:19 +00:00
if ($objsoc->fetch($socid) > 0) {
$this->socid = $objsoc->id;
$this->cond_reglement_id = (!empty($objsoc->cond_reglement_id) ? $objsoc->cond_reglement_id : 0);
$this->deposit_percent = (!empty($objsoc->deposit_percent) ? $objsoc->deposit_percent : 0);
$this->mode_reglement_id = (!empty($objsoc->mode_reglement_id) ? $objsoc->mode_reglement_id : 0);
$this->fk_project = 0;
$this->fk_delivery_address = 0;
2018-08-30 07:40:04 +00:00
}
// TODO Change product price if multi-prices
}
2018-08-30 07:40:04 +00:00
$this->id = 0;
$this->ref = '';
$this->statut = self::STATUS_DRAFT; // deprecated
$this->status = self::STATUS_DRAFT;
2018-08-30 07:40:04 +00:00
// Clear fields
$this->user_author_id = $user->id;
2022-07-03 10:54:58 +00:00
$this->user_validation_id = 0;
$this->date = dol_now();
$this->date_commande = dol_now();
$this->date_creation = '';
$this->date_validation = '';
2023-11-27 10:39:32 +00:00
if (!getDolGlobalString('MAIN_KEEP_REF_CUSTOMER_ON_CLONING')) {
2021-02-23 19:46:19 +00:00
$this->ref_client = '';
2023-05-01 11:09:09 +00:00
$this->ref_customer = '';
2021-02-23 19:46:19 +00:00
}
2020-09-04 19:38:49 +00:00
2020-09-04 19:11:58 +00:00
// Do not clone ref_ext
$num = count($this->lines);
2021-02-23 19:46:19 +00:00
for ($i = 0; $i < $num; $i++) {
2020-09-04 19:11:58 +00:00
$this->lines[$i]->ref_ext = '';
}
2018-08-30 08:30:52 +00:00
// Create clone
$this->context['createfromclone'] = 'createfromclone';
$result = $this->create($user);
2021-02-23 19:46:19 +00:00
if ($result < 0) {
$error++;
}
2018-08-30 08:30:52 +00:00
2021-02-23 19:46:19 +00:00
if (!$error) {
// copy internal contacts
2021-02-23 19:46:19 +00:00
if ($this->copy_linked_contact($objFrom, 'internal') < 0) {
$error++;
2018-08-30 07:40:04 +00:00
}
}
2018-08-30 07:40:04 +00:00
2021-02-23 19:46:19 +00:00
if (!$error) {
// copy external contacts if same company
2021-02-23 19:46:19 +00:00
if ($this->socid == $objFrom->socid) {
if ($this->copy_linked_contact($objFrom, 'external') < 0) {
$error++;
2021-02-23 19:46:19 +00:00
}
2018-08-30 08:30:52 +00:00
}
}
2021-02-23 19:46:19 +00:00
if (!$error) {
// Hook of thirdparty module
2021-02-23 19:46:19 +00:00
if (is_object($hookmanager)) {
$parameters = array('objFrom' => $objFrom);
$action = '';
$reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2021-02-23 19:46:19 +00:00
if ($reshook < 0) {
2023-02-24 15:57:39 +00:00
$this->setErrorsFromObject($hookmanager);
2021-02-23 19:46:19 +00:00
$error++;
}
2018-08-30 08:30:52 +00:00
}
}
unset($this->context['createfromclone']);
// End
2021-02-23 19:46:19 +00:00
if (!$error) {
$this->db->commit();
return $this->id;
2020-05-21 13:05:19 +00:00
} else {
$this->db->rollback();
return -1;
}
2018-08-30 07:40:04 +00:00
}
/**
* Load an object from a proposal and create a new order into database
*
* @param Propal $object Object source
2018-08-30 07:40:04 +00:00
* @param User $user User making creation
2023-12-06 14:46:39 +00:00
* @return int Return integer <0 if KO, 0 if nothing done, 1 if OK
2018-08-30 07:40:04 +00:00
*/
2019-02-24 23:56:48 +00:00
public function createFromProposal($object, User $user)
2018-08-30 07:40:04 +00:00
{
global $conf, $hookmanager, $langs;
2018-08-30 07:40:04 +00:00
2024-03-13 17:20:41 +00:00
require_once DOL_DOCUMENT_ROOT . '/multicurrency/class/multicurrency.class.php';
require_once DOL_DOCUMENT_ROOT . '/core/class/extrafields.class.php';
2018-08-30 07:40:04 +00:00
$error = 0;
2018-08-30 07:40:04 +00:00
$this->date_commande = dol_now();
2023-08-05 23:14:36 +00:00
$this->date = dol_now();
2018-08-30 07:40:04 +00:00
$this->source = 0;
$num = count($object->lines);
2021-02-23 19:46:19 +00:00
for ($i = 0; $i < $num; $i++) {
2018-08-30 07:40:04 +00:00
$line = new OrderLine($this->db);
$line->libelle = $object->lines[$i]->libelle;
$line->label = $object->lines[$i]->label;
$line->desc = $object->lines[$i]->desc;
$line->price = $object->lines[$i]->price;
$line->subprice = $object->lines[$i]->subprice;
$line->vat_src_code = $object->lines[$i]->vat_src_code;
$line->tva_tx = $object->lines[$i]->tva_tx;
$line->localtax1_tx = $object->lines[$i]->localtax1_tx;
$line->localtax2_tx = $object->lines[$i]->localtax2_tx;
$line->qty = $object->lines[$i]->qty;
$line->fk_remise_except = $object->lines[$i]->fk_remise_except;
$line->remise_percent = $object->lines[$i]->remise_percent;
$line->fk_product = $object->lines[$i]->fk_product;
$line->info_bits = $object->lines[$i]->info_bits;
$line->product_type = $object->lines[$i]->product_type;
$line->rang = $object->lines[$i]->rang;
$line->special_code = $object->lines[$i]->special_code;
$line->fk_parent_line = $object->lines[$i]->fk_parent_line;
$line->fk_unit = $object->lines[$i]->fk_unit;
2018-08-30 07:40:04 +00:00
2023-08-05 23:14:36 +00:00
$line->date_start = $object->lines[$i]->date_start;
2018-08-30 07:40:04 +00:00
$line->date_end = $object->lines[$i]->date_end;
$line->fk_fournprice = $object->lines[$i]->fk_fournprice;
$marginInfos = getMarginInfos($object->lines[$i]->subprice, $object->lines[$i]->remise_percent, $object->lines[$i]->tva_tx, $object->lines[$i]->localtax1_tx, $object->lines[$i]->localtax2_tx, $object->lines[$i]->fk_fournprice, $object->lines[$i]->pa_ht);
$line->pa_ht = $marginInfos[0];
$line->marge_tx = $marginInfos[1];
$line->marque_tx = $marginInfos[2];
$line->origin = $object->element;
$line->origin_id = $object->lines[$i]->id;
2018-08-30 07:40:04 +00:00
// get extrafields from original line
2018-02-21 13:48:25 +00:00
$object->lines[$i]->fetch_optionals();
2021-02-23 19:46:19 +00:00
foreach ($object->lines[$i]->array_options as $options_key => $value) {
2015-10-15 10:17:57 +00:00
$line->array_options[$options_key] = $value;
2021-02-23 19:46:19 +00:00
}
2023-12-04 11:01:45 +00:00
$this->lines[$i] = $line;
2018-08-30 07:40:04 +00:00
}
$this->entity = $object->entity;
2018-08-30 07:40:04 +00:00
$this->socid = $object->socid;
$this->fk_project = $object->fk_project;
$this->cond_reglement_id = $object->cond_reglement_id;
$this->deposit_percent = $object->deposit_percent;
2018-08-30 07:40:04 +00:00
$this->mode_reglement_id = $object->mode_reglement_id;
$this->fk_account = $object->fk_account;
$this->availability_id = $object->availability_id;
$this->demand_reason_id = $object->demand_reason_id;
$this->delivery_date = $object->delivery_date;
2018-08-30 07:40:04 +00:00
$this->shipping_method_id = $object->shipping_method_id;
$this->warehouse_id = $object->warehouse_id;
$this->fk_delivery_address = $object->fk_delivery_address;
$this->contact_id = $object->contact_id;
2018-08-30 07:40:04 +00:00
$this->ref_client = $object->ref_client;
2023-05-01 11:09:09 +00:00
$this->ref_customer = $object->ref_client;
2023-11-27 10:39:32 +00:00
if (!getDolGlobalString('MAIN_DISABLE_PROPAGATE_NOTES_FROM_ORIGIN')) {
$this->note_private = $object->note_private;
$this->note_public = $object->note_public;
}
2018-08-30 07:40:04 +00:00
$this->origin = $object->element;
$this->origin_id = $object->id;
2018-08-30 07:40:04 +00:00
2023-12-04 11:01:45 +00:00
// Multicurrency (test on $this->multicurrency_tx because we should take the default rate only if not using origin rate)
if (isModEnabled('multicurrency')) {
if (!empty($object->multicurrency_code)) {
$this->multicurrency_code = $object->multicurrency_code;
}
2023-11-27 10:39:32 +00:00
if (getDolGlobalString('MULTICURRENCY_USE_ORIGIN_TX') && !empty($object->multicurrency_tx)) {
$this->multicurrency_tx = $object->multicurrency_tx;
}
2022-12-21 10:00:26 +00:00
if (!empty($this->multicurrency_code) && empty($this->multicurrency_tx)) {
2023-12-04 11:01:45 +00:00
$tmparray = MultiCurrency::getIdAndTxFromCode($this->db, $this->multicurrency_code, $this->date_commande);
$this->fk_multicurrency = $tmparray[0];
$this->multicurrency_tx = $tmparray[1];
2022-12-21 10:00:26 +00:00
} else {
2023-12-04 11:01:45 +00:00
$this->fk_multicurrency = MultiCurrency::getIdFromCode($this->db, $this->multicurrency_code);
2022-12-21 10:00:26 +00:00
}
if (empty($this->fk_multicurrency)) {
2023-12-04 11:01:45 +00:00
$this->multicurrency_code = $conf->currency;
$this->fk_multicurrency = 0;
$this->multicurrency_tx = 1;
2022-12-21 10:00:26 +00:00
}
}
2018-08-30 07:40:04 +00:00
// get extrafields from original line
$object->fetch_optionals();
2018-08-30 07:40:04 +00:00
$e = new ExtraFields($this->db);
2019-09-28 08:55:09 +00:00
$element_extrafields = $e->fetch_name_optionals_label($this->table_element);
2018-08-30 07:40:04 +00:00
foreach ($object->array_options as $options_key => $value) {
if (array_key_exists(str_replace('options_', '', $options_key), $element_extrafields)) {
2018-08-30 07:40:04 +00:00
$this->array_options[$options_key] = $value;
}
}
// Possibility to add external linked objects with hooks
$this->linked_objects[$this->origin] = $this->origin_id;
2021-06-29 14:30:36 +00:00
if (isset($object->other_linked_objects) && is_array($object->other_linked_objects) && !empty($object->other_linked_objects)) {
2018-08-30 08:30:52 +00:00
$this->linked_objects = array_merge($this->linked_objects, $object->other_linked_objects);
2018-08-30 07:40:04 +00:00
}
$ret = $this->create($user);
2021-02-23 19:46:19 +00:00
if ($ret > 0) {
2018-08-30 07:40:04 +00:00
// Actions hooked (by external module)
$hookmanager->initHooks(array('orderdao'));
$parameters = array('objFrom' => $object);
$action = '';
$reshook = $hookmanager->executeHooks('createFrom', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
2021-02-23 19:46:19 +00:00
if ($reshook < 0) {
2023-02-24 15:57:39 +00:00
$this->setErrorsFromObject($hookmanager);
2021-02-23 19:46:19 +00:00
$error++;
}
2018-08-30 07:40:04 +00:00
2021-02-23 19:46:19 +00:00
if (!$error) {
// Validate immediately the order
2023-11-27 10:39:32 +00:00
if (getDolGlobalString('ORDER_VALID_AFTER_CLOSE_PROPAL')) {
2018-08-30 07:40:04 +00:00
$this->fetch($ret);
$result = $this->valid($user);
if ($result > 0 && !getDolGlobalString('MAIN_DISABLE_PDF_AUTOUPDATE')) {
$this->fetch($this->id);
$this->fetch_thirdparty();
$outputlangs = $langs;
if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($this->thirdparty->default_lang)) {
$outputlangs = new Translate('', $conf);
$outputlangs->setDefaultLang($this->thirdparty->default_lang);
$outputlangs->loadLangs(array('main', 'orders'));
}
$this->generateDocument($this->model_pdf, $outputlangs);
}
2018-08-30 07:40:04 +00:00
}
return $ret;
2021-02-23 19:46:19 +00:00
} else {
return -1;
2018-08-30 07:40:04 +00:00
}
2021-02-23 19:46:19 +00:00
} else {
return -1;
2018-08-30 07:40:04 +00:00
}
}
/**
* Add an order line into database (linked to product/service or not)
*
* @param string $desc Description of line
* @param float $pu_ht Unit price (without tax)
* @param float $qty Quantite
2025-10-19 16:32:25 +00:00
* @param float|string $txtva Force VAT rate, -1 for auto (Can contain the vat_src_code too with syntax '9.9 (CODE)')
2018-08-30 07:40:04 +00:00
* @param float $txlocaltax1 Local tax 1 rate (deprecated, use instead txtva with code inside)
* @param float $txlocaltax2 Local tax 2 rate (deprecated, use instead txtva with code inside)
* @param int $fk_product Id of product
2019-03-18 12:45:24 +00:00
* @param float $remise_percent Percentage discount of the line
2019-06-29 14:29:32 +00:00
* @param int $info_bits Bits of type of lines
2018-08-30 07:40:04 +00:00
* @param int $fk_remise_except Id remise
* @param 'HT'|'TTC'|'' $price_base_type HT or TTC or '' for subtotals
2018-08-30 07:40:04 +00:00
* @param float $pu_ttc Prix unitaire TTC
* @param int|'' $date_start Start date of the line
* @param int|'' $date_end End date of the line
* @param int<0,1> $type Type of line (0=product, 1=service). Not used if fk_product is defined, the type of product is used.
2018-08-30 07:40:04 +00:00
* @param int $rang Position of line
* @param int $special_code Special code (also used by externals modules!)
* @param int $fk_parent_line Parent line
* @param int $fk_fournprice Id supplier price
2025-04-07 13:46:10 +00:00
* @param float|string $pa_ht Buying price without tax (Can be '' to keep AWP unchanged or a float value)
2018-08-30 07:40:04 +00:00
* @param string $label Label
* @param array<string,mixed> $array_options Extrafields array. Example array('options_codeforfield1'=>'valueforfield1', 'options_codeforfield2'=>'valueforfield2', ...)
* @param ?int $fk_unit Code of the unit to use. Null to use the default one
* @param string $origin Depend on global conf MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION can be 'orderdet', 'propaldet'..., else 'order','propal,'....
* @param int $origin_id Depend on global conf MAIN_CREATEFROM_KEEP_LINE_ORIGIN_INFORMATION can be Id of origin object (aka line id), else object id
* @param float $pu_ht_devise Unit price in currency
* @param string $ref_ext Line external reference
* @param int $noupdateafterinsertline No update after insert of line
2018-08-30 07:40:04 +00:00
* @return int >0 if OK, <0 if KO
*
2019-04-08 14:04:15 +00:00
* @see add_product()
2018-08-30 07:40:04 +00:00
*
* The parameters are already supposed to be correct and with final values upon calling this method.
* Also, for the VAT rate, it must have already been defined by the caller using the method get_default_tva(societe_vendeuse, societe_acheteuse, produit)
* and the description (desc) must already have the correct value (it's up to the caller to manage multilanguage)
2018-08-30 07:40:04 +00:00
*/
2023-12-20 13:22:45 +00:00
public function addline($desc, $pu_ht, $qty, $txtva, $txlocaltax1 = 0, $txlocaltax2 = 0, $fk_product = 0, $remise_percent = 0, $info_bits = 0, $fk_remise_except = 0, $price_base_type = 'HT', $pu_ttc = 0, $date_start = '', $date_end = '', $type = 0, $rang = -1, $special_code = 0, $fk_parent_line = 0, $fk_fournprice = null, $pa_ht = 0, $label = '', $array_options = array(), $fk_unit = null, $origin = '', $origin_id = 0, $pu_ht_devise = 0, $ref_ext = '', $noupdateafterinsertline = 0)
2018-08-30 07:40:04 +00:00
{
2025-01-19 16:38:34 +00:00
global $mysoc, $langs, $user;
2018-08-30 07:40:04 +00:00
$logtext = "::addline commandeid=$this->id, desc=$desc, pu_ht=$pu_ht, qty=$qty, txtva=$txtva, fk_product=$fk_product, remise_percent=$remise_percent";
$logtext .= ", info_bits=$info_bits, fk_remise_except=$fk_remise_except, price_base_type=$price_base_type, pu_ttc=$pu_ttc, date_start=$date_start";
2025-02-10 17:09:32 +00:00
$logtext .= ", date_end=$date_end, type=$type special_code=$special_code, fk_unit=$fk_unit, origin=$origin, origin_id=$origin_id, pu_ht_devise=$pu_ht_devise, ref_ext=$ref_ext rang=$rang";
2018-08-30 07:40:04 +00:00
dol_syslog(get_class($this).$logtext, LOG_DEBUG);
if ($this->status == self::STATUS_DRAFT) {
include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
2011-09-12 17:08:02 +00:00
// Clean parameters
2020-09-04 19:38:49 +00:00
2021-02-23 19:46:19 +00:00
if (empty($remise_percent)) {
$remise_percent = 0;
}
if (empty($qty)) {
$qty = 0;
}
if (empty($info_bits)) {
$info_bits = 0;
}
if (empty($rang)) {
$rang = 0;
}
if (empty($txtva)) {
$txtva = 0;
}
if (empty($txlocaltax1)) {
$txlocaltax1 = 0;
}
if (empty($txlocaltax2)) {
$txlocaltax2 = 0;
}
if (empty($fk_parent_line) || $fk_parent_line < 0) {
$fk_parent_line = 0;
}
if (empty($this->fk_multicurrency)) {
$this->fk_multicurrency = 0;
}
if (empty($ref_ext)) {
$ref_ext = '';
}
2024-01-23 15:07:59 +00:00
$remise_percent = (float) price2num($remise_percent);
$qty = (float) price2num($qty);
$pu_ht = price2num($pu_ht);
$pu_ht_devise = price2num($pu_ht_devise);
$pu_ttc = price2num($pu_ttc);
$pa_ht = price2num($pa_ht); // do not convert to float here, it breaks the functioning of $pa_ht_isemptystring
if (!preg_match('/\((.*)\)/', (string) $txtva)) {
$txtva = price2num($txtva); // $txtva can have format '5,1' or '5.1' or '5.1(XXX)', we must clean only if '5,1'
}
$txlocaltax1 = price2num($txlocaltax1);
$txlocaltax2 = price2num($txlocaltax2);
2021-02-23 19:46:19 +00:00
if ($price_base_type == 'HT') {
$pu = $pu_ht;
2020-05-21 13:05:19 +00:00
} else {
$pu = $pu_ttc;
}
$label = trim($label);
$desc = trim($desc);
2018-08-30 07:40:04 +00:00
// Check parameters
2021-02-23 19:46:19 +00:00
if ($type < 0) {
return -1;
}
if ($date_start && $date_end && $date_start > $date_end) {
$langs->load("errors");
$this->error = $langs->trans('ErrorStartDateGreaterEnd');
return -1;
}
2018-08-30 07:40:04 +00:00
$this->db->begin();
2018-08-30 07:40:04 +00:00
$product_type = $type;
2021-07-04 19:02:02 +00:00
if (!empty($fk_product) && $fk_product > 0) {
$product = new Product($this->db);
$result = $product->fetch($fk_product);
$product_type = $product->type;
2018-08-30 07:40:04 +00:00
if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_ORDER') && $product->isStockManaged()) {
// get real stock
$productChildrenNb = 0;
if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) {
$productChildrenNb = $product->hasFatherOrChild(1);
}
if ($productChildrenNb > 0) {
// compute real stock from each subcomponent
$product_stock = null;
$product->loadStockForVirtualProduct('warehouseopen', $qty);
foreach ($product->stock_warehouse as $componentStockWarehouse) {
if ($product_stock === null) {
$product_stock = $componentStockWarehouse->real;
} else {
$product_stock = min($product_stock, $componentStockWarehouse->real);
}
}
if ($product_stock === null) {
$product_stock = 0;
}
} else {
$product_stock = $product->stock_reel;
}
if ($product_stock < $qty) {
$langs->load("errors");
$this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnOrder', $product->ref);
$this->errors[] = $this->error;
dol_syslog(get_class($this)."::addline error=Product ".$product->ref.": ".$this->error, LOG_ERR);
$this->db->rollback();
return self::STOCK_NOT_ENOUGH_FOR_ORDER;
}
2018-08-30 07:40:04 +00:00
}
}
// Calculation of the gross total (TTC) and VAT for the line from qty, pu, remise_percent and txtva
// VERY IMPORTANT: It's at the time of line insertion that we must store the net, VAT, and gross amounts,
// and this is done at the line level, which has its own VAT rate
2018-08-30 07:40:04 +00:00
$localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
2018-08-30 07:40:04 +00:00
if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) {
2025-01-29 18:15:57 +00:00
$tmpproduct = new Product($this->db);
$result = $tmpproduct->fetch($fk_product);
2026-01-29 14:20:52 +00:00
if (abs((float) $qty) < $tmpproduct->packaging) {
2025-01-29 18:15:57 +00:00
$qty = (float) $tmpproduct->packaging;
2026-01-29 12:01:49 +00:00
setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
} else {
2026-01-29 12:01:49 +00:00
if (!empty($tmpproduct->packaging) && (float) price2num(fmod((float) $qty, (float) $tmpproduct->packaging), 'MS')) {
2026-01-29 14:20:52 +00:00
$coeff = intval(abs((float) $qty) / $tmpproduct->packaging) + 1;
2025-01-29 18:15:57 +00:00
$qty = price2num((float) $tmpproduct->packaging * $coeff, 'MS');
2026-01-29 12:01:49 +00:00
setEventMessages($langs->trans('QtyRecalculatedWithPackaging'), null, 'warnings');
}
}
}
2018-08-30 08:30:52 +00:00
// Clean vat code
2020-01-13 14:58:03 +00:00
$reg = array();
$vat_src_code = '';
2021-02-23 19:46:19 +00:00
if (preg_match('/\((.*)\)/', $txtva, $reg)) {
2018-08-30 07:40:04 +00:00
$vat_src_code = $reg[1];
$txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
2018-08-30 07:40:04 +00:00
}
2025-03-04 17:02:29 +00:00
$tabprice = calcul_price_total($qty, (float) $pu, $remise_percent, $txtva, (float) $txlocaltax1, (float) $txlocaltax2, 0, $price_base_type, $info_bits, $product_type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, (float) $pu_ht_devise);
2018-08-30 07:40:04 +00:00
/*var_dump($txlocaltax1);
2018-08-30 08:30:52 +00:00
var_dump($txlocaltax2);
var_dump($localtaxes_type);
var_dump($tabprice);
var_dump($tabprice[9]);
var_dump($tabprice[10]);
exit;*/
2018-08-30 07:40:04 +00:00
$total_ht = $tabprice[0];
$total_tva = $tabprice[1];
$total_ttc = $tabprice[2];
$total_localtax1 = $tabprice[9];
$total_localtax2 = $tabprice[10];
$pu_ht = $tabprice[3];
// MultiCurrency
$multicurrency_total_ht = $tabprice[16];
$multicurrency_total_tva = $tabprice[17];
$multicurrency_total_ttc = $tabprice[18];
$pu_ht_devise = $tabprice[19];
// Rang to use
2019-08-09 22:46:20 +00:00
$ranktouse = $rang;
2025-02-10 17:09:32 +00:00
2021-02-23 19:46:19 +00:00
if ($ranktouse == -1) {
2018-08-30 07:40:04 +00:00
$rangmax = $this->line_max($fk_parent_line);
2019-08-09 22:46:20 +00:00
$ranktouse = $rangmax + 1;
2018-08-30 07:40:04 +00:00
}
// Insert line
$this->line = new OrderLine($this->db);
2018-08-30 07:40:04 +00:00
$this->line->context = $this->context;
$this->line->fk_commande = $this->id;
$this->line->label = $label;
$this->line->desc = $desc;
$this->line->qty = (float) $qty;
2020-09-04 19:11:58 +00:00
$this->line->ref_ext = $ref_ext;
$this->line->vat_src_code = $vat_src_code;
$this->line->tva_tx = $txtva;
$this->line->localtax1_tx = ($total_localtax1 ? $localtaxes_type[1] : 0);
$this->line->localtax2_tx = ($total_localtax2 ? $localtaxes_type[3] : 0);
2020-12-13 12:34:21 +00:00
$this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
$this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
$this->line->fk_product = $fk_product;
$this->line->product_type = $product_type;
$this->line->fk_remise_except = $fk_remise_except;
$this->line->remise_percent = $remise_percent;
$this->line->subprice = (float) $pu_ht;
$this->line->rang = $ranktouse;
$this->line->info_bits = $info_bits;
$this->line->total_ht = (float) $total_ht;
$this->line->total_tva = (float) $total_tva;
$this->line->total_localtax1 = (float) $total_localtax1;
$this->line->total_localtax2 = (float) $total_localtax2;
$this->line->total_ttc = (float) $total_ttc;
$this->line->special_code = $special_code;
if (empty($qty) && empty($special_code)) {
$this->line->special_code = 3;
}
$this->line->origin = $origin;
$this->line->origin_type = $origin;
$this->line->origin_id = $origin_id;
$this->line->fk_parent_line = $fk_parent_line;
$this->line->fk_unit = $fk_unit;
$this->line->date_start = $date_start;
$this->line->date_end = $date_end;
2018-08-30 07:40:04 +00:00
$this->line->fk_fournprice = $fk_fournprice;
$this->line->pa_ht = $pa_ht; // Can be '' when not defined or 0 if defined to 0 or a price value
2018-08-30 07:40:04 +00:00
// Multicurrency
$this->line->fk_multicurrency = $this->fk_multicurrency;
$this->line->multicurrency_code = $this->multicurrency_code;
$this->line->multicurrency_subprice = (float) $pu_ht_devise;
$this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
$this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
$this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
2018-08-30 07:40:04 +00:00
if (is_array($array_options) && count($array_options) > 0) {
$this->line->array_options = $array_options;
2018-08-30 07:40:04 +00:00
}
$result = $this->line->insert($user);
2021-02-23 19:46:19 +00:00
if ($result > 0) {
2025-02-10 17:09:32 +00:00
// Update denormalized fields at the order level
if (empty($noupdateafterinsertline)) {
$result = $this->update_price(1, 'auto', 0, $mysoc); // This method is designed to add line from user input so total calculation must be done using 'auto' mode.
}
2021-02-23 19:46:19 +00:00
if ($result > 0) {
2025-02-10 17:09:32 +00:00
if (!isset($this->context['createfromclone'])) {
if (!empty($fk_parent_line)) {
// Always reorder if child line
$this->line_order(true, 'DESC');
} elseif ($ranktouse > 0 && $ranktouse <= count($this->lines)) {
// Update all rank of all other lines starting from the same $ranktouse
foreach ($this->lines as $tmpline) {
if ($tmpline->rang >= $ranktouse) {
if (!empty($tmpline->id)) {
$this->updateRangOfLine($tmpline->id, $tmpline->rang + 1);
}
}
2025-02-10 17:09:32 +00:00
}
}
$this->lines[] = $this->line;
NEW Subtotal module (#33502) * Changes to follow mvc logic * Reworked admin page and form to add lines * Reworked adding line logic * Adding options when editing subtotal lines * Fix translations * Fixed errors/displays and started pdf * Color for subtotals pdf lines * FIX display of subtotal totht * Added pdf azur for propal * Fix duplicate translation * Added subtotal support for facture pdf * Added subtotal support for commande pdf * Improve UI/translations * Restored old pdf * Info to warn user for unsupported pdf * Added title lines VAT rate and discount support This is meant for future feature wich is block mass changing vat rate and discount percentage * Working on block apply vat and discount * Added buttons for block actions * Handle editing vat and discount for subtotal lines * Editing vat and discount for subtotal lines bloc working * Added possibility to move by block * Updated adding and updating a subtotal line * Improved vat and discount block update * Improvement for block vat/discount and line edition Editing a title line edits the corresponding subtotal line * Improvement for moving by block * Bad tile or st line placement managing * Improved bad title or st line placement managing * Adding subtotal line improved Adding a subtotal line adds it right under its corresponding title * Improved deleting subtotal line Added possibility to choose if you want to delete the corresponding subtotal line when deleting a title line * Preventing too high level titles to be created * Create and update line errors managing * Improved bad title or st line placement managing * Improved st line creation * improve headers * fix bad block placement managing * fix adding st line not working if duplicates titles * fix translations * Fix block update * FIX special chars bug If special char like " ' " was used in title it could be converted to special char entity. * Prepare for pdf options Pdf options like page break befor title should be shown as a picto if activated on a subtotal line on a document * Added option managing * Improved subtotal options and PDF integration * Code refactor * Action name/Error managment/PDF refreshing When adding or updating a subtotal line * Reformat * Reworked subtotal options database managment * Changed access to special code * Remove unecessary call to php trait * Changed definition of subtotals special code constant * Reworked align on PDF * Removed unecessary function and improved error managment * Typo fix and removed treated todos * Post typo changed for to better match subtotals names * Disabled edit if status is not draft * Changed way subtotals options are stored * Added view managing when creating a document form an other * Improved creating document from an other Can check subtotals lines with table head checkbox and removed highlight class for better UI. * Make include of subtotals tpl more clear * Manage centered or justified case If user chose to center or justify, we don't change nothing * Improved pdf * Removed unecessary code block * optimisation * Code sniffer fix * Code sniffer fix * Code sniffer fix * Code sniffer fix * Code sniffer fix and added missing translation * Fix php code sniffer * Reload page when setup saved on subtotals admin page * Fix php code sniffer * pjan fix * phan fix * phan fix * phan fix * phan fix * phan fix * phan fix * phan fix * phan fix * phan fix * phan fix * phan fix * php warning fix * php warning fix * php phan fix * php phan fix * php phan fix * Fix bug admin page not loading because of const not defined * php phan fix * php phan fix * FIX subtotals admin page display * php phan fix * FIX php phan * Fix bugs and langs * Fix bug pdf align * Replace include by require * Add headers to avoid refreshing and adding unwanted lines * Fix phan * Add GETPOST check for security * Fix phan * Fix phan * Fix phan * Subtotal option when creating a document from another * Fix phan * Add field subtotal options for subtotal lines * Fix phan * Fix phan * Fix phan * Update to follow mvc * Fix php phan * Fix php phan * Fix phpstan/phan * Fix phpstan * Fix phpstan * Fix phpstan * Fix phpstan * Fix phpstan * Update to switch to extraparams * Update to switch to extraparams * Retrieve extraparams from db to objectline * Modified last things to switch to extraparams * Cleaning unnecessary code lines * Fix php-stan * Section subtotal in extraparams to differentiate if needed for further devs * Fix phan * Keep extraparams when creating from another object * Change default value to false when creating a subtotal line * Fix clone would not keep extraparams in new object * Fix dark subtotal line background color Fix when a subtotal background color is too dark and edit pencil or delete trash could not be seen * Fix typo * Fix typo * Added subtotals for facturerec * Fix precommit * Added extraparams when creating rec from fac and other way * Fix phan * Fix objectline null * Desactivating block vat / discount update for facturerec * reformating code * Added expeditions for subtotal * Save extraparams for shipping lines * Display of subtotals lines in shipments * Display when creating facture from shipments * Improve display of lines and invoice creation from shipments * Fix error if missing line rang * Deleted duplicate * Added deletion of subtotal line in shipping documents * Not including subtotal lines if there is no product line in between * Update get subtotal lines in shipment docs to disable * Delete possibility to edit subtotal lines in shipments * Handle pdf for shipment * Handle conf stock or shipment supporting services * Fix precommit * Fix duplicate name creating bug * Fix bug where id could be changed by the line id and would create bug * Deleted subtotal lines when STOCK_SUPPORTS_SERVICES is enabled and block would only have service lines * Disable shipments in admin modules Shimpements subtotals lines are only created from commands * Fixing phan and stan * Deleted unused template and phan fix * Fix phan * Fix phan * Fix phan * FIX: phan * Fix template bug If document was not in subtotal scope and would use a template used by subtotal it would create an error. * Fix php-stan * Fix bad display when modules and confs were activated * Fix php codesniffer * Excluding subtotal lines when mass updating * Fix shipments service lines exluded + showing subtotal line with specific configs * Fix line display with situation invoices + bug block line update * Added ODT managment This works with invoices but has not been tested with other documents * Fix bug when editing VAT/discount by block * Fix error raised when subtotal line added Subtotal line had no fk_product and raised the error but we want this line to have no fk product * Fix precommit * Added ODT template for documents that uses subtotals This template can be usefull to understand how to create an odt template using subtotals module * Fix phan * Fix phan * fix: buttons showing in bad document status * clean: unwanted commited files * add: table examples to use with subtotals on ODT templates * fix: Unwanted print of value when creating a document from another * feat: renaming for better understanding * feat: Improved templates for documents related to subtotals --------- Co-authored-by: Marc de Lima Lucio <68746600+marc-dll@users.noreply.github.com> Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2025-05-10 00:44:31 +00:00
} else {
// Loop on all lines of parent object
foreach ($this->lines as $tmpline) {
if ($tmpline->id == $origin_id && $tmpline->element = $origin) {
$this->line->extraparams = $tmpline->extraparams;
NEW Subtotal module (#33502) * Changes to follow mvc logic * Reworked admin page and form to add lines * Reworked adding line logic * Adding options when editing subtotal lines * Fix translations * Fixed errors/displays and started pdf * Color for subtotals pdf lines * FIX display of subtotal totht * Added pdf azur for propal * Fix duplicate translation * Added subtotal support for facture pdf * Added subtotal support for commande pdf * Improve UI/translations * Restored old pdf * Info to warn user for unsupported pdf * Added title lines VAT rate and discount support This is meant for future feature wich is block mass changing vat rate and discount percentage * Working on block apply vat and discount * Added buttons for block actions * Handle editing vat and discount for subtotal lines * Editing vat and discount for subtotal lines bloc working * Added possibility to move by block * Updated adding and updating a subtotal line * Improved vat and discount block update * Improvement for block vat/discount and line edition Editing a title line edits the corresponding subtotal line * Improvement for moving by block * Bad tile or st line placement managing * Improved bad title or st line placement managing * Adding subtotal line improved Adding a subtotal line adds it right under its corresponding title * Improved deleting subtotal line Added possibility to choose if you want to delete the corresponding subtotal line when deleting a title line * Preventing too high level titles to be created * Create and update line errors managing * Improved bad title or st line placement managing * Improved st line creation * improve headers * fix bad block placement managing * fix adding st line not working if duplicates titles * fix translations * Fix block update * FIX special chars bug If special char like " ' " was used in title it could be converted to special char entity. * Prepare for pdf options Pdf options like page break befor title should be shown as a picto if activated on a subtotal line on a document * Added option managing * Improved subtotal options and PDF integration * Code refactor * Action name/Error managment/PDF refreshing When adding or updating a subtotal line * Reformat * Reworked subtotal options database managment * Changed access to special code * Remove unecessary call to php trait * Changed definition of subtotals special code constant * Reworked align on PDF * Removed unecessary function and improved error managment * Typo fix and removed treated todos * Post typo changed for to better match subtotals names * Disabled edit if status is not draft * Changed way subtotals options are stored * Added view managing when creating a document form an other * Improved creating document from an other Can check subtotals lines with table head checkbox and removed highlight class for better UI. * Make include of subtotals tpl more clear * Manage centered or justified case If user chose to center or justify, we don't change nothing * Improved pdf * Removed unecessary code block * optimisation * Code sniffer fix * Code sniffer fix * Code sniffer fix * Code sniffer fix * Code sniffer fix and added missing translation * Fix php code sniffer * Reload page when setup saved on subtotals admin page * Fix php code sniffer * pjan fix * phan fix * phan fix * phan fix * phan fix * phan fix * phan fix * phan fix * phan fix * phan fix * phan fix * phan fix * php warning fix * php warning fix * php phan fix * php phan fix * php phan fix * Fix bug admin page not loading because of const not defined * php phan fix * php phan fix * FIX subtotals admin page display * php phan fix * FIX php phan * Fix bugs and langs * Fix bug pdf align * Replace include by require * Add headers to avoid refreshing and adding unwanted lines * Fix phan * Add GETPOST check for security * Fix phan * Fix phan * Fix phan * Subtotal option when creating a document from another * Fix phan * Add field subtotal options for subtotal lines * Fix phan * Fix phan * Fix phan * Update to follow mvc * Fix php phan * Fix php phan * Fix phpstan/phan * Fix phpstan * Fix phpstan * Fix phpstan * Fix phpstan * Fix phpstan * Update to switch to extraparams * Update to switch to extraparams * Retrieve extraparams from db to objectline * Modified last things to switch to extraparams * Cleaning unnecessary code lines * Fix php-stan * Section subtotal in extraparams to differentiate if needed for further devs * Fix phan * Keep extraparams when creating from another object * Change default value to false when creating a subtotal line * Fix clone would not keep extraparams in new object * Fix dark subtotal line background color Fix when a subtotal background color is too dark and edit pencil or delete trash could not be seen * Fix typo * Fix typo * Added subtotals for facturerec * Fix precommit * Added extraparams when creating rec from fac and other way * Fix phan * Fix objectline null * Desactivating block vat / discount update for facturerec * reformating code * Added expeditions for subtotal * Save extraparams for shipping lines * Display of subtotals lines in shipments * Display when creating facture from shipments * Improve display of lines and invoice creation from shipments * Fix error if missing line rang * Deleted duplicate * Added deletion of subtotal line in shipping documents * Not including subtotal lines if there is no product line in between * Update get subtotal lines in shipment docs to disable * Delete possibility to edit subtotal lines in shipments * Handle pdf for shipment * Handle conf stock or shipment supporting services * Fix precommit * Fix duplicate name creating bug * Fix bug where id could be changed by the line id and would create bug * Deleted subtotal lines when STOCK_SUPPORTS_SERVICES is enabled and block would only have service lines * Disable shipments in admin modules Shimpements subtotals lines are only created from commands * Fixing phan and stan * Deleted unused template and phan fix * Fix phan * Fix phan * Fix phan * FIX: phan * Fix template bug If document was not in subtotal scope and would use a template used by subtotal it would create an error. * Fix php-stan * Fix bad display when modules and confs were activated * Fix php codesniffer * Excluding subtotal lines when mass updating * Fix shipments service lines exluded + showing subtotal line with specific configs * Fix line display with situation invoices + bug block line update * Added ODT managment This works with invoices but has not been tested with other documents * Fix bug when editing VAT/discount by block * Fix error raised when subtotal line added Subtotal line had no fk_product and raised the error but we want this line to have no fk product * Fix precommit * Added ODT template for documents that uses subtotals This template can be usefull to understand how to create an odt template using subtotals module * Fix phan * Fix phan * fix: buttons showing in bad document status * clean: unwanted commited files * add: table examples to use with subtotals on ODT templates * fix: Unwanted print of value when creating a document from another * feat: renaming for better understanding * feat: Improved templates for documents related to subtotals --------- Co-authored-by: Marc de Lima Lucio <68746600+marc-dll@users.noreply.github.com> Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2025-05-10 00:44:31 +00:00
$this->line->setExtraParameters();
}
}
2025-02-10 17:09:32 +00:00
}
2018-08-30 07:40:04 +00:00
$this->db->commit();
2019-11-12 09:11:30 +00:00
return $this->line->id;
2020-05-21 13:05:19 +00:00
} else {
2018-08-30 07:40:04 +00:00
$this->db->rollback();
return -1;
}
2020-05-21 13:05:19 +00:00
} else {
$this->setErrorsFromObject($this->line);
2018-08-30 07:40:04 +00:00
dol_syslog(get_class($this)."::addline error=".$this->error, LOG_ERR);
$this->db->rollback();
return -2;
}
2020-05-21 13:05:19 +00:00
} else {
2018-08-30 07:40:04 +00:00
dol_syslog(get_class($this)."::addline status of order must be Draft to allow use of ->addline()", LOG_ERR);
return -3;
}
}
2019-02-24 23:56:48 +00:00
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2018-08-30 07:40:04 +00:00
/**
* Add line into array
* $this->client must be loaded
*
2019-02-24 23:56:48 +00:00
* @param int $idproduct Product Id
* @param float $qty Quantity
* @param float $remise_percent Product discount relative
* @param int|'' $date_start Start date of the line
* @param int|'' $date_end End date of the line
2019-02-24 23:56:48 +00:00
* @return void
2018-08-30 07:40:04 +00:00
*
* TODO Remplacer les appels a cette fonction par generation object Ligne
2018-08-30 07:40:04 +00:00
*/
2019-02-24 23:56:48 +00:00
public function add_product($idproduct, $qty, $remise_percent = 0.0, $date_start = '', $date_end = '')
2018-08-30 07:40:04 +00:00
{
// phpcs:enable
2018-08-30 07:40:04 +00:00
global $conf, $mysoc;
2021-02-23 19:46:19 +00:00
if (!$qty) {
$qty = 1;
}
2018-08-30 07:40:04 +00:00
2021-02-23 19:46:19 +00:00
if ($idproduct > 0) {
$prod = new Product($this->db);
2018-08-30 07:40:04 +00:00
$prod->fetch($idproduct);
$tva_tx = get_default_tva($mysoc, $this->thirdparty, $prod->id);
$tva_npr = get_default_npr($mysoc, $this->thirdparty, $prod->id);
2021-02-23 19:46:19 +00:00
if (empty($tva_tx)) {
$tva_npr = 0;
}
$vat_src_code = ''; // May be defined into tva_tx
2018-08-30 07:40:04 +00:00
$localtax1_tx = get_localtax($tva_tx, 1, $this->thirdparty, $mysoc, $tva_npr);
$localtax2_tx = get_localtax($tva_tx, 2, $this->thirdparty, $mysoc, $tva_npr);
2018-08-30 07:40:04 +00:00
// multiprix
2025-01-23 01:42:06 +00:00
if (getDolGlobalString('PRODUIT_MULTIPRICES') && $this->thirdparty->price_level) {
2018-08-30 08:30:52 +00:00
$price = $prod->multiprices[$this->thirdparty->price_level];
$price_ttc = $prod->multiprices_ttc[$this->thirdparty->price_level];
} else {
$price = $prod->price;
$price_ttc = $prod->price_ttc;
}
$line = new OrderLine($this->db);
$line->context = $this->context;
$line->fk_product = $idproduct;
$line->desc = $prod->description;
$line->qty = $qty;
$line->subprice = $price;
$line->subprice_ttc = $price_ttc;
$line->remise_percent = $remise_percent;
$line->vat_src_code = $vat_src_code;
$line->tva_tx = $tva_tx;
$line->localtax1_tx = $localtax1_tx;
$line->localtax2_tx = $localtax2_tx;
2023-08-05 23:14:36 +00:00
$line->product_ref = $prod->ref;
$line->product_label = $prod->label;
$line->product_desc = $prod->description;
$line->fk_unit = $prod->fk_unit;
// Save the start and end date of the line in the object
2021-02-23 19:46:19 +00:00
if ($date_start) {
$line->date_start = $date_start;
}
if ($date_end) {
$line->date_end = $date_end;
}
$this->lines[] = $line;
/** POUR AJOUTER AUTOMATIQUEMENT LES SOUSPRODUITS a LA COMMANDE
* if (getDolGlobalString('PRODUIT_SOUSPRODUITS')) {
* $prod = new Product($this->db);
* $prod->fetch($idproduct);
* $prod -> get_sousproduits_arbo();
* $prods_arbo = $prod->get_arbo_each_prod();
* if(count($prods_arbo) > 0)
* {
* foreach($prods_arbo as $key => $value)
* {
* // print "id : ".$value[1].' :qty: '.$value[0].'<br>';
* if not in lines {
* $this->add_product($value[1], $value[0]);
* }
* }
* }
**/
2018-08-30 07:40:04 +00:00
}
}
/**
* Get object from database. Get also lines.
2018-08-30 07:40:04 +00:00
*
* @param int $id Id of object to load
* @param string $ref Ref of object
* @param string $ref_ext External reference of object
2025-03-16 19:04:52 +00:00
* @param string $notused Not used
2018-08-30 07:40:04 +00:00
* @return int >0 if OK, <0 if KO, 0 if not found
*/
public function fetch($id, $ref = '', $ref_ext = '', $notused = '')
2018-08-30 07:40:04 +00:00
{
// Check parameters
2021-02-23 19:46:19 +00:00
if (empty($id) && empty($ref) && empty($ref_ext)) {
return -1;
}
2018-08-30 07:40:04 +00:00
2025-02-11 15:28:37 +00:00
$sql = 'SELECT c.rowid, c.entity, c.date_creation, c.ref, c.fk_soc, c.fk_user_author, c.fk_user_valid, c.fk_user_modif, c.fk_statut as status';
$sql .= ', c.amount_ht, c.total_ht, c.total_ttc, c.total_tva, c.localtax1 as total_localtax1, c.localtax2 as total_localtax2, c.fk_cond_reglement, c.deposit_percent, c.fk_mode_reglement, c.fk_availability, c.fk_input_reason';
$sql .= ', c.fk_account';
$sql .= ', c.date_commande, c.date_valid, c.tms';
2020-11-16 11:20:18 +00:00
$sql .= ', c.date_livraison as delivery_date';
$sql .= ', c.fk_shipping_method';
$sql .= ', c.fk_warehouse';
$sql .= ', c.fk_projet as fk_project, c.source, c.facture as billed';
2022-09-23 07:35:17 +00:00
$sql .= ', c.note_private, c.note_public, c.ref_client, c.ref_ext, c.model_pdf, c.last_main_doc, c.fk_delivery_address, c.extraparams';
$sql .= ', c.fk_incoterms, c.location_incoterms';
$sql .= ", c.fk_multicurrency, c.multicurrency_code, c.multicurrency_tx, c.multicurrency_total_ht, c.multicurrency_total_tva, c.multicurrency_total_ttc";
$sql .= ", c.module_source, c.pos_source";
$sql .= ", i.libelle as label_incoterms";
$sql .= ', p.code as mode_reglement_code, p.libelle as mode_reglement_libelle';
$sql .= ', cr.code as cond_reglement_code, cr.libelle as cond_reglement_libelle, cr.libelle_facture as cond_reglement_libelle_doc';
$sql .= ', ca.code as availability_code, ca.label as availability_label';
$sql .= ', dr.code as demand_reason_code';
$sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as c';
$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_payment_term as cr ON c.fk_cond_reglement = cr.rowid';
$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as p ON c.fk_mode_reglement = p.id';
$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_availability as ca ON c.fk_availability = ca.rowid';
$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_input_reason as dr ON c.fk_input_reason = dr.rowid';
$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_incoterms as i ON c.fk_incoterms = i.rowid';
2021-02-23 19:46:19 +00:00
if ($id) {
2025-01-19 16:38:34 +00:00
$sql .= " WHERE c.rowid = ".((int) $id);
2021-02-23 19:46:19 +00:00
} else {
$sql .= " WHERE c.entity IN (".getEntity('commande').")"; // Don't use entity if you use rowid
2021-02-23 19:46:19 +00:00
}
2021-02-23 19:46:19 +00:00
if ($ref) {
2025-01-19 16:38:34 +00:00
$sql .= " AND c.ref = '".$this->db->escape($ref)."'";
2021-02-23 19:46:19 +00:00
}
if ($ref_ext) {
2025-01-19 16:38:34 +00:00
$sql .= " AND c.ref_ext = '".$this->db->escape($ref_ext)."'";
2021-02-23 19:46:19 +00:00
}
2018-08-30 07:40:04 +00:00
dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
$result = $this->db->query($sql);
2021-02-23 19:46:19 +00:00
if ($result) {
2018-08-30 07:40:04 +00:00
$obj = $this->db->fetch_object($result);
2021-02-23 19:46:19 +00:00
if ($obj) {
$this->id = $obj->rowid;
$this->entity = $obj->entity;
2018-08-30 07:40:04 +00:00
$this->ref = $obj->ref;
$this->ref_client = $obj->ref_client;
$this->ref_customer = $obj->ref_client;
2023-04-26 09:41:02 +00:00
$this->ref_ext = $obj->ref_ext;
$this->socid = $obj->fk_soc;
$this->thirdparty = null; // Clear if another value was already set by fetch_thirdparty
$this->fk_project = $obj->fk_project;
$this->project = null; // Clear if another value was already set by fetch_projet
$this->statut = $obj->status; // deprecated
2025-02-11 15:28:37 +00:00
$this->status = $obj->status;
$this->user_author_id = $obj->fk_user_author;
2022-06-28 11:09:53 +00:00
$this->user_creation_id = $obj->fk_user_author;
$this->user_validation_id = $obj->fk_user_valid;
$this->user_modification_id = $obj->fk_user_modif;
$this->total_ht = $obj->total_ht;
$this->total_tva = $obj->total_tva;
$this->total_localtax1 = $obj->total_localtax1;
$this->total_localtax2 = $obj->total_localtax2;
$this->total_ttc = $obj->total_ttc;
$this->date = $this->db->jdate($obj->date_commande);
2018-08-30 07:40:04 +00:00
$this->date_commande = $this->db->jdate($obj->date_commande);
2018-10-16 22:06:24 +00:00
$this->date_creation = $this->db->jdate($obj->date_creation);
$this->date_validation = $this->db->jdate($obj->date_valid);
$this->date_modification = $this->db->jdate($obj->tms);
2018-08-30 07:40:04 +00:00
$this->source = $obj->source;
$this->billed = $obj->billed;
$this->note = $obj->note_private; // deprecated
$this->note_private = $obj->note_private;
$this->note_public = $obj->note_public;
2020-08-18 12:48:38 +00:00
$this->model_pdf = $obj->model_pdf;
$this->last_main_doc = $obj->last_main_doc;
2018-08-30 07:40:04 +00:00
$this->mode_reglement_id = $obj->fk_mode_reglement;
$this->mode_reglement_code = $obj->mode_reglement_code;
$this->mode_reglement = $obj->mode_reglement_libelle;
$this->cond_reglement_id = $obj->fk_cond_reglement;
$this->cond_reglement_code = $obj->cond_reglement_code;
$this->cond_reglement = $obj->cond_reglement_libelle;
$this->cond_reglement_doc = $obj->cond_reglement_libelle_doc;
$this->deposit_percent = $obj->deposit_percent;
$this->fk_account = $obj->fk_account;
$this->availability_id = $obj->fk_availability;
2018-08-30 07:40:04 +00:00
$this->availability_code = $obj->availability_code;
$this->availability = $obj->availability_label;
$this->demand_reason_id = $obj->fk_input_reason;
$this->demand_reason_code = $obj->demand_reason_code;
2020-11-16 11:20:18 +00:00
$this->delivery_date = $this->db->jdate($obj->delivery_date);
$this->shipping_method_id = ($obj->fk_shipping_method > 0) ? $obj->fk_shipping_method : null;
$this->warehouse_id = ($obj->fk_warehouse > 0) ? $obj->fk_warehouse : null;
$this->fk_delivery_address = $obj->fk_delivery_address;
2019-02-20 11:29:47 +00:00
$this->module_source = $obj->module_source;
$this->pos_source = $obj->pos_source;
2019-02-24 23:56:48 +00:00
2018-08-30 07:40:04 +00:00
//Incoterms
2019-02-20 11:29:47 +00:00
$this->fk_incoterms = $obj->fk_incoterms;
$this->location_incoterms = $obj->location_incoterms;
$this->label_incoterms = $obj->label_incoterms;
2018-08-30 07:40:04 +00:00
// Multicurrency
$this->fk_multicurrency = $obj->fk_multicurrency;
$this->multicurrency_code = $obj->multicurrency_code;
2018-08-30 07:40:04 +00:00
$this->multicurrency_tx = $obj->multicurrency_tx;
$this->multicurrency_total_ht = $obj->multicurrency_total_ht;
2018-08-30 07:40:04 +00:00
$this->multicurrency_total_tva = $obj->multicurrency_total_tva;
$this->multicurrency_total_ttc = $obj->multicurrency_total_ttc;
2023-06-10 08:47:36 +00:00
$this->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
2018-08-30 07:40:04 +00:00
$this->lines = array();
2018-08-30 07:40:04 +00:00
2020-10-23 18:08:35 +00:00
// Retrieve all extrafield
2018-08-30 07:40:04 +00:00
// fetch optionals attributes and labels
$this->fetch_optionals();
$this->db->free($result);
// Lines
$result = $this->fetch_lines();
2021-02-23 19:46:19 +00:00
if ($result < 0) {
2018-08-30 07:40:04 +00:00
return -3;
}
return 1;
2020-05-21 13:05:19 +00:00
} else {
$this->error = 'Order with id '.$id.' not found sql='.$sql;
2018-08-30 07:40:04 +00:00
return 0;
}
2020-05-21 13:05:19 +00:00
} else {
$this->error = $this->db->error();
2018-08-30 07:40:04 +00:00
return -1;
}
}
2019-02-28 22:19:58 +00:00
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2018-08-30 07:40:04 +00:00
/**
2024-07-25 11:59:12 +00:00
* Add a discount line into a sale order (as a sale order line) using an existing absolute discount (Consume the discount)
2018-08-30 07:40:04 +00:00
*
2024-07-25 11:59:12 +00:00
* @param int $idremise Id for the fixed discount from table llx_societe_remise_except
* @return int >0 if OK, <0 if KO
2018-08-30 07:40:04 +00:00
*/
2019-02-24 23:56:48 +00:00
public function insert_discount($idremise)
2018-08-30 07:40:04 +00:00
{
// phpcs:enable
2018-08-30 07:40:04 +00:00
global $langs;
include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
include_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
$this->db->begin();
$remise = new DiscountAbsolute($this->db);
$result = $remise->fetch($idremise);
2018-08-30 07:40:04 +00:00
2021-02-23 19:46:19 +00:00
if ($result > 0) {
if ($remise->fk_facture) { // Protection against multiple submissions
$this->error = $langs->trans("ErrorDiscountAlreadyUsed");
2018-08-30 07:40:04 +00:00
$this->db->rollback();
return -5;
}
$line = new OrderLine($this->db);
$line->fk_commande = $this->id;
$line->fk_remise_except = $remise->id;
$line->desc = $remise->description; // Description for the order line
$line->vat_src_code = $remise->vat_src_code;
$line->tva_tx = $remise->tva_tx;
$line->localtax1_tx = $remise->localtax1_tx;
$line->localtax1_type = $remise->localtax1_type;
$line->localtax2_tx = $remise->localtax1_tx;
$line->localtax2_type = $remise->localtax1_type;
$line->subprice = -(float) $remise->amount_ht;
$line->price = -(float) $remise->amount_ht;
$line->fk_product = 0; // Predefined Product ID
$line->qty = 1;
$line->remise_percent = 0;
$line->rang = -1;
$line->info_bits = 2;
2018-08-30 07:40:04 +00:00
$line->total_ht = -(float) $remise->amount_ht;
$line->total_tva = -(float) $remise->amount_tva;
$line->total_ttc = -(float) $remise->amount_ttc;
$line->total_localtax1 = -(float) $remise->total_localtax1;
$line->total_localtax2 = -(float) $remise->total_localtax2;
2018-08-30 07:40:04 +00:00
$result = $line->insert();
2021-02-23 19:46:19 +00:00
if ($result > 0) {
$result = $this->update_price(1);
2021-02-23 19:46:19 +00:00
if ($result > 0) {
2018-08-30 07:40:04 +00:00
$this->db->commit();
return 1;
2020-05-21 13:05:19 +00:00
} else {
2018-08-30 07:40:04 +00:00
$this->db->rollback();
return -1;
}
2020-05-21 13:05:19 +00:00
} else {
$this->setErrorsFromObject($line);
2018-08-30 07:40:04 +00:00
$this->db->rollback();
return -2;
}
2020-05-21 13:05:19 +00:00
} else {
2018-08-30 07:40:04 +00:00
$this->db->rollback();
return -2;
}
}
2019-02-24 23:56:48 +00:00
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2018-08-30 07:40:04 +00:00
/**
* Load array lines
*
2022-01-24 13:09:53 +00:00
* @param int $only_product Return only physical products, not services
2019-06-25 16:12:16 +00:00
* @param int $loadalsotranslation Return translation for products
2023-12-01 18:51:32 +00:00
* @return int Return integer <0 if KO, >0 if OK
2018-08-30 07:40:04 +00:00
*/
2019-06-25 13:41:20 +00:00
public function fetch_lines($only_product = 0, $loadalsotranslation = 0)
2018-08-30 07:40:04 +00:00
{
// phpcs:enable
$this->lines = array();
2018-08-30 07:40:04 +00:00
2020-09-04 19:11:58 +00:00
$sql = 'SELECT l.rowid, l.fk_product, l.fk_parent_line, l.product_type, l.fk_commande, l.label as custom_label, l.description, l.price, l.qty, l.vat_src_code, l.tva_tx, l.ref_ext,';
$sql .= ' l.localtax1_tx, l.localtax2_tx, l.localtax1_type, l.localtax2_type, l.fk_remise_except, l.remise_percent, l.subprice, l.subprice_ttc, l.fk_product_fournisseur_price as fk_fournprice, l.buy_price_ht as pa_ht, l.rang, l.info_bits, l.special_code,';
$sql .= ' l.total_ht, l.total_ttc, l.total_tva, l.total_localtax1, l.total_localtax2, l.date_start, l.date_end,';
$sql .= ' l.fk_unit, l.extraparams,';
$sql .= ' l.fk_multicurrency, l.multicurrency_code, l.multicurrency_subprice, l.multicurrency_subprice_ttc, l.multicurrency_total_ht, l.multicurrency_total_tva, l.multicurrency_total_ttc,';
$sql .= ' p.ref as product_ref, p.description as product_desc, p.fk_product_type, p.label as product_label, p.tosell as product_tosell, p.tobuy as product_tobuy, p.tobatch as product_tobatch, p.barcode as product_barcode,';
$sql .= ' p.customcode, p.fk_country as country_id, c.code as country_code,';
2025-01-29 18:15:57 +00:00
$sql .= ' p.weight, p.weight_units, p.volume, p.volume_units, p.packaging';
$sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element_line.' as l';
$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'product as p ON (p.rowid = l.fk_product)';
$sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_country as c ON c.rowid = p.fk_country';
2021-08-27 21:36:06 +00:00
$sql .= ' WHERE l.fk_commande = '.((int) $this->id);
2021-02-23 19:46:19 +00:00
if ($only_product) {
$sql .= ' AND p.fk_product_type = 0';
}
2018-08-30 07:40:04 +00:00
$sql .= ' ORDER BY l.rang, l.rowid';
dol_syslog(get_class($this)."::fetch_lines", LOG_DEBUG);
$result = $this->db->query($sql);
2021-02-23 19:46:19 +00:00
if ($result) {
2018-08-30 07:40:04 +00:00
$num = $this->db->num_rows($result);
2015-10-15 10:17:57 +00:00
2018-08-30 07:40:04 +00:00
$i = 0;
2021-02-23 19:46:19 +00:00
while ($i < $num) {
2018-08-30 07:40:04 +00:00
$objp = $this->db->fetch_object($result);
$line = new OrderLine($this->db);
$line->rowid = $objp->rowid;
$line->id = $objp->rowid;
$line->fk_commande = $objp->fk_commande;
$line->commande_id = $objp->fk_commande;
$line->label = $objp->custom_label;
$line->desc = $objp->description;
$line->description = $objp->description; // Description line
2018-08-30 07:40:04 +00:00
$line->product_type = $objp->product_type;
$line->qty = $objp->qty;
2020-09-04 19:11:58 +00:00
$line->ref_ext = $objp->ref_ext;
2018-08-30 07:40:04 +00:00
$line->vat_src_code = $objp->vat_src_code;
$line->tva_tx = $objp->tva_tx;
$line->localtax1_tx = $objp->localtax1_tx;
$line->localtax2_tx = $objp->localtax2_tx;
$line->localtax1_type = $objp->localtax1_type;
$line->localtax2_type = $objp->localtax2_type;
$line->total_ht = $objp->total_ht;
$line->total_ttc = $objp->total_ttc;
$line->total_tva = $objp->total_tva;
$line->total_localtax1 = $objp->total_localtax1;
$line->total_localtax2 = $objp->total_localtax2;
2026-05-22 23:46:54 +00:00
$line->subprice = (float) $objp->subprice;
$line->subprice_ttc = (float) $objp->subprice_ttc;
2018-08-30 07:40:04 +00:00
$line->fk_remise_except = $objp->fk_remise_except;
$line->remise_percent = $objp->remise_percent;
$line->price = $objp->price;
$line->fk_product = $objp->fk_product;
$line->fk_fournprice = $objp->fk_fournprice;
$marginInfos = getMarginInfos($objp->subprice, $objp->remise_percent, $objp->tva_tx, $objp->localtax1_tx, $objp->localtax2_tx, $line->fk_fournprice, $objp->pa_ht);
$line->pa_ht = $marginInfos[0];
2018-08-30 07:40:04 +00:00
$line->marge_tx = $marginInfos[1];
2018-08-30 08:30:52 +00:00
$line->marque_tx = $marginInfos[2];
2018-08-30 07:40:04 +00:00
$line->rang = $objp->rang;
$line->info_bits = $objp->info_bits;
$line->special_code = $objp->special_code;
$line->fk_parent_line = $objp->fk_parent_line;
2018-08-30 07:40:04 +00:00
$line->ref = $objp->product_ref;
$line->libelle = $objp->product_label;
$line->product_ref = $objp->product_ref;
$line->product_label = $objp->product_label;
$line->product_tosell = $objp->product_tosell;
$line->product_tobuy = $objp->product_tobuy;
2018-08-30 07:40:04 +00:00
$line->product_desc = $objp->product_desc;
$line->product_tobatch = $objp->product_tobatch;
$line->product_barcode = $objp->product_barcode;
$line->product_custom_code = $objp->customcode;
$line->product_custom_country_id = $objp->country_id;
$line->product_custom_country_code = $objp->country_code;
$line->fk_product_type = $objp->fk_product_type; // Product or service
2018-08-30 07:40:04 +00:00
$line->fk_unit = $objp->fk_unit;
$line->extraparams = !empty($objp->extraparams) ? (array) json_decode($objp->extraparams, true) : array();
2018-08-30 07:40:04 +00:00
$line->weight = $objp->weight;
$line->weight_units = $objp->weight_units;
$line->volume = $objp->volume;
$line->volume_units = $objp->volume_units;
2025-01-29 18:15:57 +00:00
$line->packaging = $objp->packaging;
2018-08-30 07:40:04 +00:00
$line->date_start = $this->db->jdate($objp->date_start);
$line->date_end = $this->db->jdate($objp->date_end);
2015-10-15 10:17:57 +00:00
2018-08-30 07:40:04 +00:00
// Multicurrency
$line->fk_multicurrency = $objp->fk_multicurrency;
$line->multicurrency_code = $objp->multicurrency_code;
2018-08-30 07:40:04 +00:00
$line->multicurrency_subprice = $objp->multicurrency_subprice;
$line->multicurrency_subprice_ttc = $objp->multicurrency_subprice_ttc;
2018-08-30 07:40:04 +00:00
$line->multicurrency_total_ht = $objp->multicurrency_total_ht;
$line->multicurrency_total_tva = $objp->multicurrency_total_tva;
$line->multicurrency_total_ttc = $objp->multicurrency_total_ttc;
$line->fetch_optionals();
2019-06-25 13:41:20 +00:00
// multilangs
2022-09-24 12:37:57 +00:00
if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->fk_product) && !empty($loadalsotranslation)) {
2022-05-18 21:52:43 +00:00
$tmpproduct = new Product($this->db);
$tmpproduct->fetch($objp->fk_product);
$tmpproduct->getMultiLangs();
$line->multilangs = $tmpproduct->multilangs;
}
$this->lines[$i] = $line;
2018-08-30 07:40:04 +00:00
$i++;
}
$this->db->free($result);
return 1;
2020-05-21 13:05:19 +00:00
} else {
$this->error = $this->db->error();
2018-08-30 07:40:04 +00:00
return -3;
}
}
/**
* Return number of line with type product.
*
2023-12-06 14:46:39 +00:00
* @return int Return integer <0 if KO, Nbr of product lines if OK
2018-08-30 07:40:04 +00:00
*/
2019-02-24 23:56:48 +00:00
public function getNbOfProductsLines()
2018-08-30 07:40:04 +00:00
{
$nb = 0;
2021-02-23 19:46:19 +00:00
foreach ($this->lines as $line) {
if ($line->product_type == 0) {
$nb++;
}
2018-08-30 07:40:04 +00:00
}
return $nb;
}
/**
* Return number of line with type service.
*
2023-12-06 14:46:39 +00:00
* @return int Return integer <0 if KO, Nbr of service lines if OK
2018-08-30 07:40:04 +00:00
*/
2019-02-24 23:56:48 +00:00
public function getNbOfServicesLines()
2018-08-30 07:40:04 +00:00
{
$nb = 0;
2021-02-23 19:46:19 +00:00
foreach ($this->lines as $line) {
if ($line->product_type == 1) {
$nb++;
}
2018-08-30 07:40:04 +00:00
}
return $nb;
}
/**
2019-02-24 23:56:48 +00:00
* Count number of shipments for this order
2018-08-30 07:40:04 +00:00
*
2023-12-06 14:46:39 +00:00
* @return int Return integer <0 if KO, Nb of shipment found if OK
2018-08-30 07:40:04 +00:00
*/
2019-02-24 23:56:48 +00:00
public function getNbOfShipments()
2018-08-30 07:40:04 +00:00
{
$nb = 0;
$sql = 'SELECT COUNT(DISTINCT ed.fk_expedition) as nb';
$sql .= ' FROM '.MAIN_DB_PREFIX.'expeditiondet as ed,';
$sql .= ' '.MAIN_DB_PREFIX.$this->table_element_line.' as cd';
$sql .= ' WHERE';
$sql .= ' ed.fk_elementdet = cd.rowid';
2021-08-27 21:36:06 +00:00
$sql .= ' AND cd.fk_commande = '.((int) $this->id);
2018-08-30 07:40:04 +00:00
//print $sql;
dol_syslog(get_class($this)."::getNbOfShipments", LOG_DEBUG);
$resql = $this->db->query($sql);
2021-02-23 19:46:19 +00:00
if ($resql) {
2018-08-30 08:30:52 +00:00
$obj = $this->db->fetch_object($resql);
2021-02-23 19:46:19 +00:00
if ($obj) {
$nb = (int) $obj->nb;
2021-02-23 19:46:19 +00:00
}
2018-08-30 07:40:04 +00:00
2018-08-30 08:30:52 +00:00
$this->db->free($resql);
2018-08-30 07:40:04 +00:00
return $nb;
2020-05-21 13:05:19 +00:00
} else {
$this->error = $this->db->lasterror();
2018-08-30 07:40:04 +00:00
return -1;
}
}
/**
* Load array this->expeditions of lines of shipments with nb of products sent for each order line
* Note: For a dedicated shipment, the fetch_lines can be used to load the qty_asked and qty_shipped. This function is use to return qty_shipped cumulated for the order
*
* @param int $filtre_statut Filter on shipment status
2022-01-24 13:09:53 +00:00
* @param int $fk_product Add a filter on a product
2023-12-06 14:46:39 +00:00
* @return int Return integer <0 if KO, Nb of lines found if OK
2018-08-30 07:40:04 +00:00
*/
2022-01-24 13:09:53 +00:00
public function loadExpeditions($filtre_statut = -1, $fk_product = 0)
2018-08-30 07:40:04 +00:00
{
$this->expeditions = array();
$sql = 'SELECT cd.rowid, cd.fk_product,';
$sql .= ' sum(ed.qty) as qty';
$sql .= ' FROM '.MAIN_DB_PREFIX.'expeditiondet as ed,';
2021-02-23 19:46:19 +00:00
if ($filtre_statut >= 0) {
$sql .= ' '.MAIN_DB_PREFIX.'expedition as e,';
}
$sql .= ' '.MAIN_DB_PREFIX.$this->table_element_line.' as cd';
$sql .= ' WHERE';
2021-02-23 19:46:19 +00:00
if ($filtre_statut >= 0) {
$sql .= ' ed.fk_expedition = e.rowid AND';
}
$sql .= ' ed.fk_elementdet = cd.rowid';
2021-08-27 21:36:06 +00:00
$sql .= ' AND cd.fk_commande = '.((int) $this->id);
if ($fk_product > 0) {
$sql .= ' AND cd.fk_product = '.((int) $fk_product);
2021-02-23 19:46:19 +00:00
}
if ($filtre_statut >= 0) {
2021-08-27 21:36:06 +00:00
$sql .= ' AND e.fk_statut >= '.((int) $filtre_statut);
2021-02-23 19:46:19 +00:00
}
$sql .= ' GROUP BY cd.rowid, cd.fk_product';
2018-08-30 07:40:04 +00:00
//print $sql;
dol_syslog(get_class($this)."::loadExpeditions", LOG_DEBUG);
$resql = $this->db->query($sql);
2021-02-23 19:46:19 +00:00
if ($resql) {
2018-08-30 07:40:04 +00:00
$num = $this->db->num_rows($resql);
$i = 0;
2021-02-23 19:46:19 +00:00
while ($i < $num) {
2018-08-30 07:40:04 +00:00
$obj = $this->db->fetch_object($resql);
$this->expeditions[$obj->rowid] = $obj->qty;
$i++;
}
$this->db->free($resql);
return $num;
2020-05-21 13:05:19 +00:00
} else {
$this->error = $this->db->lasterror();
2018-08-30 07:40:04 +00:00
return -1;
}
}
/**
2023-01-06 18:24:57 +00:00
* Returns an array with expeditions lines number
2018-08-30 07:40:04 +00:00
*
* @return int Nb of shipments
*/
2023-01-06 18:24:57 +00:00
public function countNbOfShipments()
2018-08-30 07:40:04 +00:00
{
$sql = 'SELECT count(*)';
$sql .= ' FROM '.MAIN_DB_PREFIX.'expedition as e';
$sql .= ', '.MAIN_DB_PREFIX.'element_element as el';
2021-08-27 21:36:06 +00:00
$sql .= ' WHERE el.fk_source = '.((int) $this->id);
2021-03-16 13:58:51 +00:00
$sql .= " AND el.sourcetype = 'commande'";
$sql .= " AND el.fk_target = e.rowid";
$sql .= " AND el.targettype = 'shipping'";
2018-08-30 07:40:04 +00:00
$resql = $this->db->query($sql);
2021-02-23 19:46:19 +00:00
if ($resql) {
2018-08-30 07:40:04 +00:00
$row = $this->db->fetch_row($resql);
return $row[0];
2021-02-23 19:46:19 +00:00
} else {
dol_print_error($this->db);
2018-08-30 07:40:04 +00:00
}
2023-01-06 18:24:57 +00:00
return 0;
2018-08-30 07:40:04 +00:00
}
/**
* Delete an order line
*
* @param User $user User object
* @param int $lineid Id of line to delete
2023-01-04 10:36:46 +00:00
* @param int $id Id of object (for a check)
2018-08-30 07:40:04 +00:00
* @return int >0 if OK, 0 if nothing to do, <0 if KO
*/
public function deleteLine($user = null, $lineid = 0, $id = 0)
2018-08-30 07:40:04 +00:00
{
if ($this->status == self::STATUS_DRAFT) {
2018-08-30 07:40:04 +00:00
$this->db->begin();
2023-01-04 10:36:46 +00:00
// Delete line
$line = new OrderLine($this->db);
2018-08-30 07:40:04 +00:00
2023-01-04 10:36:46 +00:00
$line->context = $this->context;
2018-08-30 07:40:04 +00:00
2023-01-04 10:36:46 +00:00
// Load data
$line->fetch($lineid);
2018-08-30 07:40:04 +00:00
2023-01-04 10:36:46 +00:00
if ($id > 0 && $line->fk_commande != $id) {
$this->error = 'ErrorLineIDDoesNotMatchWithObjectID';
return -1;
}
2018-08-30 07:40:04 +00:00
2023-01-04 10:36:46 +00:00
// Memorize previous line for triggers
$staticline = clone $line;
$line->oldline = $staticline;
2023-01-04 10:36:46 +00:00
if ($line->delete($user) > 0) {
$result = $this->update_price(1);
2018-08-30 07:40:04 +00:00
2023-01-04 10:36:46 +00:00
if ($result > 0) {
$this->db->commit();
return 1;
2020-05-21 13:05:19 +00:00
} else {
2018-08-30 07:40:04 +00:00
$this->db->rollback();
2023-01-04 10:36:46 +00:00
$this->error = $this->db->lasterror();
return -1;
2018-08-30 07:40:04 +00:00
}
2020-05-21 13:05:19 +00:00
} else {
2018-08-30 07:40:04 +00:00
$this->db->rollback();
$this->setErrorsFromObject($line);
2018-08-30 07:40:04 +00:00
return -1;
}
2020-05-21 13:05:19 +00:00
} else {
$this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
2018-08-30 07:40:04 +00:00
return -1;
}
}
2019-02-24 23:56:48 +00:00
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2018-08-30 07:40:04 +00:00
/**
* Applique une remise relative
*
2024-03-15 18:57:45 +00:00
* @deprecated Use setDiscount() instead.
2021-02-09 12:25:55 +00:00
* @see setDiscount()
2018-08-30 07:40:04 +00:00
* @param User $user User qui positionne la remise
* @param float $remise Discount (percent)
* @param int $notrigger 1=Does not execute triggers, 0= execute triggers
2023-12-01 18:51:32 +00:00
* @return int Return integer <0 if KO, >0 if OK
2018-08-30 07:40:04 +00:00
*/
2019-02-24 23:56:48 +00:00
public function set_remise($user, $remise, $notrigger = 0)
2018-08-30 07:40:04 +00:00
{
// phpcs:enable
2021-02-09 12:25:55 +00:00
dol_syslog(get_class($this)."::set_remise is deprecated, use setDiscount instead", LOG_NOTICE);
2024-03-15 18:57:45 +00:00
// @phan-suppress-next-line PhanDeprecatedFunction
2021-02-09 12:25:55 +00:00
return $this->setDiscount($user, $remise, $notrigger);
}
2018-08-30 07:40:04 +00:00
2021-02-09 12:25:55 +00:00
/**
2021-04-25 17:21:48 +00:00
* Set a percentage discount
2021-02-09 12:25:55 +00:00
*
2021-04-25 17:21:48 +00:00
* @param User $user User setting the discount
* @param float|string $remise Discount (percent)
* @param int<0,1> $notrigger 1=Does not execute triggers, 0= execute triggers
* @return int<-1,1> Return integer <0 if KO, >0 if OK
2021-02-09 12:25:55 +00:00
*/
public function setDiscount($user, $remise, $notrigger = 0)
{
$remise = trim((string) $remise) ? trim((string) $remise) : 0;
2018-08-30 07:40:04 +00:00
2023-02-27 09:34:28 +00:00
if ($user->hasRight('commande', 'creer')) {
$error = 0;
2018-08-30 07:40:04 +00:00
$this->db->begin();
2021-03-29 20:51:36 +00:00
$remise = price2num($remise, 2);
2018-08-30 07:40:04 +00:00
$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2021-03-29 20:51:36 +00:00
$sql .= ' SET remise_percent = '.((float) $remise);
2021-08-27 21:36:06 +00:00
$sql .= ' WHERE rowid = '.((int) $this->id).' AND fk_statut = '.((int) self::STATUS_DRAFT);
2018-08-30 07:40:04 +00:00
dol_syslog(__METHOD__, LOG_DEBUG);
$resql = $this->db->query($sql);
2021-02-23 19:46:19 +00:00
if (!$resql) {
$this->errors[] = $this->db->error();
2018-08-30 07:40:04 +00:00
$error++;
}
2021-02-23 19:46:19 +00:00
if (!$error) {
$this->oldcopy = clone $this;
2018-08-30 07:40:04 +00:00
$this->remise_percent = $remise;
$this->update_price(1);
}
2021-02-23 19:46:19 +00:00
if (!$notrigger && empty($error)) {
2018-08-30 07:40:04 +00:00
// Call trigger
$result = $this->call_trigger('ORDER_MODIFY', $user);
2021-02-23 19:46:19 +00:00
if ($result < 0) {
$error++;
}
2018-08-30 07:40:04 +00:00
// End call triggers
}
2021-02-23 19:46:19 +00:00
if (!$error) {
2018-08-30 07:40:04 +00:00
$this->db->commit();
return 1;
2020-05-21 13:05:19 +00:00
} else {
2021-02-23 19:46:19 +00:00
foreach ($this->errors as $errmsg) {
2018-08-30 07:40:04 +00:00
dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2018-08-30 07:40:04 +00:00
}
$this->db->rollback();
return -1 * $error;
2018-08-30 07:40:04 +00:00
}
}
2023-01-06 18:24:57 +00:00
return 0;
2018-08-30 07:40:04 +00:00
}
2019-02-24 23:56:48 +00:00
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2018-08-30 07:40:04 +00:00
/**
* Set the order date
*
* @param User $user Object user making change
* @param int $date Date
* @param int $notrigger 1=Does not execute triggers, 0= execute triggers
2023-12-01 18:51:32 +00:00
* @return int Return integer <0 if KO, >0 if OK
2018-08-30 07:40:04 +00:00
*/
2019-02-24 23:56:48 +00:00
public function set_date($user, $date, $notrigger = 0)
2018-08-30 07:40:04 +00:00
{
// phpcs:enable
2023-02-27 09:24:24 +00:00
if ($user->hasRight('commande', 'creer')) {
$error = 0;
2018-08-30 07:40:04 +00:00
$this->db->begin();
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
$sql .= " SET date_commande = ".($date ? "'".$this->db->idate($date)."'" : 'null');
2021-08-23 17:33:24 +00:00
$sql .= " WHERE rowid = ".((int) $this->id)." AND fk_statut = ".((int) self::STATUS_DRAFT);
2018-08-30 07:40:04 +00:00
dol_syslog(__METHOD__, LOG_DEBUG);
$resql = $this->db->query($sql);
2021-02-23 19:46:19 +00:00
if (!$resql) {
$this->errors[] = $this->db->error();
2018-08-30 07:40:04 +00:00
$error++;
}
2021-02-23 19:46:19 +00:00
if (!$error) {
$this->oldcopy = clone $this;
2018-08-30 07:40:04 +00:00
$this->date = $date;
}
2021-02-23 19:46:19 +00:00
if (!$notrigger && empty($error)) {
2018-08-30 07:40:04 +00:00
// Call trigger
$result = $this->call_trigger('ORDER_MODIFY', $user);
2021-02-23 19:46:19 +00:00
if ($result < 0) {
$error++;
}
2018-08-30 07:40:04 +00:00
// End call triggers
}
2021-02-23 19:46:19 +00:00
if (!$error) {
2018-08-30 07:40:04 +00:00
$this->db->commit();
return 1;
2020-05-21 13:05:19 +00:00
} else {
2021-02-23 19:46:19 +00:00
foreach ($this->errors as $errmsg) {
2018-08-30 07:40:04 +00:00
dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
}
2018-08-30 07:40:04 +00:00
$this->db->rollback();
return -1 * $error;
}
2020-05-21 13:05:19 +00:00
} else {
2018-08-30 07:40:04 +00:00
return -2;
}
}
2011-09-12 17:08:02 +00:00
2019-02-24 23:56:48 +00:00
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
/**
* Set delivery date
2020-10-26 16:06:05 +00:00
*
* @param User $user Object user that modify
* @param int $delivery_date Delivery date
* @param int $notrigger 1=Does not execute triggers, 0= execute triggers
2023-12-06 14:46:39 +00:00
* @return int Return integer <0 if ko, >0 if ok
* @deprecated Use setDeliveryDate
*/
2020-10-26 17:21:47 +00:00
public function set_date_livraison($user, $delivery_date, $notrigger = 0)
{
// phpcs:enable
return $this->setDeliveryDate($user, $delivery_date, $notrigger);
}
2018-08-30 07:40:04 +00:00
/**
* Set the planned delivery date
*
* @param User $user Object User who makes the update
2020-10-06 09:37:55 +00:00
* @param int $delivery_date Delivery date
2018-08-30 07:40:04 +00:00
* @param int $notrigger 1=Does not execute triggers, 0= execute triggers
2023-12-06 14:46:39 +00:00
* @return int Return integer <0 si ko, >0 si ok
2018-08-30 07:40:04 +00:00
*/
public function setDeliveryDate($user, $delivery_date, $notrigger = 0)
2018-08-30 07:40:04 +00:00
{
2023-02-27 09:24:24 +00:00
if ($user->hasRight('commande', 'creer')) {
$error = 0;
2018-08-30 07:40:04 +00:00
$this->db->begin();
2011-09-12 17:08:02 +00:00
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
2020-10-06 09:37:55 +00:00
$sql .= " SET date_livraison = ".($delivery_date ? "'".$this->db->idate($delivery_date)."'" : 'null');
2021-08-23 17:33:24 +00:00
$sql .= " WHERE rowid = ".((int) $this->id);
2012-07-19 08:26:26 +00:00
2018-08-30 07:40:04 +00:00
dol_syslog(__METHOD__, LOG_DEBUG);
$resql = $this->db->query($sql);
2021-02-23 19:46:19 +00:00
if (!$resql) {
$this->errors[] = $this->db->error();
2018-08-30 07:40:04 +00:00
$error++;
}
2016-01-22 23:38:17 +00:00
2021-02-23 19:46:19 +00:00
if (!$error) {
$this->oldcopy = clone $this;
2020-11-11 17:08:40 +00:00
$this->delivery_date = $delivery_date;
2018-08-30 07:40:04 +00:00
}
2011-09-12 17:08:02 +00:00
2021-02-23 19:46:19 +00:00
if (!$notrigger && empty($error)) {
2018-08-30 07:40:04 +00:00
// Call trigger
$result = $this->call_trigger('ORDER_MODIFY', $user);
2021-02-23 19:46:19 +00:00
if ($result < 0) {
$error++;
}
2018-08-30 07:40:04 +00:00
// End call triggers
}
2021-02-23 19:46:19 +00:00
if (!$error) {
2018-08-30 07:40:04 +00:00
$this->db->commit();
return 1;
2020-05-21 13:05:19 +00:00
} else {
2021-02-23 19:46:19 +00:00
foreach ($this->errors as $errmsg) {
2018-08-30 07:40:04 +00:00
dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2018-08-30 07:40:04 +00:00
}
$this->db->rollback();
return -1 * $error;
2018-08-30 07:40:04 +00:00
}
2020-05-21 13:05:19 +00:00
} else {
2018-08-30 07:40:04 +00:00
return -2;
}
}
2019-02-24 23:56:48 +00:00
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2018-08-30 07:40:04 +00:00
/**
* Return list of orders (eventuelly filtered on a user) into an array
*
* @param int<0,1> $shortlist 0=Return array[id]=ref, 1=Return array[](id=>id,ref=>ref,name=>name)
* @param int<0,1> $draft 0=not draft, 1=draft
* @param ?User $excluser Object user to exclude
* @param int $socid Id third party
* @param int $limit For pagination
* @param int $offset For pagination
* @param string $sortfield Sort criteria
* @param string $sortorder Sort order
* @return array<int,string>|array<int,array{id:int,ref:string,name:string}>|int<-1,-1> -1 if KO, array with result if OK
2018-08-30 07:40:04 +00:00
*/
2023-12-20 13:22:45 +00:00
public function liste_array($shortlist = 0, $draft = 0, $excluser = null, $socid = 0, $limit = 0, $offset = 0, $sortfield = 'c.date_commande', $sortorder = 'DESC')
2018-08-30 07:40:04 +00:00
{
// phpcs:enable
2018-08-30 07:40:04 +00:00
global $user;
2018-08-30 07:40:04 +00:00
$ga = array();
2018-08-30 07:40:04 +00:00
$sql = "SELECT s.rowid, s.nom as name, s.client,";
$sql .= " c.rowid as cid, c.ref";
2025-01-13 10:58:16 +00:00
if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
2021-02-23 19:46:19 +00:00
$sql .= ", sc.fk_soc, sc.fk_user";
}
$sql .= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX.$this->table_element." as c";
2025-01-13 10:58:16 +00:00
if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
2021-02-23 19:46:19 +00:00
$sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
}
$sql .= " WHERE c.entity IN (".getEntity('commande').")";
$sql .= " AND c.fk_soc = s.rowid";
2025-01-13 10:58:16 +00:00
if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
$sql .= " AND s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
2018-08-30 07:40:04 +00:00
}
2021-02-23 19:46:19 +00:00
if ($socid) {
2021-04-24 18:18:11 +00:00
$sql .= " AND s.rowid = ".((int) $socid);
2021-02-23 19:46:19 +00:00
}
if ($draft) {
$sql .= " AND c.fk_statut = ".self::STATUS_DRAFT;
}
if (is_object($excluser)) {
2021-08-23 17:33:24 +00:00
$sql .= " AND c.fk_user_author <> ".((int) $excluser->id);
2021-02-23 19:46:19 +00:00
}
$sql .= $this->db->order($sortfield, $sortorder);
$sql .= $this->db->plimit($limit, $offset);
2011-09-12 17:08:02 +00:00
$result = $this->db->query($sql);
2021-02-23 19:46:19 +00:00
if ($result) {
2018-08-30 07:40:04 +00:00
$numc = $this->db->num_rows($result);
2021-02-23 19:46:19 +00:00
if ($numc) {
2018-08-30 07:40:04 +00:00
$i = 0;
2021-02-23 19:46:19 +00:00
while ($i < $numc) {
2018-08-30 07:40:04 +00:00
$obj = $this->db->fetch_object($result);
2018-02-24 15:21:16 +00:00
2021-02-23 19:46:19 +00:00
if ($shortlist == 1) {
2018-08-30 07:40:04 +00:00
$ga[$obj->cid] = $obj->ref;
2021-02-23 19:46:19 +00:00
} elseif ($shortlist == 2) {
2018-08-30 07:40:04 +00:00
$ga[$obj->cid] = $obj->ref.' ('.$obj->name.')';
2020-05-21 13:05:19 +00:00
} else {
$ga[$i]['id'] = $obj->cid;
2018-08-30 07:40:04 +00:00
$ga[$i]['ref'] = $obj->ref;
$ga[$i]['name'] = $obj->name;
}
$i++;
}
}
return $ga;
2020-05-21 13:05:19 +00:00
} else {
2018-08-30 07:40:04 +00:00
dol_print_error($this->db);
return -1;
}
}
2012-07-08 10:18:29 +00:00
2018-08-30 07:40:04 +00:00
/**
* Update delivery delay
*
* @param int $availability_id Id du nouveau mode
* @param int $notrigger 1=Does not execute triggers, 0= execute triggers
* @return int >0 if OK, <0 if KO
*/
2019-02-24 23:56:48 +00:00
public function availability($availability_id, $notrigger = 0)
2018-08-30 07:40:04 +00:00
{
global $user;
2018-08-30 07:40:04 +00:00
dol_syslog('Commande::availability('.$availability_id.')');
if ($this->status >= self::STATUS_DRAFT) {
$error = 0;
2018-08-30 07:40:04 +00:00
$this->db->begin();
2011-09-12 17:08:02 +00:00
$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2021-03-30 09:36:50 +00:00
$sql .= ' SET fk_availability = '.((int) $availability_id);
2021-03-14 11:20:23 +00:00
$sql .= ' WHERE rowid='.((int) $this->id);
2018-08-30 07:40:04 +00:00
dol_syslog(__METHOD__, LOG_DEBUG);
$resql = $this->db->query($sql);
2021-02-23 19:46:19 +00:00
if (!$resql) {
$this->errors[] = $this->db->error();
2018-08-30 07:40:04 +00:00
$error++;
}
2021-02-23 19:46:19 +00:00
if (!$error) {
$this->oldcopy = clone $this;
2018-08-30 07:40:04 +00:00
$this->availability_id = $availability_id;
}
2011-09-12 17:08:02 +00:00
2021-02-23 19:46:19 +00:00
if (!$notrigger && empty($error)) {
2018-08-30 07:40:04 +00:00
// Call trigger
$result = $this->call_trigger('ORDER_MODIFY', $user);
2021-02-23 19:46:19 +00:00
if ($result < 0) {
$error++;
}
2018-08-30 07:40:04 +00:00
// End call triggers
}
2021-02-23 19:46:19 +00:00
if (!$error) {
2018-08-30 07:40:04 +00:00
$this->db->commit();
return 1;
2020-05-21 13:05:19 +00:00
} else {
2021-02-23 19:46:19 +00:00
foreach ($this->errors as $errmsg) {
2018-08-30 07:40:04 +00:00
dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2018-08-30 07:40:04 +00:00
}
$this->db->rollback();
return -1 * $error;
2018-08-30 07:40:04 +00:00
}
2020-05-21 13:05:19 +00:00
} else {
$error_str = 'Command status do not meet requirement '.$this->status;
2018-08-30 07:40:04 +00:00
dol_syslog(__METHOD__.$error_str, LOG_ERR);
$this->error = $error_str;
$this->errors[] = $this->error;
2018-08-30 07:40:04 +00:00
return -2;
}
}
2019-02-24 23:56:48 +00:00
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2018-08-30 07:40:04 +00:00
/**
* Update order demand_reason
*
* @param int $demand_reason_id Id of new demand
* @param int $notrigger 1=Does not execute triggers, 0= execute triggers
* @return int >0 if ok, <0 if ko
*/
2019-02-24 23:56:48 +00:00
public function demand_reason($demand_reason_id, $notrigger = 0)
2018-08-30 07:40:04 +00:00
{
// phpcs:enable
2018-08-30 07:40:04 +00:00
global $user;
2011-09-12 17:08:02 +00:00
2018-08-30 07:40:04 +00:00
dol_syslog('Commande::demand_reason('.$demand_reason_id.')');
if ($this->status >= self::STATUS_DRAFT) {
$error = 0;
2011-09-12 17:08:02 +00:00
2018-08-30 07:40:04 +00:00
$this->db->begin();
$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
2021-03-30 09:36:50 +00:00
$sql .= ' SET fk_input_reason = '.((int) $demand_reason_id);
2021-03-14 11:20:23 +00:00
$sql .= ' WHERE rowid='.((int) $this->id);
2018-08-30 07:40:04 +00:00
dol_syslog(__METHOD__, LOG_DEBUG);
$resql = $this->db->query($sql);
2021-02-23 19:46:19 +00:00
if (!$resql) {
$this->errors[] = $this->db->error();
2018-08-30 07:40:04 +00:00
$error++;
}
2021-02-23 19:46:19 +00:00
if (!$error) {
$this->oldcopy = clone $this;
2018-08-30 07:40:04 +00:00
$this->demand_reason_id = $demand_reason_id;
}
2021-02-23 19:46:19 +00:00
if (!$notrigger && empty($error)) {
2018-08-30 07:40:04 +00:00
// Call trigger
$result = $this->call_trigger('ORDER_MODIFY', $user);
2021-02-23 19:46:19 +00:00
if ($result < 0) {
$error++;
}
2018-08-30 07:40:04 +00:00
// End call triggers
}
2021-02-23 19:46:19 +00:00
if (!$error) {
2018-08-30 07:40:04 +00:00
$this->db->commit();
return 1;
2020-05-21 13:05:19 +00:00
} else {
2021-02-23 19:46:19 +00:00
foreach ($this->errors as $errmsg) {
2018-08-30 07:40:04 +00:00
dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2018-08-30 07:40:04 +00:00
}
$this->db->rollback();
return -1 * $error;
2018-08-30 07:40:04 +00:00
}
2020-05-21 13:05:19 +00:00
} else {
$error_str = 'order status do not meet requirement '.$this->status;
2018-08-30 07:40:04 +00:00
dol_syslog(__METHOD__.$error_str, LOG_ERR);
$this->error = $error_str;
$this->errors[] = $this->error;
2018-08-30 07:40:04 +00:00
return -2;
}
}
2019-02-24 23:56:48 +00:00
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2018-08-30 07:40:04 +00:00
/**
* Set customer ref
*
* @param User $user User that make change
* @param string $ref_client Customer ref
* @param int $notrigger 1=Does not execute triggers, 0= execute triggers
2023-12-01 18:51:32 +00:00
* @return int Return integer <0 if KO, >0 if OK
2018-08-30 07:40:04 +00:00
*/
2019-02-24 23:56:48 +00:00
public function set_ref_client($user, $ref_client, $notrigger = 0)
2018-08-30 07:40:04 +00:00
{
// phpcs:enable
2023-02-27 09:24:24 +00:00
if ($user->hasRight('commande', 'creer')) {
$error = 0;
2018-08-30 07:40:04 +00:00
$this->db->begin();
$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element.' SET';
2021-03-29 20:51:36 +00:00
$sql .= ' ref_client = '.(empty($ref_client) ? 'NULL' : "'".$this->db->escape($ref_client)."'");
2021-08-27 21:36:06 +00:00
$sql .= ' WHERE rowid = '.((int) $this->id);
2018-08-30 07:40:04 +00:00
dol_syslog(__METHOD__.' this->id='.$this->id.', ref_client='.$ref_client, LOG_DEBUG);
$resql = $this->db->query($sql);
2021-02-23 19:46:19 +00:00
if (!$resql) {
$this->errors[] = $this->db->error();
2018-08-30 07:40:04 +00:00
$error++;
}
2021-02-23 19:46:19 +00:00
if (!$error) {
$this->oldcopy = clone $this;
2018-08-30 07:40:04 +00:00
$this->ref_client = $ref_client;
2023-05-01 11:09:09 +00:00
$this->ref_customer = $ref_client;
2018-08-30 07:40:04 +00:00
}
2021-02-23 19:46:19 +00:00
if (!$notrigger && empty($error)) {
2018-08-30 07:40:04 +00:00
// Call trigger
$result = $this->call_trigger('ORDER_MODIFY', $user);
2021-02-23 19:46:19 +00:00
if ($result < 0) {
$error++;
}
2018-08-30 07:40:04 +00:00
// End call triggers
}
2021-02-23 19:46:19 +00:00
if (!$error) {
2018-08-30 07:40:04 +00:00
$this->db->commit();
return 1;
2020-05-21 13:05:19 +00:00
} else {
2021-02-23 19:46:19 +00:00
foreach ($this->errors as $errmsg) {
2018-08-30 07:40:04 +00:00
dol_syslog(__METHOD__.' Error: '.$errmsg, LOG_ERR);
$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2018-08-30 07:40:04 +00:00
}
$this->db->rollback();
return -1 * $error;
2018-08-30 07:40:04 +00:00
}
2020-05-21 13:05:19 +00:00
} else {
2018-08-30 07:40:04 +00:00
return -1;
}
}
2011-09-12 17:08:02 +00:00
2012-07-08 10:18:29 +00:00
/**
* Classify the order as invoiced
*
2016-07-08 19:02:27 +00:00
* @param User $user Object user making the change
* @param int $notrigger 1=Does not execute triggers, 0=execute triggers
2023-12-06 14:46:39 +00:00
* @return int Return integer <0 if KO, 0 if already billed, >0 if OK
2012-07-08 10:18:29 +00:00
*/
2019-02-24 23:56:48 +00:00
public function classifyBilled(User $user, $notrigger = 0)
2012-07-08 10:18:29 +00:00
{
2018-08-30 07:40:04 +00:00
$error = 0;
2021-02-23 19:46:19 +00:00
if ($this->billed) {
return 0;
}
2011-09-12 17:08:02 +00:00
$this->db->begin();
2011-09-12 17:08:02 +00:00
$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element.' SET facture = 1';
2021-08-27 21:36:06 +00:00
$sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > '.self::STATUS_DRAFT;
2014-06-12 09:31:53 +00:00
dol_syslog(get_class($this)."::classifyBilled", LOG_DEBUG);
2021-02-23 19:46:19 +00:00
if ($this->db->query($sql)) {
$this->oldcopy = clone $this;
$this->billed = 1;
2016-07-08 19:28:21 +00:00
2021-02-23 19:46:19 +00:00
if (!$notrigger && empty($error)) {
2018-08-30 07:40:04 +00:00
// Call trigger
$result = $this->call_trigger('ORDER_CLASSIFY_BILLED', $user);
2021-02-23 19:46:19 +00:00
if ($result < 0) {
$error++;
}
2018-08-30 07:40:04 +00:00
// End call triggers
2016-07-08 19:02:27 +00:00
}
2012-07-08 10:18:29 +00:00
2021-02-23 19:46:19 +00:00
if (!$error) {
$this->db->commit();
return 1;
2020-05-21 13:05:19 +00:00
} else {
2021-02-23 19:46:19 +00:00
foreach ($this->errors as $errmsg) {
dol_syslog(get_class($this)."::classifyBilled ".$errmsg, LOG_ERR);
$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
}
$this->db->rollback();
return -1 * $error;
}
2020-05-21 13:05:19 +00:00
} else {
$this->error = $this->db->error();
2018-08-30 07:40:04 +00:00
$this->db->rollback();
2012-07-08 10:18:29 +00:00
return -1;
}
}
/**
* Classify the order as not invoiced
*
2021-03-02 01:55:24 +00:00
* @param User $user Object user making the change
* @param int $notrigger 1=Does not execute triggers, 0=execute triggers
2023-12-06 14:46:39 +00:00
* @return int Return integer <0 if ko, >0 if ok
*/
2021-03-02 01:55:24 +00:00
public function classifyUnBilled(User $user, $notrigger = 0)
{
2018-08-30 07:40:04 +00:00
$error = 0;
$this->db->begin();
$sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element.' SET facture = 0';
2021-08-27 21:36:06 +00:00
$sql .= " WHERE rowid = ".((int) $this->id).' AND fk_statut > '.self::STATUS_DRAFT;
2018-08-30 07:40:04 +00:00
dol_syslog(get_class($this)."::classifyUnBilled", LOG_DEBUG);
2021-02-23 19:46:19 +00:00
if ($this->db->query($sql)) {
$this->oldcopy = clone $this;
$this->billed = 1;
2018-08-30 07:40:04 +00:00
2021-03-02 01:55:24 +00:00
if (!$notrigger && empty($error)) {
// Call trigger
$result = $this->call_trigger('ORDER_CLASSIFY_UNBILLED', $user);
if ($result < 0) {
$error++;
}
// End call triggers
2021-02-23 19:46:19 +00:00
}
2018-08-30 07:40:04 +00:00
2021-02-23 19:46:19 +00:00
if (!$error) {
$this->billed = 0;
2018-08-30 07:40:04 +00:00
$this->db->commit();
return 1;
2020-05-21 13:05:19 +00:00
} else {
2021-02-23 19:46:19 +00:00
foreach ($this->errors as $errmsg) {
2018-08-30 07:40:04 +00:00
dol_syslog(get_class($this)."::classifyUnBilled ".$errmsg, LOG_ERR);
$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
2018-08-30 07:40:04 +00:00
}
$this->db->rollback();
return -1 * $error;
2018-08-30 07:40:04 +00:00
}
2020-05-21 13:05:19 +00:00
} else {
$this->error = $this->db->error();
2018-08-30 07:40:04 +00:00
$this->db->rollback();
return -1;
}
}
2018-08-30 07:40:04 +00:00
/**
* Update a line in database
*
* @param int $rowid Id of line to update
* @param string $desc Description of line
* @param float $pu Unit price
* @param float $qty Quantity
* @param float $remise_percent Percent of discount
2025-10-19 16:32:25 +00:00
* @param float|string $txtva VAT rate. Can be '19.6' or '19.6 (CODE)'
2018-08-30 07:40:04 +00:00
* @param float $txlocaltax1 Local tax 1 rate
* @param float $txlocaltax2 Local tax 2 rate
* @param string $price_base_type HT or TTC
* @param int $info_bits Miscellaneous information on line
* @param int|'' $date_start Start date of the line
* @param int|'' $date_end End date of the line
2018-08-30 07:40:04 +00:00
* @param int $type Type of line (0=product, 1=service)
* @param int $fk_parent_line Id of parent line (0 in most cases, used by modules adding sublevels into lines).
* @param int $skip_update_total Keep fields total_xxx to 0 (used for special lines by some modules)
* @param int $fk_fournprice Id of origin supplier price
2025-04-07 13:46:10 +00:00
* @param float|string $pa_ht Price (without tax) of product when it was bought (Can be '' to keep AWP unchanged or a float value)
2018-08-30 07:40:04 +00:00
* @param string $label Label
* @param int $special_code Special code (also used by externals modules!)
* @param array<string,mixed> $array_options extrafields array
* @param ?int $fk_unit Code of the unit to use. Null to use the default one
* @param float $pu_ht_devise Amount in currency
2018-08-30 07:40:04 +00:00
* @param int $notrigger disable line update trigger
2020-09-04 19:11:58 +00:00
* @param string $ref_ext external reference
* @param int $rang line rank
2023-12-06 14:46:39 +00:00
* @return int Return integer < 0 if KO, > 0 if OK
2018-08-30 07:40:04 +00:00
*/
2023-12-21 12:43:19 +00:00
public function updateline($rowid, $desc, $pu, $qty, $remise_percent, $txtva, $txlocaltax1 = 0.0, $txlocaltax2 = 0.0, $price_base_type = 'HT', $info_bits = 0, $date_start = '', $date_end = '', $type = 0, $fk_parent_line = 0, $skip_update_total = 0, $fk_fournprice = null, $pa_ht = 0, $label = '', $special_code = 0, $array_options = array(), $fk_unit = null, $pu_ht_devise = 0, $notrigger = 0, $ref_ext = '', $rang = 0)
2018-08-30 07:40:04 +00:00
{
2025-01-19 16:38:34 +00:00
global $mysoc, $langs, $user;
2018-08-30 07:40:04 +00:00
2020-09-04 19:11:58 +00:00
dol_syslog(get_class($this)."::updateline id=$rowid, desc=$desc, pu=$pu, qty=$qty, remise_percent=$remise_percent, txtva=$txtva, txlocaltax1=$txlocaltax1, txlocaltax2=$txlocaltax2, price_base_type=$price_base_type, info_bits=$info_bits, date_start=$date_start, date_end=$date_end, type=$type, fk_parent_line=$fk_parent_line, pa_ht=$pa_ht, special_code=$special_code, ref_ext=$ref_ext");
2018-08-30 07:40:04 +00:00
include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
if ($this->status == Commande::STATUS_DRAFT) {
2018-08-30 07:40:04 +00:00
// Clean parameters
2021-02-23 19:46:19 +00:00
if (empty($qty)) {
$qty = 0;
}
if (empty($info_bits)) {
$info_bits = 0;
}
if (empty($txtva)) {
$txtva = 0;
}
if (empty($txlocaltax1)) {
$txlocaltax1 = 0;
}
if (empty($txlocaltax2)) {
$txlocaltax2 = 0;
}
if (empty($remise_percent)) {
$remise_percent = 0;
}
if (empty($qty) && empty($special_code)) {
$special_code = 3; // Set option tag
}
if (!empty($qty) && $special_code == 3) {
$special_code = 0; // Remove option tag
2021-02-23 19:46:19 +00:00
}
if (empty($ref_ext)) {
$ref_ext = '';
}
if ($date_start && $date_end && $date_start > $date_end) {
$langs->load("errors");
$this->error = $langs->trans('ErrorStartDateGreaterEnd');
return -1;
}
2018-08-30 07:40:04 +00:00
2024-01-23 15:07:59 +00:00
$remise_percent = (float) price2num($remise_percent);
$qty = (float) price2num($qty);
2018-08-30 07:40:04 +00:00
$pu = price2num($pu);
$pa_ht = price2num($pa_ht); // do not convert to float here, it breaks the functioning of $pa_ht_isemptystring
$pu_ht_devise = price2num($pu_ht_devise);
if (!preg_match('/\((.*)\)/', (string) $txtva)) {
$txtva = price2num($txtva); // $txtva can have format '5.0(XXX)' or '5'
}
2024-01-23 15:07:59 +00:00
$txlocaltax1 = (float) price2num($txlocaltax1);
$txlocaltax2 = (float) price2num($txlocaltax2);
$this->db->begin();
2018-08-30 07:40:04 +00:00
// Calculation of the gross total (TTC) and VAT for the line from qty, pu, remise_percent and txtva
// VERY IMPORTANT: It's at the time of line insertion that we must store the net, VAT, and gross amounts,
// and this is done at the line level, which has its own VAT rate
2018-08-30 07:40:04 +00:00
$localtaxes_type = getLocalTaxesFromRate($txtva, 0, $this->thirdparty, $mysoc);
2018-08-30 07:40:04 +00:00
2018-08-30 08:30:52 +00:00
// Clean vat code
$vat_src_code = '';
2020-11-27 13:24:15 +00:00
$reg = array();
2021-02-23 19:46:19 +00:00
if (preg_match('/\((.*)\)/', $txtva, $reg)) {
2018-08-30 07:40:04 +00:00
$vat_src_code = $reg[1];
$txtva = preg_replace('/\s*\(.*\)/', '', $txtva); // Remove code into vatrate.
2018-08-30 07:40:04 +00:00
}
2025-03-04 17:02:29 +00:00
$tabprice = calcul_price_total($qty, (float) $pu, $remise_percent, $txtva, $txlocaltax1, $txlocaltax2, 0, $price_base_type, $info_bits, $type, $mysoc, $localtaxes_type, 100, $this->multicurrency_tx, (float) $pu_ht_devise);
2018-08-30 07:40:04 +00:00
2023-02-27 09:24:24 +00:00
$total_ht = $tabprice[0];
2018-08-30 07:40:04 +00:00
$total_tva = $tabprice[1];
$total_ttc = $tabprice[2];
$total_localtax1 = $tabprice[9];
$total_localtax2 = $tabprice[10];
2023-02-27 09:24:24 +00:00
$pu_ht = $tabprice[3];
2016-12-10 23:42:52 +00:00
$pu_tva = $tabprice[4];
$pu_ttc = $tabprice[5];
2011-09-12 17:08:02 +00:00
2016-01-22 23:38:17 +00:00
// MultiCurrency
2023-02-27 09:24:24 +00:00
$multicurrency_total_ht = $tabprice[16];
2018-08-30 07:40:04 +00:00
$multicurrency_total_tva = $tabprice[17];
$multicurrency_total_ttc = $tabprice[18];
2016-12-10 23:42:52 +00:00
$pu_ht_devise = $tabprice[19];
2024-10-15 13:39:29 +00:00
// Fetch current line from the database and then clone the object and set it in $oldline property
2018-08-30 07:40:04 +00:00
$line = new OrderLine($this->db);
$line->fetch($rowid);
2020-01-25 13:03:24 +00:00
$line->fetch_optionals();
2018-08-30 07:40:04 +00:00
2021-02-23 19:46:19 +00:00
if (!empty($line->fk_product)) {
$product = new Product($this->db);
$result = $product->fetch($line->fk_product);
$product_type = $product->type;
2018-08-30 07:40:04 +00:00
if (getDolGlobalString('STOCK_MUST_BE_ENOUGH_FOR_ORDER') && $product->isStockManaged()) {
// get real stock
$productChildrenNb = 0;
if (getDolGlobalInt('PRODUIT_SOUSPRODUITS')) {
$productChildrenNb = $product->hasFatherOrChild(1);
}
if ($productChildrenNb > 0) {
// compute real stock from each subcomponent
$product_stock = null;
$product->loadStockForVirtualProduct('warehouseopen', $qty);
foreach ($product->stock_warehouse as $componentStockWarehouse) {
if ($product_stock === null) {
$product_stock = $componentStockWarehouse->real;
} else {
$product_stock = min($product_stock, $componentStockWarehouse->real);
}
}
if ($product_stock === null) {
$product_stock = 0;
}
} else {
$product_stock = $product->stock_reel;
}
2021-04-25 17:21:48 +00:00
if ($product_stock < $qty) {
$langs->load("errors");
$this->error = $langs->trans('ErrorStockIsNotEnoughToAddProductOnOrder', (string) $product->ref);
$this->errors[] = $this->error;
2021-04-25 17:21:48 +00:00
dol_syslog(get_class($this)."::addline error=Product ".$product->ref.": ".$this->error, LOG_ERR);
$this->db->rollback();
return self::STOCK_NOT_ENOUGH_FOR_ORDER;
}
2018-08-30 07:40:04 +00:00
}
}
$staticline = clone $line;
$line->oldline = $staticline;
$this->line = $line;
$this->line->context = $this->context;
$this->line->rang = $rang;
2018-08-30 07:40:04 +00:00
// Reorder if fk_parent_line change
2021-02-23 19:46:19 +00:00
if (!empty($fk_parent_line) && !empty($staticline->fk_parent_line) && $fk_parent_line != $staticline->fk_parent_line) {
2018-08-30 07:40:04 +00:00
$rangmax = $this->line_max($fk_parent_line);
$this->line->rang = $rangmax + 1;
}
if (getDolGlobalString('PRODUCT_USE_CUSTOMER_PACKAGING')) {
Fix #38782 harmonize packaging-rounding logic between supplier and customer order paths (#38800) When PRODUCT_USE_CUSTOMER_PACKAGING / PRODUCT_USE_SUPPLIER_PACKAGING are enabled, the four order line methods (Commande::addline, Commande::updateline, CommandeFournisseur::addline, CommandeFournisseur::updateline) recompute the quantity to the next packaging multiple. Reporter audited the four code paths and listed six divergences between them; the customer addline at htdocs/commande/class/commande.class.php:1720 is the reference and the rest were re-aligned on it: - Bug 1: supplier addline did not call setEventMessages() when qty < packaging, so the user was silently corrected (htdocs/fourn/class/fournisseur.commande.class.php:2153). - Bug 2: supplier addline used a raw float multiplication; switch to price2num('MS'). - Bug 3: supplier updateline used 'fmod() > 0' which is vulnerable to float noise (fmod(3.0, 1.5) can yield 4.44e-16); switch to price2num((float) ..., 'MS'). - Bug 4: supplier updateline missed the qty < packaging notification. - Bug 5: both updateline paths missed abs() in coeff = intval(qty / packaging) + 1, which broke rounding for negative qty; the addline paths already do abs(). - Bug 6: supplier path used setEventMessages('mesgs') (green/info); the user feedback is a corrected input, semantically a warning. Switch to 'warnings' to match the customer path. No behavior change for callers passing positive, valid quantities; the diff only affects edge cases (negative qty, qty just under packaging, float-fmod noise) and the message channel. Signed-off-by: Dolicraft <contact@dolicraft.com> Co-authored-by: Dolicraft <contact@dolicraft.com>
2026-06-13 22:50:37 +00:00
if (abs((float) $qty) < $this->line->packaging) {
$qty = $this->line->packaging;
2026-01-29 12:01:49 +00:00
setEventMessage($langs->trans('QtyRecalculatedWithPackaging'), 'warnings');
} else {
if (!empty($this->line->packaging)
&& is_numeric($this->line->packaging)
&& (float) $this->line->packaging > 0
2026-01-29 12:01:49 +00:00
&& (float) price2num(fmod((float) $qty, (float) $this->line->packaging), 'MS')) {
Fix #38782 harmonize packaging-rounding logic between supplier and customer order paths (#38800) When PRODUCT_USE_CUSTOMER_PACKAGING / PRODUCT_USE_SUPPLIER_PACKAGING are enabled, the four order line methods (Commande::addline, Commande::updateline, CommandeFournisseur::addline, CommandeFournisseur::updateline) recompute the quantity to the next packaging multiple. Reporter audited the four code paths and listed six divergences between them; the customer addline at htdocs/commande/class/commande.class.php:1720 is the reference and the rest were re-aligned on it: - Bug 1: supplier addline did not call setEventMessages() when qty < packaging, so the user was silently corrected (htdocs/fourn/class/fournisseur.commande.class.php:2153). - Bug 2: supplier addline used a raw float multiplication; switch to price2num('MS'). - Bug 3: supplier updateline used 'fmod() > 0' which is vulnerable to float noise (fmod(3.0, 1.5) can yield 4.44e-16); switch to price2num((float) ..., 'MS'). - Bug 4: supplier updateline missed the qty < packaging notification. - Bug 5: both updateline paths missed abs() in coeff = intval(qty / packaging) + 1, which broke rounding for negative qty; the addline paths already do abs(). - Bug 6: supplier path used setEventMessages('mesgs') (green/info); the user feedback is a corrected input, semantically a warning. Switch to 'warnings' to match the customer path. No behavior change for callers passing positive, valid quantities; the diff only affects edge cases (negative qty, qty just under packaging, float-fmod noise) and the message channel. Signed-off-by: Dolicraft <contact@dolicraft.com> Co-authored-by: Dolicraft <contact@dolicraft.com>
2026-06-13 22:50:37 +00:00
// Use abs() to keep the rounding consistent for negative qty,
// matching what addline() at line 1725 already does (#38782 bug 5).
$coeff = intval(abs((float) $qty) / $this->line->packaging) + 1;
$qty = price2num((float) $this->line->packaging * $coeff, 'MS');
2026-01-29 12:01:49 +00:00
setEventMessage($langs->trans('QtyRecalculatedWithPackaging'), 'warnings');
}
}
}
$this->line->id = $rowid;
2021-06-19 12:52:06 +00:00
$this->line->label = $label;
$this->line->desc = $desc;
$this->line->qty = $qty;
2020-09-04 19:11:58 +00:00
$this->line->ref_ext = $ref_ext;
2018-08-30 07:40:04 +00:00
$this->line->vat_src_code = $vat_src_code;
2018-08-30 07:40:04 +00:00
$this->line->tva_tx = $txtva;
$this->line->localtax1_tx = $txlocaltax1;
$this->line->localtax2_tx = $txlocaltax2;
2020-12-22 16:33:17 +00:00
$this->line->localtax1_type = empty($localtaxes_type[0]) ? '' : $localtaxes_type[0];
$this->line->localtax2_type = empty($localtaxes_type[2]) ? '' : $localtaxes_type[2];
2018-08-30 07:40:04 +00:00
$this->line->remise_percent = $remise_percent;
$this->line->subprice = (float) $pu_ht;
2018-08-30 07:40:04 +00:00
$this->line->info_bits = $info_bits;
$this->line->special_code = $special_code;
$this->line->total_ht = (float) $total_ht;
$this->line->total_tva = (float) $total_tva;
$this->line->total_localtax1 = (float) $total_localtax1;
$this->line->total_localtax2 = (float) $total_localtax2;
$this->line->total_ttc = (float) $total_ttc;
2018-08-30 07:40:04 +00:00
$this->line->date_start = $date_start;
$this->line->date_end = $date_end;
$this->line->product_type = $type;
$this->line->fk_parent_line = $fk_parent_line;
$this->line->skip_update_total = $skip_update_total;
2018-08-30 07:40:04 +00:00
$this->line->fk_unit = $fk_unit;
2011-09-12 17:08:02 +00:00
$this->line->fk_fournprice = $fk_fournprice;
2012-08-23 05:50:20 +00:00
$this->line->pa_ht = $pa_ht;
2012-07-20 07:57:50 +00:00
2016-01-22 23:38:17 +00:00
// Multicurrency
$this->line->multicurrency_subprice = (float) $pu_ht_devise;
$this->line->multicurrency_total_ht = (float) $multicurrency_total_ht;
$this->line->multicurrency_total_tva = (float) $multicurrency_total_tva;
$this->line->multicurrency_total_ttc = (float) $multicurrency_total_ttc;
if (is_array($array_options) && count($array_options) > 0) {
2020-01-25 13:03:24 +00:00
// We replace values in this->line->array_options only for entries defined into $array_options
foreach ($array_options as $key => $value) {
2020-01-25 13:03:24 +00:00
$this->line->array_options[$key] = $array_options[$key];
}
}
$result = $this->line->update($user, $notrigger);
2021-02-23 19:46:19 +00:00
if ($result > 0) {
2018-08-30 07:40:04 +00:00
// Reorder if child line
2021-02-23 19:46:19 +00:00
if (!empty($fk_parent_line)) {
$this->line_order(true, 'DESC');
}
2018-08-30 07:40:04 +00:00
// Mise a jour info denormalisees
$this->update_price(1, 'auto');
2018-08-30 07:40:04 +00:00
$this->db->commit();
return $result;
2020-05-21 13:05:19 +00:00
} else {
$this->setErrorsFromObject($this->line);
2018-08-30 07:40:04 +00:00
$this->db->rollback();
return -1;
}
2020-05-21 13:05:19 +00:00
} else {
$this->error = get_class($this)."::updateline Order status makes operation forbidden";
$this->errors = array('OrderStatusMakeOperationForbidden');
2018-08-30 07:40:04 +00:00
return -2;
}
}
2011-09-12 17:08:02 +00:00
/**
* Update database
*
* @param User $user User that modify
* @param int $notrigger 0=launch triggers after, 1=disable triggers
2023-12-01 18:51:32 +00:00
* @return int Return integer <0 if KO, >0 if OK
*/
2019-02-24 23:56:48 +00:00
public function update(User $user, $notrigger = 0)
{
$error = 0;
// Clean parameters
2021-02-23 19:46:19 +00:00
if (isset($this->ref)) {
$this->ref = trim($this->ref);
}
if (isset($this->ref_client)) {
$this->ref_client = trim($this->ref_client);
}
2023-05-01 11:09:09 +00:00
if (isset($this->ref_customer)) {
$this->ref_customer = trim($this->ref_customer);
}
2021-02-23 19:46:19 +00:00
if (isset($this->note) || isset($this->note_private)) {
$this->note_private = (isset($this->note_private) ? trim($this->note_private) : trim($this->note));
}
if (isset($this->note_public)) {
$this->note_public = trim($this->note_public);
}
if (isset($this->model_pdf)) {
$this->model_pdf = trim($this->model_pdf);
}
if (isset($this->import_key)) {
$this->import_key = trim($this->import_key);
}
$delivery_date = $this->delivery_date;
// Check parameters
// Put here code to add control on parameters values
// Update request
$sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
$sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "null").",";
$sql .= " ref_client=".(isset($this->ref_client) ? "'".$this->db->escape($this->ref_client)."'" : "null").",";
$sql .= " ref_ext=".(isset($this->ref_ext) ? "'".$this->db->escape($this->ref_ext)."'" : "null").",";
$sql .= " fk_soc=".(isset($this->socid) ? $this->socid : "null").",";
$sql .= " date_commande=".(strval($this->date_commande) != '' ? "'".$this->db->idate($this->date_commande)."'" : 'null').",";
$sql .= " date_valid=".(strval($this->date_validation) != '' ? "'".$this->db->idate($this->date_validation)."'" : 'null').",";
$sql .= " total_tva=".(isset($this->total_tva) ? $this->total_tva : "null").",";
$sql .= " localtax1=".(isset($this->total_localtax1) ? $this->total_localtax1 : "null").",";
$sql .= " localtax2=".(isset($this->total_localtax2) ? $this->total_localtax2 : "null").",";
$sql .= " total_ht=".(isset($this->total_ht) ? $this->total_ht : "null").",";
$sql .= " total_ttc=".(isset($this->total_ttc) ? $this->total_ttc : "null").",";
$sql .= " fk_statut=".(isset($this->status) ? $this->status : "null").",";
$sql .= " fk_user_modif=".(isset($user->id) ? $user->id : "null").",";
$sql .= " fk_user_valid=".((isset($this->user_validation_id) && $this->user_validation_id > 0) ? $this->user_validation_id : "null").",";
$sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").",";
$sql .= " fk_cond_reglement=".(isset($this->cond_reglement_id) ? $this->cond_reglement_id : "null").",";
2022-08-31 20:14:20 +00:00
$sql .= " deposit_percent=".(!empty($this->deposit_percent) ? strval($this->deposit_percent) : "null").",";
$sql .= " fk_mode_reglement=".(isset($this->mode_reglement_id) ? $this->mode_reglement_id : "null").",";
$sql .= " date_livraison=".(strval($this->delivery_date) != '' ? "'".$this->db->idate($this->delivery_date)."'" : 'null').",";
$sql .= " fk_shipping_method=".(isset($this->shipping_method_id) ? $this->shipping_method_id : "null").",";
$sql .= " fk_account=".($this->fk_account > 0 ? $this->fk_account : "null").",";
2020-12-03 15:01:17 +00:00
$sql .= " fk_input_reason=".($this->demand_reason_id > 0 ? $this->demand_reason_id : "null").",";
$sql .= " note_private=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
$sql .= " note_public=".(isset($this->note_public) ? "'".$this->db->escape($this->note_public)."'" : "null").",";
2021-02-08 17:30:09 +00:00
$sql .= " model_pdf=".(isset($this->model_pdf) ? "'".$this->db->escape($this->model_pdf)."'" : "null").",";
$sql .= " fk_warehouse=".($this->warehouse_id > 0 ? $this->warehouse_id : "null").",";
$sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null").",";
$sql .= " module_source = ".(isset($this->module_source) ? "'".$this->db->escape($this->module_source)."'" : "null").",";
$sql .= " pos_source = ".(isset($this->pos_source) ? "'".$this->db->escape($this->pos_source)."'" : "null");
2021-03-14 11:20:23 +00:00
$sql .= " WHERE rowid=".((int) $this->id);
$this->db->begin();
dol_syslog(get_class($this)."::update", LOG_DEBUG);
$resql = $this->db->query($sql);
if (!$resql) {
2023-12-04 11:01:45 +00:00
$error++;
$this->errors[] = "Error ".$this->db->lasterror();
}
2021-02-23 19:46:19 +00:00
if (!$error) {
$result = $this->insertExtraFields();
2021-02-23 19:46:19 +00:00
if ($result < 0) {
2018-08-12 14:29:26 +00:00
$error++;
}
}
2021-02-23 19:46:19 +00:00
if (!$error && !$notrigger) {
2018-08-12 14:29:26 +00:00
// Call trigger
$result = $this->call_trigger('ORDER_MODIFY', $user);
2021-02-23 19:46:19 +00:00
if ($result < 0) {
$error++;
}
2018-08-12 14:29:26 +00:00
// End call triggers
}
// Commit or rollback
2021-02-23 19:46:19 +00:00
if ($error) {
foreach ($this->errors as $errmsg) {
dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
}
$this->db->rollback();
return -1 * $error;
2020-05-21 13:05:19 +00:00
} else {
2018-08-30 07:40:04 +00:00
$this->db->commit();
return 1;
}
}
/**
2022-10-12 13:29:30 +00:00
* Delete the sales order
2018-08-30 07:40:04 +00:00
*
* @param User $user User object
* @param int $notrigger 1=Does not execute triggers, 0= execute triggers
2023-12-06 14:46:39 +00:00
* @return int Return integer <=0 if KO, >0 if OK
2018-08-30 07:40:04 +00:00
*/
2019-02-24 23:56:48 +00:00
public function delete($user, $notrigger = 0)
2018-08-30 07:40:04 +00:00
{
global $conf, $langs;
require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
$error = 0;
dol_syslog(get_class($this)."::delete ".$this->id, LOG_DEBUG);
2018-08-30 07:40:04 +00:00
$this->db->begin();
2020-11-26 08:16:16 +00:00
if (!$notrigger) {
2018-08-30 07:40:04 +00:00
// Call trigger
$result = $this->call_trigger('ORDER_DELETE', $user);
2021-02-23 19:46:19 +00:00
if ($result < 0) {
$error++;
}
2018-08-30 07:40:04 +00:00
// End call triggers
}
2020-11-26 08:16:16 +00:00
// Test we can delete
2023-01-06 18:24:57 +00:00
if ($this->countNbOfShipments() != 0) {
2018-08-30 07:40:04 +00:00
$this->errors[] = $langs->trans('SomeShipmentExists');
$error++;
}
2023-07-24 07:42:21 +00:00
// Remove linked categories.
if (!$error) {
$sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_order";
$sql .= " WHERE fk_order = ".((int) $this->id);
$result = $this->db->query($sql);
if (!$result) {
$error++;
$this->errors[] = $this->db->lasterror();
}
}
2020-11-26 08:16:16 +00:00
// Delete extrafields of lines and lines
if (!$error && !empty($this->table_element_line)) {
$tabletodelete = $this->table_element_line;
2021-08-23 17:33:24 +00:00
$sqlef = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete."_extrafields WHERE fk_object IN (SELECT rowid FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id).")";
$sql = "DELETE FROM ".MAIN_DB_PREFIX.$tabletodelete." WHERE ".$this->fk_element." = ".((int) $this->id);
if (!$this->db->query($sqlef) || !$this->db->query($sql)) {
2018-08-30 07:40:04 +00:00
$error++;
2020-11-26 08:16:16 +00:00
$this->error = $this->db->lasterror();
$this->errors[] = $this->error;
dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
2018-08-30 07:40:04 +00:00
}
}
2020-11-26 08:16:16 +00:00
if (!$error) {
2018-08-30 07:40:04 +00:00
// Delete linked object
$res = $this->deleteObjectLinked();
2021-02-23 19:46:19 +00:00
if ($res < 0) {
$error++;
}
2018-08-30 07:40:04 +00:00
}
2020-11-26 08:16:16 +00:00
if (!$error) {
2018-08-30 07:40:04 +00:00
// Delete linked contacts
$res = $this->delete_linked_contact();
2021-02-23 19:46:19 +00:00
if ($res < 0) {
$error++;
}
2018-08-30 07:40:04 +00:00
}
2020-11-26 08:16:16 +00:00
// Removed extrafields of object
if (!$error) {
2020-04-23 11:21:39 +00:00
$result = $this->deleteExtraFields();
2020-11-26 08:16:16 +00:00
if ($result < 0) {
2020-04-23 11:21:39 +00:00
$error++;
2020-11-26 08:16:16 +00:00
dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
2018-08-30 07:40:04 +00:00
}
}
2020-11-26 08:16:16 +00:00
// Delete main record
if (!$error) {
2021-08-23 17:33:24 +00:00
$sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".((int) $this->id);
2020-11-26 08:16:16 +00:00
$res = $this->db->query($sql);
if (!$res) {
2018-08-30 07:40:04 +00:00
$error++;
2020-11-26 08:16:16 +00:00
$this->error = $this->db->lasterror();
$this->errors[] = $this->error;
dol_syslog(get_class($this)."::delete error ".$this->error, LOG_ERR);
2018-08-30 07:40:04 +00:00
}
}
2020-11-26 08:16:16 +00:00
// Delete record into ECM index and physically
if (!$error) {
2025-03-16 19:15:52 +00:00
$res = $this->deleteEcmFiles(0); // Deleting files physically is done later with the dol_delete_dir_recursive, old method.
$res = $this->deleteEcmFiles(1); // Deleting files physically is done later with the dol_delete_dir_recursive
if (!$res) {
2020-11-26 08:16:16 +00:00
$error++;
}
}
2020-11-26 08:16:16 +00:00
if (!$error) {
// We remove directory
$ref = dol_sanitizeFileName($this->ref);
if ($conf->commande->multidir_output[$this->entity] && !empty($this->ref)) {
$dir = $conf->commande->multidir_output[$this->entity]."/".$ref;
$file = $dir."/".$ref.".pdf";
if (file_exists($file)) {
2018-08-30 07:40:04 +00:00
dol_delete_preview($this);
2020-11-26 08:16:16 +00:00
if (!dol_delete_file($file, 0, 0, 0, $this)) {
$this->error = 'ErrorFailToDeleteFile';
$this->errors[] = $this->error;
2018-08-30 07:40:04 +00:00
$this->db->rollback();
return 0;
}
}
2020-11-26 08:16:16 +00:00
if (file_exists($dir)) {
$res = @dol_delete_dir_recursive($dir);
if (!$res) {
$this->error = 'ErrorFailToDeleteDir';
$this->errors[] = $this->error;
2018-08-30 07:40:04 +00:00
$this->db->rollback();
return 0;
}
}
}
}
2020-11-26 08:16:16 +00:00
if (!$error) {
dol_syslog(get_class($this)."::delete ".$this->id." by ".$user->id, LOG_DEBUG);
2018-08-30 07:40:04 +00:00
$this->db->commit();
return 1;
2020-05-21 13:05:19 +00:00
} else {
2018-08-30 07:40:04 +00:00
$this->db->rollback();
2020-11-26 08:16:16 +00:00
return -1;
2018-08-30 07:40:04 +00:00
}
}
2019-02-24 23:56:48 +00:00
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2018-08-30 07:40:04 +00:00
/**
* Load indicators for dashboard (this->nbtodo and this->nbtodolate)
*
* @param User $user Object user
2023-07-31 01:00:05 +00:00
* @param string $mode Mode ('toship', 'tobill', 'shippedtobill')
2023-12-06 14:46:39 +00:00
* @return WorkboardResponse|int Return integer <0 if KO, WorkboardResponse if OK
2018-08-30 07:40:04 +00:00
*/
public function load_board($user, $mode)
2018-08-30 07:40:04 +00:00
{
// phpcs:enable
global $conf, $langs, $hookmanager;
2018-08-30 07:40:04 +00:00
$clause = " WHERE";
$sql = "SELECT c.rowid, c.date_creation as datec, c.date_commande, c.date_livraison as delivery_date, c.fk_statut, c.total_ht";
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as c";
2025-01-13 10:58:16 +00:00
if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON c.fk_soc = sc.fk_soc";
$sql .= " WHERE sc.fk_user = ".((int) $user->id);
2018-08-30 07:40:04 +00:00
$clause = " AND";
}
$sql .= $clause." c.entity IN (".getEntity('commande').")";
2018-08-30 07:40:04 +00:00
//$sql.= " AND c.fk_statut IN (1,2,3) AND c.facture = 0";
if ($mode == 'toship') {
// An order to ship is an open order (validated or in progress)
$sql .= " AND c.fk_statut IN (" . self::STATUS_VALIDATED . "," . self::STATUS_SHIPMENTONPROCESS . ")";
}
if ($mode == 'tobill') {
// An order to bill is an order not already billed
$sql .= " AND c.fk_statut IN (" . self::STATUS_VALIDATED . "," . self::STATUS_SHIPMENTONPROCESS . ", " . self::STATUS_CLOSED . ") AND c.facture = 0";
}
if ($mode == 'shippedtobill') {
// An order shipped and to bill is a delivered order not already billed
$sql .= " AND c.fk_statut IN (" . self::STATUS_CLOSED . ") AND c.facture = 0";
}
2021-02-23 19:46:19 +00:00
if ($user->socid) {
$sql .= " AND c.fk_soc = ".((int) $user->socid);
2021-02-23 19:46:19 +00:00
}
// Add where from hooks
$parameters = array('socid' => $user->socid);
$reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
$sql .= $hookmanager->resPrint;
$resql = $this->db->query($sql);
2021-02-23 19:46:19 +00:00
if ($resql) {
$delay_warning = 0;
$label = $labelShort = $url = '';
if ($mode == 'toship') {
$delay_warning = $conf->commande->client->warning_delay / 60 / 60 / 24;
$url = DOL_URL_ROOT.'/commande/list.php?search_status=-2&mainmenu=commercial&leftmenu=orders';
$label = $langs->transnoentitiesnoconv("OrdersToProcess");
$labelShort = $langs->transnoentitiesnoconv("Opened");
}
if ($mode == 'tobill') {
$url = DOL_URL_ROOT.'/commande/list.php?search_status=-3&search_billed=0&mainmenu=commercial&leftmenu=orders';
$label = $langs->trans("OrdersToBill"); // We set here bill but may be billed or ordered
$labelShort = $langs->trans("ToBill");
}
if ($mode == 'shippedtobill') {
$url = DOL_URL_ROOT.'/commande/list.php?search_status=3&search_billed=0&mainmenu=commercial&leftmenu=orders';
$label = $langs->trans("OrdersToBill"); // We set here bill but may be billed or ordered
$labelShort = $langs->trans("StatusOrderDelivered").' '.$langs->trans("and").' '.$langs->trans("ToBill");
}
2018-08-30 07:40:04 +00:00
$response = new WorkboardResponse();
2024-09-16 09:56:23 +00:00
$response->warning_delay = $delay_warning;
$response->label = $label;
$response->labelShort = $labelShort;
$response->url = $url;
2024-09-16 09:56:23 +00:00
$response->url_late = DOL_URL_ROOT.'/commande/list.php?search_option=late&mainmenu=commercial&leftmenu=orders';
$response->img = img_object('', "order");
2018-08-30 07:40:04 +00:00
$generic_commande = new Commande($this->db);
2021-02-23 19:46:19 +00:00
while ($obj = $this->db->fetch_object($resql)) {
2018-08-30 07:40:04 +00:00
$response->nbtodo++;
$response->total += $obj->total_ht;
2018-08-30 07:40:04 +00:00
$generic_commande->status = $obj->fk_statut;
2020-11-11 17:08:40 +00:00
$generic_commande->date = $this->db->jdate($obj->date_commande);
$generic_commande->delivery_date = $this->db->jdate($obj->delivery_date);
2018-08-30 07:40:04 +00:00
if ($mode == 'toship' && $generic_commande->hasDelay()) {
2018-08-30 07:40:04 +00:00
$response->nbtodolate++;
}
}
return $response;
2020-05-21 13:05:19 +00:00
} else {
$this->error = $this->db->error();
2018-08-30 07:40:04 +00:00
return -1;
}
}
/**
* Return source label of order
*
* @return string Label
*/
2019-02-24 23:56:48 +00:00
public function getLabelSource()
2018-08-30 07:40:04 +00:00
{
global $langs;
$label = $langs->trans('OrderSource'.$this->source);
2018-08-30 07:40:04 +00:00
2021-02-23 19:46:19 +00:00
if ($label == 'OrderSource') {
return '';
}
2018-08-30 07:40:04 +00:00
return $label;
}
/**
* Return status label of Order
*
* @param int $mode 0=Long label, 1=Short label, 2=Picto + Short label, 3=Picto, 4=Picto + Long label, 5=Short label + Picto, 6=Long label + Picto
* @return string Label of status
*/
2019-02-24 23:56:48 +00:00
public function getLibStatut($mode)
2018-08-30 07:40:04 +00:00
{
return $this->LibStatut($this->status, $this->billed, $mode);
2018-08-30 07:40:04 +00:00
}
2019-02-24 23:56:48 +00:00
// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2018-08-30 07:40:04 +00:00
/**
* Return label of status
*
2019-11-01 22:58:14 +00:00
* @param int $status Id status
2018-08-30 07:40:04 +00:00
* @param int $billed If invoiced
* @param int $mode 0=Long label, 1=Short label, 2=Picto + Short label, 3=Picto, 4=Picto + Long label, 5=Short label + Picto, 6=Long label + Picto
* @param int $donotshowbilled Do not show billed status after order status
* @return string Label of status
*/
2019-11-01 22:58:14 +00:00
public function LibStatut($status, $billed, $mode, $donotshowbilled = 0)
2018-08-30 07:40:04 +00:00
{
// phpcs:enable
2023-10-15 20:08:09 +00:00
global $langs, $hookmanager;
2018-08-30 07:40:04 +00:00
$billedtext = '';
2021-02-23 19:46:19 +00:00
if (empty($donotshowbilled)) {
$billedtext .= ($billed ? ' - '.$langs->transnoentitiesnoconv("Billed") : '');
}
2025-09-04 15:54:29 +00:00
$billedtextlong = ($billed ? ' - '.$langs->transnoentitiesnoconv("Billed") : '');
2018-08-30 07:40:04 +00:00
2020-10-08 07:30:15 +00:00
$labelTooltip = '';
$paramsBadge = array('badgeParams' => array('attr' => array(
'data-status-element' => $this->element,
'data-billed' => (int) $billed,
'data-status' => (int) $status
)));
if ($status == self::STATUS_CANCELED) {
2020-10-25 13:11:09 +00:00
$labelStatus = $langs->transnoentitiesnoconv('StatusOrderCanceled');
$labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderCanceledShort');
$statusType = 'status9';
2020-05-21 13:05:19 +00:00
} elseif ($status == self::STATUS_DRAFT) {
2020-10-25 13:11:09 +00:00
$labelStatus = $langs->transnoentitiesnoconv('StatusOrderDraft');
$labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderDraftShort');
$statusType = 'status0';
2020-05-21 13:05:19 +00:00
} elseif ($status == self::STATUS_VALIDATED) {
2025-09-04 15:54:29 +00:00
$labelStatus = $langs->transnoentitiesnoconv('StatusOrderValidated').$billedtextlong;
2020-10-25 13:11:09 +00:00
$labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderValidatedShort').$billedtext;
$statusType = 'status1';
2020-05-21 13:05:19 +00:00
} elseif ($status == self::STATUS_SHIPMENTONPROCESS) {
2025-09-04 15:54:29 +00:00
$labelStatus = $langs->transnoentitiesnoconv('StatusOrderSent').$billedtextlong;
2020-10-25 13:11:09 +00:00
$labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderSentShort').$billedtext;
2022-09-12 22:11:00 +00:00
$labelTooltip = $langs->transnoentitiesnoconv("StatusOrderSent");
if (!empty($this->delivery_date)) {
$labelTooltip .= ' - '.$langs->transnoentitiesnoconv("DateDeliveryPlanned").dol_print_date($this->delivery_date, 'day').$billedtext;
}
$statusType = 'status4';
2023-09-19 18:29:24 +00:00
} elseif ($status == self::STATUS_CLOSED) {
2025-09-04 15:54:29 +00:00
$labelStatus = $langs->transnoentitiesnoconv('StatusOrderDelivered').$billedtextlong;
2023-09-19 19:16:32 +00:00
$labelStatusShort = $langs->transnoentitiesnoconv('StatusOrderDeliveredShort').$billedtext;
$statusType = 'status6';
2020-05-21 13:05:19 +00:00
} else {
2020-10-25 13:11:09 +00:00
$labelStatus = $langs->transnoentitiesnoconv('Unknown');
$labelStatusShort = '';
$statusType = '';
$mode = 0;
}
2019-03-15 14:07:52 +00:00
$paramsBadge['tooltip'] = $labelTooltip;
2022-11-08 15:24:05 +00:00
$parameters = array(
'status' => $status,
'mode' => $mode,
'billed' => $billed,
'donotshowbilled' => $donotshowbilled,
'paramsBadge' => & $paramsBadge
2022-11-08 15:24:05 +00:00
);
2022-11-08 15:24:05 +00:00
$reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
2022-11-08 15:24:05 +00:00
if ($reshook > 0) {
return $hookmanager->resPrint;
}
2022-11-08 15:24:05 +00:00
return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode, '', $paramsBadge);
}
2023-01-30 22:24:23 +00:00
/**
* getTooltipContentArray
2023-08-15 12:44:03 +00:00
*
* @param array<string,mixed> $params params to construct tooltip data
* @since v18
* @return array{picto?:string,ref?:string,refsupplier?:string,label?:string,date?:string,date_echeance?:string,amountht?:string,total_ht?:string,totaltva?:string,amountlt1?:string,amountlt2?:string,amountrevenustamp?:string,totalttc?:string}|array{optimize:string}
2023-01-30 22:24:23 +00:00
*/
2023-01-31 21:43:54 +00:00
public function getTooltipContentArray($params)
2023-01-30 22:24:23 +00:00
{
global $conf, $langs, $user;
$langs->load('orders');
2023-01-30 22:24:23 +00:00
$datas = [];
$nofetch = !empty($params['nofetch']);
2023-01-30 22:24:23 +00:00
2023-11-27 10:39:32 +00:00
if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2023-01-30 22:24:23 +00:00
return ['optimize' => $langs->trans("Order")];
}
2023-01-31 21:43:54 +00:00
if ($user->hasRight('commande', 'lire')) {
$datas['picto'] = img_picto('', $this->picto, '', 0, 0, 0, '', 'paddingrightonly').'<u>'.$langs->trans("Order").'</u>';
if (isset($this->status)) {
2023-05-31 11:53:41 +00:00
$datas['status'] = ' '.$this->getLibStatut(5);
2023-01-30 22:24:23 +00:00
}
$datas['Ref'] = '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
if (!$nofetch) {
$langs->load('companies');
if (empty($this->thirdparty)) {
$this->fetch_thirdparty();
}
2023-03-14 21:27:35 +00:00
$datas['customer'] = '<br><b>'.$langs->trans('Customer').':</b> '.$this->thirdparty->getNomUrl(1, '', 0, 1);
}
$datas['RefCustomer'] = '<br><b>'.$langs->trans('RefCustomer').':</b> '.(empty($this->ref_customer) ? (empty($this->ref_client) ? '' : $this->ref_client) : $this->ref_customer);
2023-03-14 21:27:35 +00:00
if (!$nofetch) {
$langs->load('project');
2024-06-15 14:03:53 +00:00
if (is_null($this->project) || (is_object($this->project) && $this->project->isEmpty())) {
$res = $this->fetchProject();
2024-06-15 14:03:53 +00:00
if ($res > 0 && $this->project instanceof Project) {
2025-02-10 20:20:09 +00:00
$datas['project'] = '<br><b>'.$langs->trans('Project').':</b> '.$this->project->getNomUrl(1, '', 0, '1');
2023-03-14 21:27:35 +00:00
}
}
}
2023-01-30 22:24:23 +00:00
if (!empty($this->total_ht)) {
$datas['AmountHT'] = '<br><b>'.$langs->trans('AmountHT').':</b> '.price($this->total_ht, 0, $langs, 0, -1, -1, $conf->currency);
}
if (!empty($this->total_tva)) {
$datas['VAT'] = '<br><b>'.$langs->trans('VAT').':</b> '.price($this->total_tva, 0, $langs, 0, -1, -1, $conf->currency);
}
if (!empty($this->total_ttc)) {
$datas['AmountTTC'] = '<br><b>'.$langs->trans('AmountTTC').':</b> '.price($this->total_ttc, 0, $langs, 0, -1, -1, $conf->currency);
}
if (!empty($this->date)) {
$datas['Date'] = '<br><b>'.$langs->trans('Date').':</b> '.dol_print_date($this->date, 'day');
}
if (!empty($this->delivery_date)) {
$datas['DeliveryDate'] = '<br><b>'.$langs->trans('DeliveryDate').':</b> '.dol_print_date($this->delivery_date, 'dayhour');
}
}
return $datas;
}
2011-09-12 17:08:02 +00:00
2018-08-30 07:40:04 +00:00
/**
* Return clickable link of object (with eventually picto)
2018-08-30 07:40:04 +00:00
*
* @param int $withpicto Add picto into link
* @param string $option Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
* @param int $max Max length to show
* @param int $short ???
* @param int $notooltip 1=Disable tooltip
* @param int $save_lastsearch_value -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
2020-02-16 11:05:00 +00:00
* @param int $addlinktonotes Add link to notes
2022-02-04 14:01:32 +00:00
* @param string $target attribute target for link
2018-08-30 07:40:04 +00:00
* @return string String with URL
*/
public function getNomUrl($withpicto = 0, $option = '', $max = 0, $short = 0, $notooltip = 0, $save_lastsearch_value = -1, $addlinktonotes = 0, $target = '')
2018-08-30 07:40:04 +00:00
{
2022-02-15 17:14:44 +00:00
global $conf, $langs, $user, $hookmanager;
2018-08-30 07:40:04 +00:00
2021-02-23 19:46:19 +00:00
if (!empty($conf->dol_no_mouse_hover)) {
$notooltip = 1; // Force disable tooltips
}
2011-09-12 17:08:02 +00:00
$result = '';
2017-06-09 07:25:15 +00:00
if (isModEnabled("shipping") && ($option == '1' || $option == '2')) {
add dolBuildUrl with examples of use (#35549) * add dol_buildurl with examples of use * add dol_buildurl with examples of use * add dol_buildurl with examples of use * add dol_buildurl with examples of use * wip * wip * add more example * add more example * add more example * add more example * add more example * add more example * add more example * add example * add example * add example * add example * add example * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * clean code * clean code * clean code * clean code * clean code * clean code * clean code * add example * add example * add example * add example * add example * add example add example add example add example add example * add example * add example add example add example add example * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * clean code * clean code * clean code * clean code * clean code * clean code * clean code * clean code * clean code * clean code * fix * wip * wip * clean code
2025-10-06 16:17:02 +00:00
$baseurl = DOL_URL_ROOT . '/expedition/shipment.php';
2021-02-23 19:46:19 +00:00
} else {
add dolBuildUrl with examples of use (#35549) * add dol_buildurl with examples of use * add dol_buildurl with examples of use * add dol_buildurl with examples of use * add dol_buildurl with examples of use * wip * wip * add more example * add more example * add more example * add more example * add more example * add more example * add more example * add example * add example * add example * add example * add example * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * clean code * clean code * clean code * clean code * clean code * clean code * clean code * add example * add example * add example * add example * add example * add example add example add example add example add example * add example * add example add example add example add example * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * clean code * clean code * clean code * clean code * clean code * clean code * clean code * clean code * clean code * clean code * fix * wip * wip * clean code
2025-10-06 16:17:02 +00:00
$baseurl = DOL_URL_ROOT . '/commande/card.php';
2021-02-23 19:46:19 +00:00
}
add dolBuildUrl with examples of use (#35549) * add dol_buildurl with examples of use * add dol_buildurl with examples of use * add dol_buildurl with examples of use * add dol_buildurl with examples of use * wip * wip * add more example * add more example * add more example * add more example * add more example * add more example * add more example * add example * add example * add example * add example * add example * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * clean code * clean code * clean code * clean code * clean code * clean code * clean code * add example * add example * add example * add example * add example * add example add example add example add example add example * add example * add example add example add example add example * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * clean code * clean code * clean code * clean code * clean code * clean code * clean code * clean code * clean code * clean code * fix * wip * wip * clean code
2025-10-06 16:17:02 +00:00
$query = ['id' => $this->id];
2023-10-15 13:32:35 +00:00
if (!$user->hasRight('commande', 'lire')) {
2018-08-30 07:40:04 +00:00
$option = 'nolink';
2021-02-23 19:46:19 +00:00
}
2012-03-18 18:23:01 +00:00
2021-02-23 19:46:19 +00:00
if ($option !== 'nolink') {
// Add param to save lastsearch_values or not
$add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
2023-09-10 15:41:22 +00:00
if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
2021-02-23 19:46:19 +00:00
$add_save_lastsearch_values = 1;
}
if ($add_save_lastsearch_values) {
add dolBuildUrl with examples of use (#35549) * add dol_buildurl with examples of use * add dol_buildurl with examples of use * add dol_buildurl with examples of use * add dol_buildurl with examples of use * wip * wip * add more example * add more example * add more example * add more example * add more example * add more example * add more example * add example * add example * add example * add example * add example * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * clean code * clean code * clean code * clean code * clean code * clean code * clean code * add example * add example * add example * add example * add example * add example add example add example add example add example * add example * add example add example add example add example * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * clean code * clean code * clean code * clean code * clean code * clean code * clean code * clean code * clean code * clean code * fix * wip * wip * clean code
2025-10-06 16:17:02 +00:00
$query = array_merge($query, ['save_lastsearch_values' => 1]);
2021-02-23 19:46:19 +00:00
}
}
add dolBuildUrl with examples of use (#35549) * add dol_buildurl with examples of use * add dol_buildurl with examples of use * add dol_buildurl with examples of use * add dol_buildurl with examples of use * wip * wip * add more example * add more example * add more example * add more example * add more example * add more example * add more example * add example * add example * add example * add example * add example * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * stay in the same area even we are on other area in other tab * clean code * clean code * clean code * clean code * clean code * clean code * clean code * add example * add example * add example * add example * add example * add example add example add example add example add example * add example * add example add example add example add example * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * fix * clean code * clean code * clean code * clean code * clean code * clean code * clean code * clean code * clean code * clean code * fix * wip * wip * clean code
2025-10-06 16:17:02 +00:00
$url = dolBuildUrl($baseurl, $query);
2021-02-23 19:46:19 +00:00
if ($short) {
return $url;
}
2023-02-06 21:21:54 +00:00
$params = [
'id' => $this->id,
'objecttype' => $this->element,
'option' => $option,
'nofetch' => 1,
2023-02-06 21:21:54 +00:00
];
2023-02-05 19:41:29 +00:00
$classfortooltip = 'classfortooltip';
$dataparams = '';
if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
$classfortooltip = 'classforajaxtooltip';
2023-04-03 17:51:40 +00:00
$dataparams = ' data-params="'.dol_escape_htmltag(json_encode($params)).'"';
$label = '';
} else {
$label = implode($this->getTooltipContentArray($params));
2023-02-05 19:41:29 +00:00
}
2023-02-06 21:21:54 +00:00
$linkclose = '';
2023-02-14 21:56:12 +00:00
if (empty($notooltip) && $user->hasRight('commande', 'lire')) {
2023-11-27 10:39:32 +00:00
if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
$label = $langs->trans("Order");
2025-01-09 01:01:49 +00:00
$linkclose .= ' alt="'.dolPrintHTMLForAttribute($label).'"';
2018-08-30 07:40:04 +00:00
}
2025-01-09 01:01:49 +00:00
$linkclose .= ($label ? ' title="'.dolPrintHTMLForAttribute($label).'"' : ' title="tocomplete"');
2023-02-05 19:41:29 +00:00
$linkclose .= $dataparams.' class="'.$classfortooltip.'"';
2022-02-04 13:42:07 +00:00
2023-01-30 22:24:23 +00:00
$target_value = array('_self', '_blank', '_parent', '_top');
2022-02-04 13:42:07 +00:00
if (in_array($target, $target_value)) {
2022-03-07 16:10:28 +00:00
$linkclose .= ' target="'.dol_escape_htmltag($target).'"';
2022-02-04 13:38:34 +00:00
}
}
2018-08-30 07:40:04 +00:00
$linkstart = '<a href="'.$url.'"';
$linkstart .= $linkclose.'>';
$linkend = '</a>';
2018-08-30 07:40:04 +00:00
if ($option === 'nolink') {
$linkstart = '';
$linkend = '';
}
2018-08-30 07:40:04 +00:00
$result .= $linkstart;
2021-02-23 19:46:19 +00:00
if ($withpicto) {
2023-04-27 13:55:21 +00:00
$result .= img_object(($notooltip ? '' : $label), $this->picto, (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
2021-02-23 19:46:19 +00:00
}
if ($withpicto != 2) {
$result .= $this->ref;
}
$result .= $linkend;
2018-08-30 07:40:04 +00:00
2021-02-23 19:46:19 +00:00
if ($addlinktonotes) {
2020-01-15 18:49:45 +00:00
$txttoshow = ($user->socid > 0 ? $this->note_public : $this->note_private);
2021-02-23 19:46:19 +00:00
if ($txttoshow) {
2020-01-15 18:49:45 +00:00
$notetoshow = $langs->trans("ViewPrivateNote").':<br>'.dol_string_nohtmltag($txttoshow, 1);
$result .= ' <span class="note inline-block">';
$result .= '<a href="'.DOL_URL_ROOT.'/commande/note.php?id='.$this->id.'" class="classfortooltip" title="'.dol_escape_htmltag($notetoshow).'">';
$result .= img_picto('', 'note');
$result .= '</a>';
//$result.=img_picto($langs->trans("ViewNote"),'object_generic');
//$result.='</a>';
$result .= '</span>';
}
}
2022-02-15 09:51:06 +00:00
global $action;
2022-02-15 10:02:22 +00:00
$hookmanager->initHooks(array($this->element . 'dao'));
$parameters = array('id' => $this->id, 'getnomurl' => &$result);
2022-02-15 09:51:06 +00:00
$reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
if ($reshook > 0) {
$result = $hookmanager->resPrint;
} else {
$result .= $hookmanager->resPrint;
}
return $result;
2018-08-30 07:40:04 +00:00
}
/**
* Charge les information d'ordre info dans l'objet commande
2018-08-30 07:40:04 +00:00
*
* @param int $id Id of order
* @return void
*/
2019-02-24 23:56:48 +00:00
public function info($id)
2018-08-30 07:40:04 +00:00
{
$sql = 'SELECT c.rowid, date_creation as datec, tms as datem,';
$sql .= ' date_valid as datev,';
$sql .= ' date_cloture as datecloture,';
$sql .= ' fk_user_author, fk_user_valid, fk_user_cloture';
$sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as c';
2021-03-14 10:48:39 +00:00
$sql .= ' WHERE c.rowid = '.((int) $id);
$result = $this->db->query($sql);
2021-02-23 19:46:19 +00:00
if ($result) {
if ($this->db->num_rows($result)) {
2018-08-30 07:40:04 +00:00
$obj = $this->db->fetch_object($result);
$this->id = $obj->rowid;
2021-02-23 19:46:19 +00:00
if ($obj->fk_user_author) {
2022-07-03 10:54:58 +00:00
$this->user_creation_id = $obj->fk_user_author;
2018-08-30 07:40:04 +00:00
}
2021-02-23 19:46:19 +00:00
if ($obj->fk_user_valid) {
2022-07-03 10:54:58 +00:00
$this->user_validation_id = $obj->fk_user_valid;
2018-08-30 07:40:04 +00:00
}
2021-02-23 19:46:19 +00:00
if ($obj->fk_user_cloture) {
2022-07-03 10:54:58 +00:00
$this->user_closing_id = $obj->fk_user_cloture;
2018-08-30 07:40:04 +00:00
}
$this->date_creation = $this->db->jdate($obj->datec);
$this->date_modification = $this->db->jdate($obj->datem);
$this->date_validation = $this->db->jdate($obj->datev);
$this->date_cloture = $this->db->jdate($obj->datecloture);
}
$this->db->free($result);
2020-05-21 13:05:19 +00:00
} else {
2018-08-30 07:40:04 +00:00
dol_print_error($this->db);
}
}
/**
* Initialise an instance with random values.
* Used to build previews or test instances.
* id must be 0 if object instance is a specimen.
*
2025-10-17 17:39:45 +00:00
* @param array<string|mixed> $param Array of options
* @return int
2018-08-30 07:40:04 +00:00
*/
2025-10-17 17:39:45 +00:00
public function initAsSpecimen($param = array())
2018-08-30 07:40:04 +00:00
{
2020-01-20 23:16:11 +00:00
global $conf, $langs;
2018-08-30 07:40:04 +00:00
dol_syslog(get_class($this)."::initAsSpecimen");
// Load array of products prodids
$num_prods = 0;
$prodids = array();
$sql = "SELECT rowid";
$sql .= " FROM ".MAIN_DB_PREFIX."product";
$sql .= " WHERE entity IN (".getEntity('product').")";
2025-10-17 17:39:45 +00:00
if (array_key_exists('tosell', $param)) {
$sql .= " AND tosell = ".((int) $param['tosell']);
}
$sql .= $this->db->plimit(100);
2018-08-30 07:40:04 +00:00
$resql = $this->db->query($sql);
2021-02-23 19:46:19 +00:00
if ($resql) {
2018-08-30 07:40:04 +00:00
$num_prods = $this->db->num_rows($resql);
$i = 0;
2021-02-23 19:46:19 +00:00
while ($i < $num_prods) {
2018-08-30 07:40:04 +00:00
$i++;
$row = $this->db->fetch_row($resql);
$prodids[$i] = $row[0];
}
}
// Initialise parameters
$this->id = 0;
2018-08-30 07:40:04 +00:00
$this->ref = 'SPECIMEN';
$this->specimen = 1;
2023-03-08 20:39:05 +00:00
$this->entity = $conf->entity;
2018-08-30 07:40:04 +00:00
$this->socid = 1;
$this->date = time();
$this->date_lim_reglement = $this->date + 3600 * 24 * 30;
2018-08-30 07:40:04 +00:00
$this->cond_reglement_code = 'RECEP';
$this->mode_reglement_code = 'CHQ';
$this->availability_code = 'DSP';
$this->demand_reason_code = 'SRC_00';
2020-01-20 23:16:11 +00:00
$this->note_public = 'This is a comment (public)';
$this->note_private = 'This is a comment (private)';
2020-01-20 23:16:11 +00:00
$this->multicurrency_tx = 1;
$this->multicurrency_code = $conf->currency;
$this->status = $this::STATUS_DRAFT;
2018-08-30 07:40:04 +00:00
// Lines
$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)
2018-08-30 07:40:04 +00:00
$xnbp = 0;
2021-02-23 19:46:19 +00:00
while ($xnbp < $nbp) {
$line = new OrderLine($this->db);
2018-08-30 07:40:04 +00:00
$line->desc = $langs->trans("Description")." ".$xnbp;
$line->qty = 1;
$line->subprice = 100;
$line->price = 100;
$line->tva_tx = 20;
2021-02-23 19:46:19 +00:00
if ($xnbp == 2) {
$line->total_ht = 50;
$line->total_ttc = 60;
$line->total_tva = 10;
$line->remise_percent = 50;
2020-05-21 13:05:19 +00:00
} else {
$line->total_ht = 100;
$line->total_ttc = 120;
$line->total_tva = 20;
$line->remise_percent = 0;
2018-08-30 07:40:04 +00:00
}
2021-02-23 19:46:19 +00:00
if ($num_prods > 0) {
2018-08-30 07:40:04 +00:00
$prodid = mt_rand(1, $num_prods);
$line->fk_product = $prodids[$prodid];
$line->product_ref = 'SPECIMEN';
2018-08-30 07:40:04 +00:00
}
$this->lines[$xnbp] = $line;
2011-09-12 17:08:02 +00:00
$this->total_ht += $line->total_ht;
$this->total_tva += $line->total_tva;
$this->total_ttc += $line->total_ttc;
2011-09-12 17:08:02 +00:00
2018-08-30 07:40:04 +00:00
$xnbp++;
}
return 1;
2018-08-30 07:40:04 +00:00
}
2011-09-12 17:08:02 +00:00
2018-08-30 07:40:04 +00:00
/**
* Load the indicators this->nb for the state board
2018-08-30 07:40:04 +00:00
*
* @return int Return integer <0 if KO, >0 if OK
2018-08-30 07:40:04 +00:00
*/
2024-01-18 17:55:53 +00:00
public function loadStateBoard()
2018-08-30 07:40:04 +00:00
{
global $user, $hookmanager;
2018-08-30 07:40:04 +00:00
$this->nb = array();
2018-08-30 07:40:04 +00:00
$clause = "WHERE";
$sql = "SELECT count(c.rowid) as nb";
$sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." as c";
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON c.fk_soc = s.rowid";
2025-01-13 10:58:16 +00:00
if (empty($user->socid) && !$user->hasRight('societe', 'client', 'voir')) {
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
$sql .= " WHERE sc.fk_user = ".((int) $user->id);
2018-08-30 07:40:04 +00:00
$clause = "AND";
}
$sql .= " ".$clause." c.entity IN (".getEntity('commande').")";
// Add where from hooks
$parameters = array();
$hookmanager->executeHooks('printFieldListWhere', $parameters, $this); // Note that $action and $object may have been modified by hook
$sql .= $hookmanager->resPrint;
$resql = $this->db->query($sql);
2021-02-23 19:46:19 +00:00
if ($resql) {
while ($obj = $this->db->fetch_object($resql)) {
$this->nb["orders"] = $obj->nb;
2018-08-30 07:40:04 +00:00
}
$this->db->free($resql);
return 1;
2020-05-21 13:05:19 +00:00
} else {
2018-08-30 07:40:04 +00:00
dol_print_error($this->db);
$this->error = $this->db->error();
2018-08-30 07:40:04 +00:00
return -1;
}
}
/**
* Create an array of order lines
*
* @return int >0 if OK, <0 if KO
2018-08-30 07:40:04 +00:00
*/
2019-02-24 23:56:48 +00:00
public function getLinesArray()
2018-08-30 07:40:04 +00:00
{
return $this->fetch_lines();
}
/**
* Create a document onto disk according to template module.
*
* @param string $modele Force template to use ('' to not force)
* @param Translate $outputlangs object lang a utiliser pour traduction
* @param int<0,1> $hidedetails Hide details of lines
* @param int<0,1> $hidedesc Hide description
* @param int<0,1> $hideref Hide ref
* @param array<string,mixed> $moreparams Array to provide more information
* @return int<0,1> 0 if KO, 1 if OK
*/
public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
{
global $langs;
$langs->load("orders");
$outputlangs->load("products");
if (!dol_strlen($modele)) {
$modele = 'einstein';
2020-12-13 12:34:21 +00:00
if (!empty($this->model_pdf)) {
$modele = $this->model_pdf;
2023-11-27 10:39:32 +00:00
} elseif (getDolGlobalString('COMMANDE_ADDON_PDF')) {
2024-01-05 03:18:53 +00:00
$modele = getDolGlobalString('COMMANDE_ADDON_PDF');
}
}
2014-09-21 16:16:14 +00:00
$modelpath = "core/modules/commande/doc/";
return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
}
/**
* Function used to replace a thirdparty id with another one.
*
2022-12-28 12:27:13 +00:00
* @param DoliDB $dbs Database handler, because function is static we name it $dbs not $db to avoid breaking coding test
* @param int $origin_id Old thirdparty id
* @param int $dest_id New thirdparty id
* @return bool
*/
2022-12-28 12:27:13 +00:00
public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
{
$tables = array(
2018-08-30 08:30:52 +00:00
'commande'
);
2022-12-28 12:27:13 +00:00
return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
}
2015-09-05 08:42:54 +00:00
2021-11-23 20:38:29 +00:00
/**
* Function used to replace a product id with another one.
*
* @param DoliDB $db Database handler
* @param int $origin_id Old product id
* @param int $dest_id New product id
* @return bool
*/
public static function replaceProduct(DoliDB $db, $origin_id, $dest_id)
{
$tables = array(
'commandedet',
);
return CommonObject::commonReplaceProduct($db, $origin_id, $dest_id, $tables);
}
2018-08-30 07:40:04 +00:00
/**
2022-10-12 13:29:30 +00:00
* Is the sales order delayed?
2018-08-30 07:40:04 +00:00
*
* @return bool true if late, false if not
*/
public function hasDelay()
{
global $conf;
if (!($this->status > Commande::STATUS_DRAFT && $this->status < Commande::STATUS_CLOSED)) {
return false; // Never late if not inside this status range
2018-08-30 07:40:04 +00:00
}
$now = dol_now();
return max($this->date, $this->delivery_date) < ($now - $conf->commande->client->warning_delay);
2018-08-30 07:40:04 +00:00
}
/**
* Show the customer delayed info
*
* @return string Show delayed information
*/
public function showDelay()
{
global $conf, $langs;
if (empty($this->delivery_date)) {
$text = $langs->trans("OrderDate").' '.dol_print_date($this->date, 'day');
2021-02-23 19:46:19 +00:00
} else {
$text = $text = $langs->trans("DeliveryDate").' '.dol_print_date($this->delivery_date, 'day');
2021-02-23 19:46:19 +00:00
}
$text .= ' '.($conf->commande->client->warning_delay > 0 ? '+' : '-').' '.round(abs($conf->commande->client->warning_delay) / 3600 / 24, 1).' '.$langs->trans("days").' < '.$langs->trans("Today");
2018-08-30 07:40:04 +00:00
return $text;
}
/**
* Set signed status
*
* @param User $user Object user that modify
* @param int $status Newsigned status to set (often a constant like self::STATUS_XXX)
* @param int<0,1> $notrigger 1 = Does not execute triggers, 0 = Execute triggers
* @param string $triggercode Trigger code to use
* @return int 0 < if KO, > 0 if OK
*/
public function setSignedStatus(User $user, int $status = 0, int $notrigger = 0, $triggercode = ''): int
{
return $this->setSignedStatusCommon($user, $status, $notrigger, $triggercode);
}
NEW : adds visual indicators on the Sales Order card (commande/card.php) (#37460) * This PR adds visual indicators on the Sales Order card (commande/card.php) to quickly identify if an order or its lines are shippable based on current stock levels. This feature mirrors the existing functionality found in the Orders List, bringing valuable logistics information directly into the Order Card context. Key Features: Global Shippable Status: Adds a "shippable" icon (Truck) next to the "Planned delivery date" field. Logic: Checks if all products in the order are in stock. Condition: Visible only if Stock and Shipment modules are enabled, a delivery date is set, and the order is validated. Visual: Green icon if fully shippable, Red/Warning if stock is insufficient. Includes a detailed tooltip. Per-Line Shippable Status: Adds a new "Shippable" column in the product lines table. Logic: Compares real stock vs. remaining quantity to ship for each line. Visual: Displays a green/red status bullet (standard Dolibarr status icons) directly in the line. Implementation: Done by updating objectline_title.tpl.php and objectline_view.tpl.php with specific checks for the commande element. New Configuration Option: Added a hidden option ORDER_DISABLE_SHIPPABLE_ICON_ON_CARD to disable this feature if needed (configurable in Home > Setup > Other Setup or via module settings). Backend Optimization: Added a new method getShippableInfos() in commande.class.php to centralize the stock check logic (optimized with stock caching to avoid N+1 query issues). Technical Details: Modified Files: commande/card.php: Integration of the global icon next to the date. commande/class/commande.class.php: Added getShippableInfos() method. core/tpl/objectline_title.tpl.php: Added "Shippable" column header (conditional for Orders). core/tpl/objectline_view.tpl.php: Added "Shippable" column cell content (conditional for Orders). Performance: Logic uses static caching for product stock to minimize database impact when rendering large orders. * missed element * img_picto * phan add * load_stock and global $i * phan * ci retour * change icon change phan directive * phan * Fight against optionflation Signed-off-by: Laurent Destailleur <eldy@destailleur.fr> * Change condition for displaying shippable icon Signed-off-by: Laurent Destailleur <eldy@destailleur.fr> * Change condition for displaying shippable status icon Signed-off-by: Laurent Destailleur <eldy@destailleur.fr> * Update card.php Signed-off-by: Laurent Destailleur <eldy@destailleur.fr> * Update shippable status condition in template Signed-off-by: Laurent Destailleur <eldy@destailleur.fr> * Update shippable status condition logic Signed-off-by: Laurent Destailleur <eldy@destailleur.fr> --------- Signed-off-by: Laurent Destailleur <eldy@destailleur.fr> Co-authored-by: jpb <jean-pascal.boudet@atm-consulting> Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-12 13:42:31 +00:00
/**
* Compute shippable status and tooltip/icon for the order.
*
* @param array<mixed> $options Extra options (reserved for future use)
* @return array<string,mixed> Array with keys: has_product, shippable, texticon, textinfo, warning
* /
*/
public function getShippableInfos(array $options = array()): array
NEW : adds visual indicators on the Sales Order card (commande/card.php) (#37460) * This PR adds visual indicators on the Sales Order card (commande/card.php) to quickly identify if an order or its lines are shippable based on current stock levels. This feature mirrors the existing functionality found in the Orders List, bringing valuable logistics information directly into the Order Card context. Key Features: Global Shippable Status: Adds a "shippable" icon (Truck) next to the "Planned delivery date" field. Logic: Checks if all products in the order are in stock. Condition: Visible only if Stock and Shipment modules are enabled, a delivery date is set, and the order is validated. Visual: Green icon if fully shippable, Red/Warning if stock is insufficient. Includes a detailed tooltip. Per-Line Shippable Status: Adds a new "Shippable" column in the product lines table. Logic: Compares real stock vs. remaining quantity to ship for each line. Visual: Displays a green/red status bullet (standard Dolibarr status icons) directly in the line. Implementation: Done by updating objectline_title.tpl.php and objectline_view.tpl.php with specific checks for the commande element. New Configuration Option: Added a hidden option ORDER_DISABLE_SHIPPABLE_ICON_ON_CARD to disable this feature if needed (configurable in Home > Setup > Other Setup or via module settings). Backend Optimization: Added a new method getShippableInfos() in commande.class.php to centralize the stock check logic (optimized with stock caching to avoid N+1 query issues). Technical Details: Modified Files: commande/card.php: Integration of the global icon next to the date. commande/class/commande.class.php: Added getShippableInfos() method. core/tpl/objectline_title.tpl.php: Added "Shippable" column header (conditional for Orders). core/tpl/objectline_view.tpl.php: Added "Shippable" column cell content (conditional for Orders). Performance: Logic uses static caching for product stock to minimize database impact when rendering large orders. * missed element * img_picto * phan add * load_stock and global $i * phan * ci retour * change icon change phan directive * phan * Fight against optionflation Signed-off-by: Laurent Destailleur <eldy@destailleur.fr> * Change condition for displaying shippable icon Signed-off-by: Laurent Destailleur <eldy@destailleur.fr> * Change condition for displaying shippable status icon Signed-off-by: Laurent Destailleur <eldy@destailleur.fr> * Update card.php Signed-off-by: Laurent Destailleur <eldy@destailleur.fr> * Update shippable status condition in template Signed-off-by: Laurent Destailleur <eldy@destailleur.fr> * Update shippable status condition logic Signed-off-by: Laurent Destailleur <eldy@destailleur.fr> --------- Signed-off-by: Laurent Destailleur <eldy@destailleur.fr> Co-authored-by: jpb <jean-pascal.boudet@atm-consulting> Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
2026-03-12 13:42:31 +00:00
{
global $conf, $langs;
$langs->loadLangs(array('orders', 'sendings', 'stocks', 'products'));
$result = array(
'has_product' => false,
'shippable' => false,
'texticon' => '',
'textinfo' => '',
'warning' => false,
);
// Requested naming for statuses
if ($this->status == self::STATUS_DRAFT || $this->status == self::STATUS_CLOSED) {
return $result;
}
$genericCommande = $this;
$genericProduct = new Product($this->db);
$productstatcache = array();
$productstatcachevirtual = array();
$genericCommande->getLinesArray(); // Load array ->lines
$genericCommande->loadExpeditions(); // Load array ->expeditions
$notshippable = 0;
$has_reliquat = 0;
$warning = 0;
$textinfo = '';
$textwarning = '';
$nbprod = 0;
$genericProduct = new Product($this->db);
$numlines = count($genericCommande->lines);
for ($lig = 0; $lig < $numlines; $lig++) {
$orderLine = $genericCommande->lines[$lig]; // @phan-var-force OrderLine $orderLine
if (isset($genericCommande->expeditions[$orderLine->id])) {
$reliquat = $orderLine->qty - $genericCommande->expeditions[$orderLine->id];
} else {
$reliquat = $orderLine->qty;
}
if ($orderLine->product_type == 0 && $orderLine->fk_product > 0) { // product, not service
$nbprod = 1;
if (empty($productstatcache[$orderLine->fk_product])) {
$genericProduct->fetch($orderLine->fk_product);
$genericProduct->load_stock('nobatch,warehouseopen'); // loadvirtualstock included
$productstatcache[$orderLine->fk_product]['stockreel'] = $genericProduct->stock_reel;
$productstatcachevirtual[$orderLine->fk_product]['stockreel'] = $genericProduct->stock_theorique;
}
$genericProduct->stock_reel = $productstatcache[$orderLine->fk_product]['stockreel'];
$genericProduct->stock_theorique = $productstatcachevirtual[$orderLine->fk_product]['stockreel'];
if ($reliquat > 0) {
$has_reliquat = 1;
if (!getDolGlobalString('SHIPPABLE_ORDER_ICON_IN_LIST')) {
$textinfo .= $reliquat . ' x ' . $orderLine->product_ref . '&nbsp;' . dol_trunc($orderLine->product_label, 20);
$textinfo .= ' - ' . $langs->trans("Stock") . ': <span class="' . ($genericProduct->stock_reel >= $reliquat ? 'ok' : 'error') . '">' . $genericProduct->stock_reel . '</span>';
$textinfo .= ' - ' . $langs->trans("VirtualStock") . ': <span class="' . ($genericProduct->stock_theorique >= $reliquat ? 'ok' : 'error') . '">' . $genericProduct->stock_theorique . '</span>';
if ($reliquat != $orderLine->qty) {
$textinfo .= ' <span class="opacitymedium">' . $langs->trans("QtyInOtherShipments") . ' ' . ($orderLine->qty - $reliquat) . '</span>';
}
$textinfo .= '<br>';
} else {
// BUGGED CODE (kept for backward compatibility and hidden conf)
$stockorder = 0;
$stockordersupplier = 0;
if (getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT') || getDolGlobalString('STOCK_CALCULATE_ON_SHIPMENT_CLOSE')) {
if (isModEnabled('order')) {
if (empty($productstatcache[$orderLine->fk_product]['statsordercustomer'])) {
$genericProduct->fetch($orderLine->fk_product);
$genericProduct->load_stats_commande(0, '1,2');
$productstatcache[$orderLine->fk_product]['statsordercustomer'] = $genericProduct->stats_commande['qty'];
}
$genericProduct->stats_commande['qty'] = $productstatcache[$orderLine->fk_product]['statsordercustomer'];
$stockorder = $genericProduct->stats_commande['qty'];
if (isModEnabled('supplier_order')) {
if (empty($productstatcache[$orderLine->fk_product]['statsordersupplier'])) {
$genericProduct->load_stats_commande_fournisseur(0, '3');
$productstatcache[$orderLine->fk_product]['statsordersupplier'] = $genericProduct->stats_commande_fournisseur['qty'];
}
$genericProduct->stats_commande_fournisseur['qty'] = $productstatcache[$orderLine->fk_product]['statsordersupplier'];
$stockordersupplier = $genericProduct->stats_commande_fournisseur['qty'];
}
}
}
$textinfo .= $reliquat . ' x ' . $orderLine->ref . '&nbsp;' . dol_trunc($orderLine->product_label, 20);
$textinfo .= ' ' . $langs->trans("Available") . '&nbsp;&nbsp;' . $genericProduct->stock_reel . '..' . $stockorder;
if ($stockorder && $genericProduct->stock_reel < ($genericProduct->stock_reel - $stockorder + $reliquat)) {
$warning++;
$textwarning .= '<span class="warning">' . $langs->trans("Available") . '&nbsp;&nbsp;' . $genericProduct->stock_reel . '..' . $stockorder . '</span>';
} else {
if ($reliquat > $genericProduct->stock_reel) {
$textinfo .= ' <span class="warning">' . $langs->trans("Available") . '&nbsp;&nbsp;' . $genericProduct->stock_reel . '</span>';
} else {
$textinfo .= ' <span class="ok">' . $langs->trans("Available") . '&nbsp;&nbsp;' . $genericProduct->stock_reel . '</span>';
}
}
if (isModEnabled('supplier_order')) {
$textinfo .= '&nbsp;' . $langs->trans("SupplierOrder") . '&nbsp;&nbsp;' . $stockordersupplier;
}
if ($reliquat != $orderLine->qty) {
$textinfo .= ' <span class="opacitymedium">' . $langs->trans("QtyInOtherShipments") . ' ' . ($orderLine->qty - $reliquat) . '</span>';
}
$textinfo .= '<br>';
}
if ($reliquat > $genericProduct->stock_reel) {
$notshippable++;
}
}
}
}
if ($nbprod) {
if (!$has_reliquat) {
$texticon = img_picto('', 'statut5', '', 0, 0, 0, '', 'paddingleft');
$textinfo = $texticon . ' ' . $langs->trans("Shipped");
$result['shippable'] = true;
} elseif ($notshippable) {
$texticon = img_picto('', 'dolly', '', 0, 0, 0, '', 'error paddingleft');
$textinfo = $texticon . ' ' . $langs->trans("NonShippable") . '<br>' . $textinfo;
$result['shippable'] = false;
} else {
$texticon = img_picto('', 'dolly', '', 0, 0, 0, '', 'green paddingleft');
$textinfo = $texticon . ' ' . $langs->trans("Shippable") . '<br>' . $textinfo;
$result['shippable'] = true;
}
$result['has_product'] = true;
$result['texticon'] = $texticon;
$result['textinfo'] = $textinfo;
$result['warning'] = !empty($warning);
}
return $result;
}
}