* FIX Drag and drop of a file reports a wrong error, or none at all
The error handler of dragAndDropFileUpload() had three defects that all end on
the user believing the file was attached when it was not.
- The first assignment of the message was dead code, there was no return after
the test on the http code 403, so a refusal was reported as a generic error.
- The key ErrorUploadPermissionDenied it used exists in no language file, so
the raw key was shown.
- JSON.parse() was called with no try/catch on an answer that is not always a
json, a fatal error of the endpoint or a request over post_max_size for
example. The exception left the user on a page with no message at all.
- An empty list of files was treated as a success, while it means the endpoint
stored nothing.
The value of PHP_SELF is also escaped before it is written into the 6 generated
javascript strings. It holds the path info of the request on a server that
accepts it, so it is a user input. dol_escape_js() is called with the mode that
escapes a double quote by a double quote, the strings being delimited by double
quotes, and the sequence '</' is escaped too because the function does not do it
and the path info could otherwise close the script tag and open one of its own.
Adds the key ErrorOnAtLeastOneFileUpload to en_US, used when some files of a
batch failed and some did not.
* FIX getMultidirOutput returns a directory the Documents tab does not read
Four defects of the same function, all ending on a file stored where the user
will never see it, or written outside the documents directory.
- The ref of the project of a task was only passed through dol_sanitizePathName(),
which keeps a slash, a colon and the accented chars, while projet/tasks/document.php
sanitizes it with dol_sanitizeFileName(). A project ref holding a slash even
created an extra level of directory. Measured on real databases: 304 projects
over 330 hold a slash on one of them, carrying 701 tasks over 824.
- The same case calls $object->fetchProject() with no guard, while the signature
of the function accepts an object that is not a CommonObject, and even a null
when a module is given. Such a caller gets a fatal error where it expects the
error string. No core caller is in that case today, an external module or a
hook can be.
- The entity of the object may have no declared directory, an object shared by
another entity for example. The undefined index returned a relative path, so
the caller read or wrote under the web root. The current entity is used
instead, and the fallback is logged because the directory is then not the one
of the entity of the object, which matters for a caller that deletes files.
- When the current entity has no declared directory either, the fallback
returned the sub directory alone, again a relative path. The same error than
for a module that declares no directory at all is now returned.
Adds the sub directory of a partnership and of a stock transfer, which their own
document tabs already read.
* FIX getElementProperties answers wrong properties for 9 elements
A customer payment, a supplier payment, a various payment, a stock transfer and
the 4 objects of the hrm module had no properties at all, or wrong ones, so any
caller that resolves a class, a table or a document directory from an element
failed on them.
- payment, payment_supplier and payment_various had no branch. The branch of a
customer payment tests $elementType and not $element, because the rule on the
elements named myobject_mysubobject rewrites $element to 'payment' for
'payment_salary' too, which is stored somewhere else.
- job, position, skill and evaluation answered a wrong table (hrm_job_user for
a position, and so on) and no sub directory, while their document tabs read
one named after the element.
- stocktransfer answered an empty classname, because it is not the ucfirst() of
the element, so a caller doing new $classname($db) ended on a fatal error.
- The sub directory was concatenated even when the module is disabled and the
directory is empty, which answered a path at the root of the file system.
- A contact and a conference are stored into a sub directory their tab reads.
isModEnabled('invoice') is tested for a customer payment: there is no module
named 'compta', so testing it was always false, while $conf->compta->payment is
set unconditionally by Conf::setValues() and could not be used as a proxy.
* FIX Access refused to everyone on 11 objects of the core
restrictedArea() and checkUserAccessToObject() refuse the access to objects that
no permission and no rule can match, whatever the user, an administrator
included.
- The hrm module declares no permission at its first level, only 'all', and the
stocktransfer module only 'stocktransfer'. A check on the module itself
therefore tests a permission that does not exist. The mapping is the same one
as into User::hasRight().
- The module of an event organization declares no permission at all, its whole
permission block being commented out on purpose, and its cards check the
parent project instead. The feature is mapped onto that project, with the two
guards the card has: an external user is refused, and so is a conference with
no parent project, whose id of 0 would otherwise grant an access with no check
on the record at all.
- The default rule of checkUserAccessToObject() builds its sql on the columns
entity and fk_soc of the table. llx_asset, llx_paiement, llx_paiementfourn and
llx_workstation_workstation have no fk_soc, and llx_hrm_job, llx_hrm_job_user
and llx_hrm_skill have neither. The sql failed, so the access was refused to
every user this rule applies to. These tables are now checked on their entity
only, which is what the $check rule already does for the same class of tables,
and the 3 tables of hrm can be checked on nothing at all. The rule is selected
on the table and not on the element, because $object is an id and not an
object for most of the callers, the cards of an asset and of a workstation
included, which are broken today for any user without the permission to see
all third parties.
- An external user is refused explicitly on those tables: none of these objects
is linked to a third party, so the default rule refused him through a link
that does not exist, and the rules that replace it do not look at the third
party of the user at all.
Measured on a vanilla instance with 5 profiles, an administrator, an internal
user with every right, one without the permission to see all third parties, one
that is not a sales representative of the third party of the object, and an
external user: the 11 objects go from refused to granted for the internal users
and stay refused for the external one, and the 26 other elements answer exactly
the same for the 5 profiles.
* FIX A file dropped on a card is lost, or reported as refused when it was stored
FileUpload stores the file into a directory that the "Attached files" tab of the
object never reads, so the user attaches a file that no screen will ever show,
and nothing is indexed in database to find it back. Measured on real databases:
216487 thirdparties over 216887 and 157852 products over 280319 are in that case
on the cards that already enable the drag and drop.
- The directory of the object is now forged with get_exdir(), the way the tabs
do: it always uses the id for a thirdparty, whose ref is a company name and is
not unique, and it falls back on the id when the ref is empty. The sub
directory of the module is read with getMultidirOutput(), which knows the
elements that store their documents into one. That function does not return
an empty string when it fails but a string starting with 'error-', so only an
absolute path is accepted: writing into that string would create the files
under the web root.
- fetchObjectByElement() returns an object even when fetch() returned 0. The
object was then not loaded, and the file was stored at the root of the
directory of the module, out of any object. The constructor now throws, and
the endpoint answers the error with the same json contract than a successful
call so that the caller can show it, instead of a fatal error and an http 500.
- An attachment of the same name was silently overwritten, dol_move_uploaded_file()
being called with $allowoverwrite = 1 while the name was checked before the
ref of the object was added as a prefix. The check is done again on the final
name, and on the .noexe suffixed name too, which that function appends to an
executable file.
- An executable file was renamed with that .noexe suffix and then reported as an
error, while it was correctly stored.
- The endpoint called restrictedArea() with an empty feature when the element is
unknown, and the loop of that function then takes no branch at all and grants
the access with no check. It refuses before, with the same http code and the
same message than a refusal, so that a user cannot tell an object that exists
but is not allowed from an object that does not exist. The refusal of an
external user on an object of another third party answered a message of its
own, which allowed the same enumeration.
Comes with the tests of the path resolution, of the fallback of get_exdir(), of
the rejection of the error string of getMultidirOutput(), and of the file name
deduplication.
* FIX Remove the drop area from 13 cards where dropping a file is harmful
Two distinct groups, both of them removing a drop area that only produces a
result the user does not want.
7 pages have no "Attached files" tab at all, so a file dropped on them can be
reached by no screen: a fiscal year and its info page, an accounting model, a
webhook trigger history, an intracomm report, a bookcal calendar and its booking
list. Measured: 4 of them accepted the upload and wrote an orphan file, the
accounting model wrote it at the root of the directory of the module, out of any
object, and the 2 bookcal ones answered an http 500.
6 cards print their tabs inside their edit form, so the drop area covered that
form: a product, an expense report, a VAT payment, a social contribution, a loan
and a salary. Dropping a file there reloads the page, which discards what the
user is typing. A product and an expense report have a second call for the read
only view, so the drop area is only removed from the call of the edit branch;
the 4 others share a single call between both views, so the parameter is now
conditional.
* NEW Drag and drop a file on the 23 remaining cards
Every card of the core that owns an "Attached files" tab can now receive a file
by drag and drop, which was the case of 32 of them only. The 23 added here are
an asset, an event, a various payment, a customer payment, a contact, a donation,
a conference or booth, a shipment, a supplier payment, a leave request, an
evaluation, a job, a position, a skill, a knowledge record, a manufacturing
order, a partnership, a lot, a stock transfer, a task, a reception, a resource
and a workstation.
The tab bar of a leave request and of a resource is printed inside their edit
form, so the drop area is not enabled there: dropping a file reloads the page,
which would discard what the user is typing. The card of a supplier payment
prints its tab bar even when the object was not loaded, so the drop area is only
enabled when it is.
* FIX getMultidirOutput refuses an entity with no directory instead of falling back [skip-claudemd]
The previous revision of this PR fell back on the directory of the current
entity when the entity of the object had none, with a LOG_WARNING. On a
multicompany install that made a caller read, write and above all delete
files in the directory of another entity. Refuse instead: the function
already answers 'error-diroutput-not-defined-for-this-object' when the
module declares no directory at all, so the caller has one behaviour to
handle, not two.
The entity is cast to int, which is what the array index needs and what
silences the four PhanTypeMismatchDimFetchNullable this function reported.
---------
Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
3370 lines
144 KiB
PHP
3370 lines
144 KiB
PHP
<?php
|
|
/* Copyright (C) 2001-2007 Rodolphe Quiedeville <rodolphe@quiedeville.org>
|
|
* Copyright (C) 2004-2016 Laurent Destailleur <eldy@users.sourceforge.net>
|
|
* Copyright (C) 2005 Eric Seigne <eric.seigne@ryxeo.com>
|
|
* Copyright (C) 2005-2015 Regis Houssin <regis.houssin@capnetworks.com>
|
|
* Copyright (C) 2006 Andre Cianfarani <acianfa@free.fr>
|
|
* Copyright (C) 2006 Auguria SARL <info@auguria.org>
|
|
* Copyright (C) 2010-2015 Juanjo Menent <jmenent@2byte.es>
|
|
* Copyright (C) 2013-2016 Marcos García <marcosgdf@gmail.com>
|
|
* Copyright (C) 2012-2013 Cédric Salvador <csalvador@gpcsolutions.fr>
|
|
* Copyright (C) 2011-2023 Alexandre Spangaro <alexandre@inovea-conseil.com>
|
|
* Copyright (C) 2014 Cédric Gross <c.gross@kreiz-it.fr>
|
|
* Copyright (C) 2014-2015 Ferran Marcet <fmarcet@2byte.es>
|
|
* Copyright (C) 2015 Jean-François Ferry <jfefe@aternatik.fr>
|
|
* Copyright (C) 2015 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr>
|
|
* Copyright (C) 2016-2022 Charlene Benke <charlene@patas-monkey.com>
|
|
* Copyright (C) 2016 Meziane Sof <virtualsof@yahoo.fr>
|
|
* Copyright (C) 2017 Josep Lluís Amador <joseplluis@lliuretic.cat>
|
|
* Copyright (C) 2019-2026 Frédéric France <frederic.france@free.fr>
|
|
* Copyright (C) 2019-2020 Thibault FOUCART <support@ptibogxiv.net>
|
|
* Copyright (C) 2020 Pierre Ardoin <mapiolca@me.com>
|
|
* Copyright (C) 2022 Vincent de Grandpré <vincent@de-grandpre.quebec>
|
|
* Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
|
|
* Copyright (C) 2025 William Mead <william@m34d.com>
|
|
* Copyright (C) 2026 Jose MARTINEZ <jose.martinez@pichinov.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* 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
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/**
|
|
* \file htdocs/product/card.php
|
|
* \ingroup product
|
|
* \brief Page to show product
|
|
*/
|
|
|
|
|
|
// Load Dolibarr environment
|
|
require '../main.inc.php';
|
|
/**
|
|
* @var Conf $conf
|
|
* @var DoliDB $db
|
|
* @var ExtraFields $extrafields
|
|
* @var HookManager $hookmanager
|
|
* @var Societe $mysoc
|
|
* @var Translate $langs
|
|
* @var User $user
|
|
*/
|
|
require_once DOL_DOCUMENT_ROOT.'/core/class/canvas.class.php';
|
|
require_once DOL_DOCUMENT_ROOT.'/core/class/genericobject.class.php';
|
|
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php';
|
|
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php';
|
|
require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
|
|
require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
|
|
require_once DOL_DOCUMENT_ROOT.'/core/modules/product/modules_product.class.php';
|
|
require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
|
|
require_once DOL_DOCUMENT_ROOT.'/product/class/html.formproduct.class.php';
|
|
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
|
|
if (isModEnabled('propal')) {
|
|
require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propal.class.php';
|
|
}
|
|
if (isModEnabled('invoice')) {
|
|
require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
|
|
}
|
|
if (isModEnabled('order')) {
|
|
require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
|
|
}
|
|
if (isModEnabled('accounting')) {
|
|
require_once DOL_DOCUMENT_ROOT.'/core/lib/accounting.lib.php';
|
|
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formaccounting.class.php';
|
|
require_once DOL_DOCUMENT_ROOT.'/accountancy/class/accountingaccount.class.php';
|
|
}
|
|
if (isModEnabled('bom')) {
|
|
require_once DOL_DOCUMENT_ROOT.'/bom/class/bom.class.php';
|
|
$langs->load("mrp");
|
|
}
|
|
if (isModEnabled('workstation')) {
|
|
require_once DOL_DOCUMENT_ROOT.'/workstation/class/workstation.class.php';
|
|
}
|
|
|
|
// Load translation files required by the page
|
|
$langs->loadLangs(array('products', 'other'));
|
|
if (isModEnabled('stock')) {
|
|
$langs->load("stocks");
|
|
}
|
|
if (isModEnabled('invoice')) {
|
|
$langs->load("bills");
|
|
}
|
|
if (isModEnabled('productbatch')) {
|
|
$langs->load("productbatch");
|
|
}
|
|
|
|
$backtopageforcancel = GETPOST('backtopageforcancel');
|
|
|
|
$mesg = '';
|
|
$error = 0;
|
|
$errors = array();
|
|
|
|
$refalreadyexists = 0;
|
|
$formbarcode = null;
|
|
|
|
// Get parameters
|
|
$id = GETPOSTINT('id');
|
|
if (getDolGlobalString('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
|
|
$ref = (GETPOSTISSET('ref') ? GETPOST('ref', 'nohtml') : null);
|
|
} else {
|
|
$ref = (GETPOSTISSET('ref') ? GETPOST('ref', 'alpha') : null);
|
|
}
|
|
$type = (GETPOSTISSET('type') ? GETPOSTINT('type') : Product::TYPE_PRODUCT);
|
|
$action = (GETPOST('action', 'alpha') ? GETPOST('action', 'alpha') : 'view');
|
|
$cancel = GETPOST('cancel', 'alpha');
|
|
$backtopage = GETPOST('backtopage', 'alpha');
|
|
$dol_openinpopup = GETPOST('dol_openinpopup', 'aZ09');
|
|
$confirm = GETPOST('confirm', 'alpha');
|
|
$socid = GETPOSTINT('socid');
|
|
$duration_value = GETPOST('duration_value') === '' ? null : GETPOSTINT('duration_value'); // duration value can be an empty string
|
|
$duration_unit = GETPOST('duration_unit', 'alpha');
|
|
|
|
$accountancy_code_sell = GETPOST('accountancy_code_sell', 'alpha');
|
|
$accountancy_code_sell_intra = GETPOST('accountancy_code_sell_intra', 'alpha');
|
|
$accountancy_code_sell_export = GETPOST('accountancy_code_sell_export', 'alpha');
|
|
$accountancy_code_buy = GETPOST('accountancy_code_buy', 'alpha');
|
|
$accountancy_code_buy_intra = GETPOST('accountancy_code_buy_intra', 'alpha');
|
|
$accountancy_code_buy_export = GETPOST('accountancy_code_buy_export', 'alpha');
|
|
|
|
$checkmandatory = GETPOST('accountancy_code_buy_export', 'alpha');
|
|
|
|
// by default 'alphanohtml' (better security); hidden conf MAIN_SECURITY_ALLOW_UNSECURED_LABELS_WITH_HTML allows basic html
|
|
if (getDolGlobalString('MAIN_SECURITY_ALLOW_UNSECURED_REF_LABELS')) {
|
|
$label_security_check = 'nohtml';
|
|
} else {
|
|
$label_security_check = !getDolGlobalString('MAIN_SECURITY_ALLOW_UNSECURED_LABELS_WITH_HTML') ? 'alphanohtml' : 'restricthtml';
|
|
}
|
|
|
|
if (!empty($user->socid)) {
|
|
$socid = $user->socid;
|
|
}
|
|
|
|
// Load object modCodeProduct
|
|
$module = getDolGlobalString('PRODUCT_CODEPRODUCT_ADDON', 'mod_codeproduct_leopard');
|
|
if (substr($module, 0, 16) == 'mod_codeproduct_' && substr($module, -3) == 'php') {
|
|
$module = substr($module, 0, dol_strlen($module) - 4);
|
|
}
|
|
$result = dol_include_once('/core/modules/product/'.$module.'.php');
|
|
if ($result > 0) {
|
|
$modCodeProduct = new $module();
|
|
/** @var ModeleProductCode $modCodeProduct */
|
|
}
|
|
|
|
$object = new Product($db);
|
|
$object->type = $type; // so test later to fill $usercancxxx is correct
|
|
$refLabelKey = ($type == Product::TYPE_SERVICE ? 'ServiceRef' : 'ProductRef');
|
|
|
|
// fetch optionals attributes and labels
|
|
$extrafields->fetch_name_optionals_label($object->table_element);
|
|
|
|
if ($id > 0 || !empty($ref)) {
|
|
$result = $object->fetch($id, (string) $ref);
|
|
if ($result < 0) {
|
|
dol_print_error($db, $object->error, $object->errors);
|
|
}
|
|
$entity = (empty($object->entity) ? $conf->entity : $object->entity);
|
|
if (isModEnabled("product")) {
|
|
$upload_dir = $conf->product->multidir_output[$entity].'/'.get_exdir(0, 0, 0, 0, $object, 'product').dol_sanitizeFileName($object->ref);
|
|
} elseif (isModEnabled("service")) {
|
|
$upload_dir = $conf->service->multidir_output[$entity].'/'.get_exdir(0, 0, 0, 0, $object, 'product').dol_sanitizeFileName($object->ref);
|
|
}
|
|
|
|
if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) { // For backward compatibility, we scan also old dirs
|
|
if (isModEnabled("product")) {
|
|
$upload_dirold = $conf->product->multidir_output[$entity].'/'.substr(substr("000".$object->id, -2), 1, 1).'/'.substr(substr("000".$object->id, -2), 0, 1).'/'.$object->id."/photos";
|
|
} else {
|
|
$upload_dirold = $conf->service->multidir_output[$entity].'/'.substr(substr("000".$object->id, -2), 1, 1).'/'.substr(substr("000".$object->id, -2), 0, 1).'/'.$object->id."/photos";
|
|
}
|
|
}
|
|
}
|
|
|
|
$modulepart = 'product';
|
|
|
|
// Get object canvas (By default, this is not defined, so standard usage of dolibarr)
|
|
$canvas = !empty($object->canvas) ? $object->canvas : GETPOST("canvas");
|
|
$objcanvas = null;
|
|
if (!empty($canvas)) {
|
|
require_once DOL_DOCUMENT_ROOT.'/core/class/canvas.class.php';
|
|
$objcanvas = new Canvas($db, $action);
|
|
$objcanvas->getCanvas('product', 'card', $canvas);
|
|
}
|
|
|
|
// Security check
|
|
$fieldvalue = (!empty($id) ? $id : (!empty($ref) ? $ref : ''));
|
|
$fieldtype = (!empty($id) ? 'rowid' : 'ref');
|
|
|
|
// Initialize a technical object to manage hooks of page. Note that conf->hooks_modules contains an array of hook context
|
|
$hookmanager->initHooks(array('productcard', 'globalcard'));
|
|
|
|
if ($object->id > 0) {
|
|
if ($object->type == $object::TYPE_PRODUCT) {
|
|
restrictedArea($user, 'produit', $object->id, 'product&product', '', '');
|
|
}
|
|
if ($object->type == $object::TYPE_SERVICE) {
|
|
restrictedArea($user, 'service', $object->id, 'product&product', '', '');
|
|
}
|
|
} else {
|
|
restrictedArea($user, 'produit|service', 0, 'product&product', '', '', $fieldtype);
|
|
}
|
|
|
|
// Permissions
|
|
$usercanread = (($object->type == Product::TYPE_PRODUCT && $user->hasRight('produit', 'lire')) || ($object->type == Product::TYPE_SERVICE && $user->hasRight('service', 'lire')));
|
|
$usercancreate = (($object->type == Product::TYPE_PRODUCT && $user->hasRight('produit', 'creer')) || ($object->type == Product::TYPE_SERVICE && $user->hasRight('service', 'creer')));
|
|
$usercandelete = (($object->type == Product::TYPE_PRODUCT && $user->hasRight('produit', 'supprimer')) || ($object->type == Product::TYPE_SERVICE && $user->hasRight('service', 'supprimer')));
|
|
$permissiontoeditextra = $usercancreate;
|
|
if (GETPOST('attribute', 'aZ09') && isset($extrafields->attributes[$object->table_element]['perms'][GETPOST('attribute', 'aZ09')])) {
|
|
// For action 'update_extras', is there a specific permission set for the attribute to update
|
|
$permissiontoeditextra = dol_eval((string) $extrafields->attributes[$object->table_element]['perms'][GETPOST('attribute', 'aZ09')]);
|
|
}
|
|
|
|
|
|
/*
|
|
* Actions
|
|
*/
|
|
|
|
if ($cancel) {
|
|
$action = '';
|
|
}
|
|
|
|
$createbarcode = (isModEnabled('barcode') && getDolGlobalString('BARCODE_USE_ON_PRODUCT'));
|
|
if (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && !$user->hasRight('barcode', 'creer_advance')) {
|
|
$createbarcode = 0;
|
|
}
|
|
|
|
$parameters = array('id' => $id, 'ref' => $ref, 'objcanvas' => $objcanvas);
|
|
$reshook = $hookmanager->executeHooks('doActions', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
|
|
if ($reshook < 0) {
|
|
setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
|
|
}
|
|
|
|
if (empty($reshook)) {
|
|
$backurlforlist = DOL_URL_ROOT.'/product/list.php?type='.$type;
|
|
|
|
if (empty($backtopage) || ($cancel && empty($id))) {
|
|
if (empty($backtopage) || ($cancel && strpos($backtopage, '__ID__'))) {
|
|
if (empty($id) && (($action != 'add' && $action != 'create') || $cancel)) {
|
|
$backtopage = $backurlforlist;
|
|
} else {
|
|
$backtopage = DOL_URL_ROOT.'/product/card.php?id='.((!empty($id) && $id > 0) ? $id : '__ID__');
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($cancel) {
|
|
if (!empty($backtopageforcancel)) {
|
|
header("Location: ".$backtopageforcancel);
|
|
exit;
|
|
} elseif (!empty($backtopage)) {
|
|
header("Location: ".$backtopage);
|
|
exit;
|
|
}
|
|
$action = '';
|
|
}
|
|
// merge products
|
|
if ($action == 'confirm_merge' && $confirm == 'yes' && $user->hasRight('societe', 'creer')) {
|
|
$error = 0;
|
|
$productOriginId = GETPOSTINT('product_origin');
|
|
$productOrigin = new Product($db);
|
|
|
|
if ($productOriginId <= 0) {
|
|
$langs->load('errors');
|
|
setEventMessages($langs->trans('ErrorProductIdIsMandatory', $langs->transnoentitiesnoconv('MergeOriginProduct')), null, 'errors');
|
|
} else {
|
|
if (!$error && $productOrigin->fetch($productOriginId) < 1) {
|
|
setEventMessages($langs->trans('ErrorRecordNotFound'), null, 'errors');
|
|
$error++;
|
|
}
|
|
|
|
if (!$error) {
|
|
// TODO Move the merge function into class of object.
|
|
$db->begin();
|
|
|
|
// Recopy some data
|
|
$listofproperties = array(
|
|
'ref',
|
|
'ref_ext',
|
|
'label',
|
|
'description',
|
|
'url',
|
|
'barcode',
|
|
'fk_barcode_type',
|
|
'import_key',
|
|
'mandatory_period',
|
|
'accountancy_code_buy',
|
|
'accountancy_code_buy_intra',
|
|
'accountancy_code_buy_export',
|
|
'accountancy_code_sell',
|
|
'accountancy_code_sell_intra',
|
|
'accountancy_code_sell_export'
|
|
);
|
|
foreach ($listofproperties as $property) {
|
|
if (empty($object->$property)) {
|
|
$object->$property = $productOrigin->$property;
|
|
}
|
|
}
|
|
// Concat some data
|
|
$listofproperties = array(
|
|
'note_public', 'note_private'
|
|
);
|
|
foreach ($listofproperties as $property) {
|
|
$object->$property = dol_concatdesc($object->$property, $productOrigin->$property);
|
|
}
|
|
|
|
// Merge extrafields
|
|
if (is_array($productOrigin->array_options)) {
|
|
foreach ($productOrigin->array_options as $key => $val) {
|
|
if (empty($object->array_options[$key])) {
|
|
$object->array_options[$key] = $val;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Merge categories
|
|
$static_cat = new Categorie($db);
|
|
$custcats_ori = $static_cat->containing($productOrigin->id, 'product', 'id');
|
|
$custcats = $static_cat->containing($object->id, 'product', 'id');
|
|
$custcats = array_merge($custcats, $custcats_ori);
|
|
$object->setCategories($custcats);
|
|
|
|
// If product has a new code that is same than origin, we clean origin code to avoid duplicate key from database unique keys.
|
|
if ($productOrigin->barcode == $object->barcode) {
|
|
dol_syslog("We clean customer and supplier code so we will be able to make the update of target");
|
|
$productOrigin->barcode = '';
|
|
//$productOrigin->update($productOrigin->id, $user, 0, 'merge');
|
|
}
|
|
|
|
// Update
|
|
$result = $object->update($object->id, $user, 0, 'merge');
|
|
if ($result <= 0) {
|
|
setEventMessages($object->error, $object->errors, 'errors');
|
|
$error++;
|
|
}
|
|
|
|
// Move links
|
|
if (!$error) {
|
|
// TODO add this functionality into the api_products.class.php
|
|
// TODO Mutualise the list into object product.class.php
|
|
$objects = array(
|
|
'ActionComm' => '/comm/action/class/actioncomm.class.php',
|
|
'Bom' => '/bom/class/bom.class.php',
|
|
// do not use Categorie, it cause foreign key error, merge is done before
|
|
//'Categorie' => '/categories/class/categorie.class.php',
|
|
'Commande' => '/commande/class/commande.class.php',
|
|
'CommandeFournisseur' => '/fourn/class/fournisseur.commande.class.php',
|
|
'Contrat' => '/contrat/class/contrat.class.php',
|
|
'Delivery' => '/delivery/class/delivery.class.php',
|
|
'Facture' => '/compta/facture/class/facture.class.php',
|
|
'FactureFournisseur' => '/fourn/class/fournisseur.facture.class.php',
|
|
'FactureRec' => '/compta/facture/class/facture-rec.class.php',
|
|
'FichinterRec' => '/fichinter/class/fichinterrec.class.php',
|
|
'ProductFournisseur' => '/fourn/class/fournisseur.product.class.php',
|
|
'Propal' => '/comm/propal/class/propal.class.php',
|
|
'Reception' => '/reception/class/reception.class.php',
|
|
'SupplierProposal' => '/supplier_proposal/class/supplier_proposal.class.php',
|
|
);
|
|
|
|
// First, all core objects must update their tables
|
|
foreach ($objects as $object_name => $object_file) {
|
|
require_once DOL_DOCUMENT_ROOT.$object_file;
|
|
|
|
if (!$error && !$object_name::replaceProduct($db, $productOrigin->id, $object->id)) {
|
|
$error++;
|
|
setEventMessages($db->lasterror(), null, 'errors');
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// External modules should update their ones too
|
|
if (!$error) {
|
|
$parameters = array('soc_origin' => $productOrigin->id, 'soc_dest' => $object->id);
|
|
$reshook = $hookmanager->executeHooks(
|
|
'replaceProduct',
|
|
$parameters,
|
|
$object,
|
|
$action
|
|
);
|
|
|
|
if ($reshook < 0) {
|
|
setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
|
|
$error++;
|
|
}
|
|
}
|
|
|
|
|
|
if (!$error) {
|
|
$object->context = array(
|
|
'merge' => 1,
|
|
'mergefromid' => $productOrigin->id,
|
|
);
|
|
|
|
// Call trigger
|
|
$result = $object->call_trigger('PRODUCT_MODIFY', $user);
|
|
if ($result < 0) {
|
|
setEventMessages($object->error, $object->errors, 'errors');
|
|
$error++;
|
|
}
|
|
// End call triggers
|
|
}
|
|
|
|
if (!$error) {
|
|
// Delete the product
|
|
if ($productOrigin->delete($user) < 1) {
|
|
$error++;
|
|
}
|
|
}
|
|
|
|
if ($error) {
|
|
// Move files from the dir of the third party to delete into the dir of the third party to keep
|
|
if (!empty($conf->product->multidir_output[$productOrigin->entity ?? 1])) {
|
|
$srcdir = $conf->product->multidir_output[$productOrigin->entity ?? 1]."/".$productOrigin->ref;
|
|
$destdir = $conf->product->multidir_output[$object->entity ?? $conf->entity]."/".$object->ref;
|
|
|
|
if (dol_is_dir($srcdir)) {
|
|
$dirlist = dol_dir_list($srcdir, 'files', 1);
|
|
foreach ($dirlist as $filetomove) {
|
|
$destfile = $destdir.'/'.$filetomove['relativename'];
|
|
//var_dump('Move file '.$filetomove['relativename'].' into '.$destfile);
|
|
dol_move($filetomove['fullname'], $destfile, '0', 0, 0, 1);
|
|
}
|
|
//exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$error) {
|
|
setEventMessages($langs->trans('ProductsMergeSuccess'), null, 'mesgs');
|
|
$db->commit();
|
|
} else {
|
|
$langs->load("errors");
|
|
setEventMessages($langs->trans('ErrorsProductsMerge'), null, 'errors');
|
|
$db->rollback();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Type
|
|
if ($action == 'setfk_product_type' && $usercancreate) {
|
|
$result = $object->setValueFrom('fk_product_type', GETPOST('fk_product_type'), '', null, 'text', '', $user, 'PRODUCT_MODIFY');
|
|
header("Location: ".$_SERVER['PHP_SELF']."?id=".$object->id);
|
|
exit;
|
|
}
|
|
|
|
// Actions to build doc
|
|
$upload_dir = $conf->product->dir_output;
|
|
$permissiontoadd = $usercancreate;
|
|
include DOL_DOCUMENT_ROOT.'/core/actions_builddoc.inc.php';
|
|
|
|
include DOL_DOCUMENT_ROOT.'/core/actions_printing.inc.php';
|
|
|
|
// Barcode type
|
|
if ($action == 'setfk_barcode_type' && $usercancreate) {
|
|
$result = $object->setValueFrom('fk_barcode_type', GETPOST('fk_barcode_type'), '', null, 'text', '', $user, 'PRODUCT_MODIFY');
|
|
header("Location: ".$_SERVER['PHP_SELF']."?id=".$object->id);
|
|
exit;
|
|
}
|
|
|
|
// Barcode value
|
|
if ($action == 'setbarcode' && $usercancreate) {
|
|
$result = $object->check_barcode(GETPOST('barcode'), GETPOST('barcode_type_code'));
|
|
|
|
if ($result >= 0) {
|
|
$result = $object->setValueFrom('barcode', GETPOST('barcode'), '', null, 'text', '', $user, 'PRODUCT_MODIFY');
|
|
header("Location: ".$_SERVER['PHP_SELF']."?id=".$object->id);
|
|
exit;
|
|
} else {
|
|
$langs->load("errors");
|
|
if ($result == -1) {
|
|
$errors[] = 'ErrorBadBarCodeSyntax';
|
|
} elseif ($result == -2) {
|
|
$errors[] = 'ErrorBarCodeRequired';
|
|
} elseif ($result == -3) {
|
|
$errors[] = 'ErrorBarCodeAlreadyUsed';
|
|
} else {
|
|
$errors[] = 'FailedToValidateBarCode';
|
|
}
|
|
|
|
$error++;
|
|
setEventMessages('', $errors, 'errors');
|
|
}
|
|
}
|
|
|
|
// Quick edit for extrafields
|
|
if ($action == 'update_extras' && $permissiontoeditextra) {
|
|
// we may use oldcopy->hasBatch( in triggers so keep 1
|
|
$object->oldcopy = dol_clone($object, 1); // @phan-suppress-current-line PhanTypeMismatchProperty
|
|
|
|
$attribute_name = GETPOST('attribute', 'aZ09');
|
|
|
|
// Fill array 'array_options' with data from update form
|
|
$ret = $extrafields->setOptionalsFromPost(null, $object, $attribute_name);
|
|
if ($ret < 0) {
|
|
$error++;
|
|
}
|
|
|
|
if (!$error) {
|
|
$result = $object->updateExtraField($attribute_name, 'PRODUCT_MODIFY');
|
|
if ($result < 0) {
|
|
setEventMessages($object->error, $object->errors, 'errors');
|
|
$error++;
|
|
}
|
|
}
|
|
|
|
if ($error) {
|
|
$action = 'edit_extras';
|
|
}
|
|
}
|
|
|
|
// Add a product or service
|
|
if ($action == 'add' && $usercancreate) {
|
|
$error = 0;
|
|
|
|
if (!GETPOST('label', $label_security_check)) {
|
|
setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentities('Label')), null, 'errors');
|
|
$action = "create";
|
|
$error++;
|
|
}
|
|
if (empty($ref)) {
|
|
if (!getDolGlobalString('PRODUCT_GENERATE_REF_AFTER_FORM')) {
|
|
setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentities($refLabelKey)), null, 'errors');
|
|
$action = "create";
|
|
$error++;
|
|
}
|
|
}
|
|
if (!empty($duration_value) && empty($duration_unit)) {
|
|
setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentities('Unit')), null, 'errors');
|
|
$action = "create";
|
|
$error++;
|
|
}
|
|
$stockable_product = (int) ($type == 0 || ($type == 1 && getDolGlobalInt('STOCK_SUPPORTS_SERVICES')));
|
|
if (GETPOST('status_batch') && $stockable_product == 0 && isModEnabled('stock') && isModEnabled('productbatch')) {
|
|
setEventMessages($langs->trans('ErrorBatchesNeedStockManagement'), null, 'errors');
|
|
$action = "create";
|
|
$error++;
|
|
}
|
|
|
|
if (!$error) {
|
|
$units = GETPOSTINT('units');
|
|
|
|
$object->entity = $conf->entity;
|
|
$object->ref = (string) $ref;
|
|
$object->label = GETPOST('label', $label_security_check);
|
|
$object->price_base_type = GETPOST('price_base_type', 'aZ09');
|
|
$object->mandatory_period = empty(GETPOST("mandatoryperiod", 'alpha')) ? 0 : 1;
|
|
if ($object->price_base_type == 'TTC') {
|
|
$object->price_ttc = GETPOSTFLOAT('price');
|
|
} else {
|
|
$object->price = GETPOSTFLOAT('price');
|
|
}
|
|
if ($object->price_base_type == 'TTC') {
|
|
$object->price_min_ttc = GETPOSTFLOAT('price_min');
|
|
} else {
|
|
$object->price_min = GETPOSTFLOAT('price_min');
|
|
}
|
|
|
|
$tva_tx_txt = GETPOST('tva_tx', 'alpha'); // tva_tx can be '8.5' or '8.5*' or '8.5 (XXX)' or '8.5* (XXX)'
|
|
|
|
// We must define tva_tx, npr and local taxes
|
|
$vatratecode = '';
|
|
$tva_tx = preg_replace('/[^0-9\.].*$/', '', $tva_tx_txt); // keep remove all after the numbers and dot
|
|
$npr = preg_match('/\*/', $tva_tx_txt) ? 1 : 0;
|
|
$localtax1 = 0;
|
|
$localtax2 = 0;
|
|
$localtax1_type = '0';
|
|
$localtax2_type = '0';
|
|
// If value contains the unique code of vat line (new recommended method), we use it to find npr and local taxes
|
|
$reg = array();
|
|
if (preg_match('/\((.*)\)/', $tva_tx_txt, $reg)) {
|
|
// We look into database using code (we can't use get_localtax() because it depends on buyer that is not known). Same in update price.
|
|
$vatratecode = $reg[1];
|
|
// Get record from code
|
|
$sql = "SELECT t.rowid, t.code, t.recuperableonly, t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
|
|
$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
|
|
$sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($mysoc->country_code)."'";
|
|
$sql .= " AND t.taux = ".((float) $tva_tx)." AND t.active = 1";
|
|
$sql .= " AND t.code = '".$db->escape($vatratecode)."'";
|
|
$sql .= " AND t.entity IN (".getEntity('c_tva').")";
|
|
$resql = $db->query($sql);
|
|
if ($resql) {
|
|
$obj = $db->fetch_object($resql);
|
|
$npr = $obj->recuperableonly;
|
|
$localtax1 = $obj->localtax1;
|
|
$localtax2 = $obj->localtax2;
|
|
$localtax1_type = $obj->localtax1_type;
|
|
$localtax2_type = $obj->localtax2_type;
|
|
}
|
|
}
|
|
|
|
$object->default_vat_code = $vatratecode;
|
|
$object->tva_tx = $tva_tx;
|
|
$object->tva_npr = $npr;
|
|
$object->localtax1_tx = $localtax1;
|
|
$object->localtax2_tx = $localtax2;
|
|
$object->localtax1_type = $localtax1_type;
|
|
$object->localtax2_type = $localtax2_type;
|
|
|
|
$object->type = $type;
|
|
$object->status = GETPOSTINT('statut');
|
|
$object->status_buy = GETPOSTINT('statut_buy');
|
|
$object->status_batch = GETPOSTINT('status_batch');
|
|
$object->sell_or_eat_by_mandatory = GETPOSTINT('sell_or_eat_by_mandatory');
|
|
$object->batch_mask = GETPOST('batch_mask');
|
|
|
|
$object->barcode_type = GETPOSTINT('fk_barcode_type');
|
|
$object->barcode = GETPOST('barcode');
|
|
// Set barcode_type_xxx from barcode_type id
|
|
$stdobject = new GenericObject($db);
|
|
$stdobject->element = 'product';
|
|
$stdobject->barcode_type = GETPOSTINT('fk_barcode_type');
|
|
$result = $stdobject->fetchBarCode();
|
|
if ($result < 0) {
|
|
$error++;
|
|
$mesg = 'Failed to get bar code type information ';
|
|
setEventMessages($mesg.$stdobject->error, $stdobject->errors, 'errors');
|
|
}
|
|
$object->barcode_type_code = $stdobject->barcode_type_code;
|
|
$object->barcode_type_coder = $stdobject->barcode_type_coder;
|
|
$object->barcode_type_label = $stdobject->barcode_type_label;
|
|
|
|
$object->description = dol_htmlcleanlastbr(GETPOST('desc', 'restricthtml'));
|
|
$object->url = GETPOST('url');
|
|
$object->note_private = dol_htmlcleanlastbr(GETPOST('note_private', 'restricthtml'));
|
|
$object->note = $object->note_private; // deprecated
|
|
$object->customcode = GETPOST('customcode', 'alphanohtml');
|
|
$object->country_id = GETPOSTINT('country_id');
|
|
$object->state_id = GETPOSTINT('state_id');
|
|
$object->lifetime = GETPOSTINT('lifetime');
|
|
$object->qc_frequency = GETPOSTINT('qc_frequency');
|
|
$object->duration_value = $duration_value;
|
|
$object->duration_unit = $duration_unit;
|
|
$object->fk_default_warehouse = GETPOSTINT('fk_default_warehouse');
|
|
$object->fk_default_workstation = GETPOSTINT('fk_default_workstation');
|
|
$object->seuil_stock_alerte = GETPOST('seuil_stock_alerte') ? GETPOST('seuil_stock_alerte') : 0;
|
|
$object->desiredstock = GETPOST('desiredstock') ? GETPOST('desiredstock') : 0;
|
|
$object->canvas = GETPOST('canvas');
|
|
$object->net_measure = GETPOST('net_measure');
|
|
$object->net_measure_units = GETPOST('net_measure_units') === '' ? null : GETPOSTINT('net_measure_units'); // This is not the fk_unit but the power of unit
|
|
$object->weight = GETPOST('weight');
|
|
$object->weight_units = GETPOST('weight_units') === '' ? null : GETPOSTINT('weight_units'); // This is not the fk_unit but the power of unit
|
|
$object->length = GETPOST('size');
|
|
$object->length_units = GETPOST('size_units') === '' ? null : GETPOSTINT('size_units'); // This is not the fk_unit but the power of unit
|
|
$object->width = GETPOST('sizewidth');
|
|
$object->height = GETPOST('sizeheight');
|
|
$object->surface = GETPOST('surface');
|
|
$object->surface_units = GETPOST('surface_units') === '' ? null : GETPOSTINT('surface_units'); // This is not the fk_unit but the power of unit
|
|
$object->volume = GETPOST('volume');
|
|
$object->volume_units = GETPOST('volume_units') === '' ? null : GETPOSTINT('volume_units'); // This is not the fk_unit but the power of unit
|
|
$finished = GETPOSTINT('finished');
|
|
if ($finished >= 0) {
|
|
$object->finished = $finished;
|
|
} else {
|
|
$object->finished = null;
|
|
}
|
|
|
|
$units = GETPOSTINT('units');
|
|
if ($units > 0) {
|
|
$object->fk_unit = $units;
|
|
} else {
|
|
$object->fk_unit = null;
|
|
}
|
|
|
|
$object->stockable_product = $stockable_product;
|
|
|
|
$accountancy_code_sell = GETPOST('accountancy_code_sell', 'alpha');
|
|
$accountancy_code_sell_intra = GETPOST('accountancy_code_sell_intra', 'alpha');
|
|
$accountancy_code_sell_export = GETPOST('accountancy_code_sell_export', 'alpha');
|
|
$accountancy_code_buy = GETPOST('accountancy_code_buy', 'alpha');
|
|
$accountancy_code_buy_intra = GETPOST('accountancy_code_buy_intra', 'alpha');
|
|
$accountancy_code_buy_export = GETPOST('accountancy_code_buy_export', 'alpha');
|
|
|
|
if (empty($accountancy_code_sell) || $accountancy_code_sell == '-1') {
|
|
$object->accountancy_code_sell = '';
|
|
} else {
|
|
$object->accountancy_code_sell = $accountancy_code_sell;
|
|
}
|
|
if (empty($accountancy_code_sell_intra) || $accountancy_code_sell_intra == '-1') {
|
|
$object->accountancy_code_sell_intra = '';
|
|
} else {
|
|
$object->accountancy_code_sell_intra = $accountancy_code_sell_intra;
|
|
}
|
|
if (empty($accountancy_code_sell_export) || $accountancy_code_sell_export == '-1') {
|
|
$object->accountancy_code_sell_export = '';
|
|
} else {
|
|
$object->accountancy_code_sell_export = $accountancy_code_sell_export;
|
|
}
|
|
if (empty($accountancy_code_buy) || $accountancy_code_buy == '-1') {
|
|
$object->accountancy_code_buy = '';
|
|
} else {
|
|
$object->accountancy_code_buy = $accountancy_code_buy;
|
|
}
|
|
if (empty($accountancy_code_buy_intra) || $accountancy_code_buy_intra == '-1') {
|
|
$object->accountancy_code_buy_intra = '';
|
|
} else {
|
|
$object->accountancy_code_buy_intra = $accountancy_code_buy_intra;
|
|
}
|
|
if (empty($accountancy_code_buy_export) || $accountancy_code_buy_export == '-1') {
|
|
$object->accountancy_code_buy_export = '';
|
|
} else {
|
|
$object->accountancy_code_buy_export = $accountancy_code_buy_export;
|
|
}
|
|
|
|
// MultiPrix
|
|
if (getDolGlobalString('PRODUIT_MULTIPRICES')) {
|
|
$produit_multiprices_limit = getDolGlobalString('PRODUIT_MULTIPRICES_LIMIT');
|
|
for ($i = 2; $i <= $produit_multiprices_limit; $i++) {
|
|
if (GETPOSTISSET("price_".$i)) {
|
|
$object->multiprices["$i"] = (float) price2num(GETPOST("price_".$i), 'MU');
|
|
$object->multiprices_base_type["$i"] = GETPOST("multiprices_base_type_".$i);
|
|
} else {
|
|
$object->multiprices["$i"] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fill array 'array_options' with data from add form
|
|
$ret = $extrafields->setOptionalsFromPost(null, $object);
|
|
if ($ret < 0) {
|
|
$error++;
|
|
}
|
|
|
|
if (!$ref && getDolGlobalString('PRODUCT_GENERATE_REF_AFTER_FORM')) {
|
|
// Generate ref...
|
|
'@phan-var ModeleProductCode $modCodeProduct';
|
|
/** @var ModeleProductCode $modCodeProduct */
|
|
$ref = $modCodeProduct->getNextValue($object, $type);
|
|
}
|
|
|
|
if (!$error) {
|
|
$id = $object->create($user);
|
|
}
|
|
|
|
if ($id > 0) {
|
|
// Category association
|
|
$categories = GETPOST('categories', 'array:int');
|
|
$object->setCategories($categories);
|
|
|
|
if (!empty($backtopage)) {
|
|
$backtopage = preg_replace('/__ID__/', (string) $object->id, $backtopage); // New method to autoselect parent project after a New on another form object creation
|
|
$backtopage = preg_replace('/--IDFORBACKTOPAGE--/', (string) $object->id, $backtopage); // New method to autoselect parent after a New on another form object creation
|
|
if (preg_match('/\?/', $backtopage)) {
|
|
$backtopage .= '&productid='.$object->id; // Old method
|
|
}
|
|
|
|
if (!empty($dol_openinpopup)) {
|
|
// Created from a popup dialog: reload the parent page instead (backtopage, with the new id substituted, can autoselect the product)
|
|
print '<script nonce="'.getNonce().'">';
|
|
print "window.parent.location.href = '".dol_escape_js($backtopage)."';";
|
|
print '</script>';
|
|
exit;
|
|
}
|
|
header("Location: ".$backtopage);
|
|
exit;
|
|
} else {
|
|
header("Location: ".$_SERVER['PHP_SELF']."?id=".$id);
|
|
exit;
|
|
}
|
|
} else {
|
|
if (count($object->errors)) {
|
|
setEventMessages($object->error, $object->errors, 'errors');
|
|
} else {
|
|
if ($object->error == 'ErrorProductAlreadyExists') {
|
|
// allow to hook on ErrorProductAlreadyExists in any module
|
|
$reshook = $hookmanager->executeHooks('onProductAlreadyExists', $parameters, $object, $action);
|
|
if ($reshook < 0) {
|
|
setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
|
|
}
|
|
if ($object->error) {
|
|
// check again to prevent translation issue,
|
|
// as error may have been cleared in hook function
|
|
setEventMessages($langs->trans($object->error), null, 'errors');
|
|
}
|
|
} else {
|
|
setEventMessages($langs->trans($object->error), null, 'errors');
|
|
}
|
|
}
|
|
$action = "create";
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update a product or service
|
|
if ($action == 'update' && $usercancreate) {
|
|
if (GETPOST('cancel', 'alpha')) {
|
|
$action = '';
|
|
} else {
|
|
if ($object->id > 0) {
|
|
// Need dol_clone methode 1 (same object class) because update product use hasbatch() method on oldcopy
|
|
$object->oldcopy = dol_clone($object, 1); // @phan-suppress-current-line PhanTypeMismatchProperty
|
|
|
|
if (!getDolGlobalString('PRODUCT_GENERATE_REF_AFTER_FORM')) {
|
|
$object->ref = (string) $ref;
|
|
}
|
|
$object->label = GETPOST('label', $label_security_check);
|
|
|
|
$desc = dol_htmlcleanlastbr(preg_replace('/ $/', '', GETPOST('desc', 'restricthtml')));
|
|
$object->description = $desc;
|
|
|
|
$object->url = GETPOST('url');
|
|
if (getDolGlobalString('MAIN_DISABLE_NOTES_TAB')) {
|
|
$object->note_private = dol_htmlcleanlastbr(GETPOST('note_private', 'restricthtml'));
|
|
$object->note = $object->note_private;
|
|
}
|
|
$object->customcode = GETPOST('customcode', 'alpha');
|
|
$object->country_id = GETPOSTINT('country_id');
|
|
$object->state_id = GETPOSTINT('state_id');
|
|
$object->lifetime = GETPOSTINT('lifetime');
|
|
$object->qc_frequency = GETPOSTINT('qc_frequency');
|
|
$object->status = GETPOSTINT('statut');
|
|
$object->status_buy = GETPOSTINT('statut_buy');
|
|
$object->status_batch = GETPOSTINT('status_batch');
|
|
$object->sell_or_eat_by_mandatory = GETPOSTINT('sell_or_eat_by_mandatory');
|
|
$object->batch_mask = GETPOST('batch_mask', 'alpha');
|
|
$object->fk_default_warehouse = GETPOSTINT('fk_default_warehouse');
|
|
$object->fk_default_workstation = GETPOSTINT('fk_default_workstation');
|
|
// removed from update view so GETPOST always empty
|
|
/*
|
|
$object->seuil_stock_alerte = GETPOST('seuil_stock_alerte');
|
|
$object->desiredstock = GETPOST('desiredstock');
|
|
*/
|
|
$object->duration_value = $duration_value;
|
|
$object->duration_unit = $duration_unit;
|
|
|
|
$object->canvas = GETPOST('canvas');
|
|
$object->net_measure = GETPOST('net_measure');
|
|
$object->net_measure_units = GETPOST('net_measure_units') === '' ? null : GETPOSTINT('net_measure_units'); // This is not the fk_unit but the power of unit
|
|
$object->weight = GETPOST('weight');
|
|
$object->weight_units = GETPOST('weight_units') === '' ? null : GETPOSTINT('weight_units'); // This is not the fk_unit but the power of unit
|
|
$object->length = GETPOST('size');
|
|
$object->length_units = GETPOST('size_units') === '' ? null : GETPOSTINT('size_units'); // This is not the fk_unit but the power of unit
|
|
$object->width = GETPOST('sizewidth');
|
|
$object->height = GETPOST('sizeheight');
|
|
|
|
$object->surface = GETPOST('surface');
|
|
$object->surface_units = GETPOST('surface_units') === '' ? null : GETPOSTINT('surface_units'); // This is not the fk_unit but the power of unit
|
|
$object->volume = GETPOST('volume');
|
|
$object->volume_units = GETPOST('volume_units') === '' ? null : GETPOSTINT('volume_units'); // This is not the fk_unit but the power of unit
|
|
|
|
$finished = GETPOSTINT('finished');
|
|
if ($finished >= 0) {
|
|
$object->finished = $finished;
|
|
} else {
|
|
$object->finished = null;
|
|
}
|
|
|
|
$fk_default_bom = GETPOSTINT('fk_default_bom');
|
|
if ($fk_default_bom >= 0) {
|
|
$object->fk_default_bom = $fk_default_bom;
|
|
} else {
|
|
$object->fk_default_bom = 0;
|
|
}
|
|
|
|
// managed_in_stock
|
|
$object->stockable_product = (int) GETPOSTISSET('stockable_product');
|
|
if ($object->status_batch > 0 && $object->stockable_product == 0 && isModEnabled('stock') && isModEnabled('productbatch')) {
|
|
$object->stockable_product = 1;
|
|
setEventMessages($langs->trans('ForceBatchesNeedStockManagement'), null, 'warnings');
|
|
}
|
|
|
|
$units = GETPOSTINT('units');
|
|
if ($units > 0) {
|
|
$object->fk_unit = $units;
|
|
} else {
|
|
$object->fk_unit = null;
|
|
}
|
|
|
|
$object->barcode_type = GETPOSTINT('fk_barcode_type');
|
|
$object->barcode = GETPOST('barcode');
|
|
// Set barcode_type_xxx from barcode_type id
|
|
$stdobject = new GenericObject($db);
|
|
$stdobject->element = 'product';
|
|
$stdobject->barcode_type = GETPOSTINT('fk_barcode_type');
|
|
$result = $stdobject->fetchBarCode();
|
|
if ($result < 0) {
|
|
$error++;
|
|
$mesg = 'Failed to get bar code type information ';
|
|
setEventMessages($mesg.$stdobject->error, $stdobject->errors, 'errors');
|
|
}
|
|
$object->barcode_type_code = $stdobject->barcode_type_code;
|
|
$object->barcode_type_coder = $stdobject->barcode_type_coder;
|
|
$object->barcode_type_label = $stdobject->barcode_type_label;
|
|
|
|
$accountancy_code_sell = GETPOST('accountancy_code_sell', 'alpha');
|
|
$accountancy_code_sell_intra = GETPOST('accountancy_code_sell_intra', 'alpha');
|
|
$accountancy_code_sell_export = GETPOST('accountancy_code_sell_export', 'alpha');
|
|
$accountancy_code_buy = GETPOST('accountancy_code_buy', 'alpha');
|
|
$accountancy_code_buy_intra = GETPOST('accountancy_code_buy_intra', 'alpha');
|
|
$accountancy_code_buy_export = GETPOST('accountancy_code_buy_export', 'alpha');
|
|
$checkmandatory = GETPOST('mandatoryperiod', 'alpha');
|
|
if (empty($accountancy_code_sell) || $accountancy_code_sell == '-1') {
|
|
$object->accountancy_code_sell = '';
|
|
} else {
|
|
$object->accountancy_code_sell = $accountancy_code_sell;
|
|
}
|
|
if (empty($accountancy_code_sell_intra) || $accountancy_code_sell_intra == '-1') {
|
|
$object->accountancy_code_sell_intra = '';
|
|
} else {
|
|
$object->accountancy_code_sell_intra = $accountancy_code_sell_intra;
|
|
}
|
|
if (empty($accountancy_code_sell_export) || $accountancy_code_sell_export == '-1') {
|
|
$object->accountancy_code_sell_export = '';
|
|
} else {
|
|
$object->accountancy_code_sell_export = $accountancy_code_sell_export;
|
|
}
|
|
if (empty($accountancy_code_buy) || $accountancy_code_buy == '-1') {
|
|
$object->accountancy_code_buy = '';
|
|
} else {
|
|
$object->accountancy_code_buy = $accountancy_code_buy;
|
|
}
|
|
if (empty($accountancy_code_buy_intra) || $accountancy_code_buy_intra == '-1') {
|
|
$object->accountancy_code_buy_intra = '';
|
|
} else {
|
|
$object->accountancy_code_buy_intra = $accountancy_code_buy_intra;
|
|
}
|
|
if (empty($accountancy_code_buy_export) || $accountancy_code_buy_export == '-1') {
|
|
$object->accountancy_code_buy_export = '';
|
|
} else {
|
|
$object->accountancy_code_buy_export = $accountancy_code_buy_export;
|
|
}
|
|
if ($object->isService()) {
|
|
$object->mandatory_period = (!empty($checkmandatory)) ? 1 : 0 ;
|
|
}
|
|
|
|
|
|
// Fill array 'array_options' with data from add form
|
|
$ret = $extrafields->setOptionalsFromPost(null, $object, '@GETPOSTISSET');
|
|
if ($ret < 0) {
|
|
$error++;
|
|
}
|
|
|
|
if (!$error && $object->check()) {
|
|
if ($object->update($object->id, $user) > 0) {
|
|
// Category association
|
|
$categories = GETPOST('categories', 'array:int');
|
|
$object->setCategories($categories);
|
|
|
|
$action = 'view';
|
|
} else {
|
|
if (count($object->errors)) {
|
|
setEventMessages($object->error, $object->errors, 'errors');
|
|
} else {
|
|
setEventMessages($langs->trans($object->error), null, 'errors');
|
|
}
|
|
$action = 'edit';
|
|
}
|
|
} else {
|
|
if (count($object->errors)) {
|
|
setEventMessages($object->error, $object->errors, 'errors');
|
|
} else {
|
|
setEventMessages($langs->trans("ErrorProductBadRefOrLabel"), null, 'errors');
|
|
}
|
|
$action = 'edit';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Action clone object
|
|
if ($action == 'confirm_clone' && $confirm != 'yes') { // Test on permission not required
|
|
$action = '';
|
|
}
|
|
if ($action == 'confirm_clone' && $confirm == 'yes' && $usercancreate) {
|
|
if (!GETPOST('clone_content') && !GETPOST('clone_prices')) {
|
|
setEventMessages($langs->trans("NoCloneOptionsSpecified"), null, 'errors');
|
|
} else {
|
|
if ($object->id > 0) {
|
|
$error = 0;
|
|
// We clone object to avoid to denaturate loaded object when setting some properties for clone or if createFromClone modifies the object.
|
|
// We use native clone to keep this->db valid and allow to use later all the methods of object.
|
|
$clone = dol_clone($object, 1);
|
|
|
|
$clone->id = 0;
|
|
unset($clone->date_creation);
|
|
$clone->ref = GETPOST('clone_ref', 'alphanohtml');
|
|
$clone->status = 0;
|
|
$clone->status_buy = 0;
|
|
$clone->barcode = -1;
|
|
|
|
if ($clone->check()) {
|
|
$db->begin();
|
|
|
|
$clone->context['createfromclone'] = 'createfromclone';
|
|
$id = $clone->create($user);
|
|
if ($id > 0) {
|
|
if (GETPOST('clone_composition')) {
|
|
$result = $clone->clone_associations($object->id, $id);
|
|
if ($result < 1) {
|
|
setEventMessages($langs->trans('ErrorProductClone'), null, 'errors');
|
|
setEventMessages($clone->error, $clone->errors, 'errors');
|
|
$error++;
|
|
}
|
|
}
|
|
|
|
if (!$error && GETPOST('clone_categories')) {
|
|
$result = $clone->cloneCategories($object->id, $id);
|
|
if ($result < 1) {
|
|
setEventMessages($langs->trans('ErrorProductClone'), null, 'errors');
|
|
setEventMessages($clone->error, $clone->errors, 'errors');
|
|
$error++;
|
|
}
|
|
}
|
|
|
|
if (!$error && GETPOST('clone_prices')) {
|
|
$result = $clone->clone_price($object->id, $id);
|
|
if ($result < 1) {
|
|
setEventMessages($langs->trans('ErrorProductClone'), null, 'errors');
|
|
setEventMessages($clone->error, $clone->errors, 'errors');
|
|
$error++;
|
|
}
|
|
}
|
|
|
|
if (!$error && isModEnabled('bom') && $user->hasRight('bom', 'write')) {
|
|
$defbomidac = 0; // to avoid cloning same BOM twice
|
|
if (GETPOST('clone_defbom') && $object->fk_default_bom > 0) {
|
|
$bomstatic = new BOM($db);
|
|
$bomclone = $bomstatic->createFromClone($user, $object->fk_default_bom);
|
|
if ((int) $bomclone < 0) {
|
|
setEventMessages($langs->trans('ErrorProductClone').' : '.$langs->trans('ErrorProductCloneBom'), null, 'warnings');
|
|
} else {
|
|
$defbomidac = $object->fk_default_bom;
|
|
$clone->fk_default_bom = $bomclone->id;
|
|
$clone->update($id, $user);
|
|
$bomclone->fk_product = $id;
|
|
$bomclone->label = $langs->trans('BOMofRef', $clone->ref);
|
|
$bomclone->update($user);
|
|
$bomclone->validate($user);
|
|
}
|
|
}
|
|
if (GETPOST('clone_otherboms')) {
|
|
$bomstatic = new BOM($db);
|
|
$bomlist = $bomstatic->fetchAll("", "", 0, 0, 'fk_product:=:'.(int) $object->id);
|
|
if (is_array($bomlist)) {
|
|
foreach ($bomlist as $bom2clone) {
|
|
if ($bom2clone->id != $defbomidac) { // to avoid cloning same BOM twice
|
|
$bomclone = $bomstatic->createFromClone($user, $bom2clone->id);
|
|
if ((int) $bomclone < 0) {
|
|
setEventMessages($langs->trans('ErrorProductClone').' : '.$langs->trans('ErrorProductCloneBom'), null, 'warnings');
|
|
} else {
|
|
$bomclone->fk_product = $id;
|
|
$bomclone->label = $langs->trans('BOMofRef', $clone->ref);
|
|
$bomclone->update($user);
|
|
$bomclone->validate($user);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// $clone->clone_fournisseurs($object->id, $id);
|
|
} else {
|
|
if ($clone->error == 'ErrorProductAlreadyExists') {
|
|
$refalreadyexists++;
|
|
$action = "";
|
|
|
|
$mesg = $langs->trans("ErrorProductAlreadyExists", $clone->ref);
|
|
$mesg .= ' <a href="' . $_SERVER["PHP_SELF"] . '?ref=' . $clone->ref . '">' . $langs->trans("ShowCardHere") . '</a>.';
|
|
setEventMessages($mesg, null, 'errors');
|
|
} else {
|
|
setEventMessages(empty($clone->error) ? '' : $langs->trans($clone->error), $clone->errors, 'errors');
|
|
}
|
|
$error++;
|
|
}
|
|
|
|
unset($clone->context['createfromclone']);
|
|
|
|
if ($error) {
|
|
$db->rollback();
|
|
} else {
|
|
$db->commit();
|
|
$db->close();
|
|
header("Location: " . $_SERVER["PHP_SELF"] . "?id=" . $id);
|
|
exit;
|
|
}
|
|
} else {
|
|
setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("NewRefForClone")), null, 'errors');
|
|
}
|
|
} else {
|
|
dol_print_error($db, $object->error, $object->errors);
|
|
}
|
|
}
|
|
$action = 'clone';
|
|
}
|
|
|
|
// Delete a product
|
|
if ($action == 'confirm_delete' && $confirm != 'yes') { // Test on permission not required
|
|
$action = '';
|
|
}
|
|
if ($action == 'confirm_delete' && $confirm == 'yes' && $usercandelete) {
|
|
$result = $object->delete($user);
|
|
|
|
if ($result > 0) {
|
|
header('Location: '.DOL_URL_ROOT.'/product/list.php?type='.$object->type.'&delprod='.urlencode($object->ref));
|
|
exit;
|
|
} else {
|
|
setEventMessages($langs->trans($object->error), null, 'errors');
|
|
$reload = 0;
|
|
$action = '';
|
|
}
|
|
}
|
|
|
|
|
|
// Add product into object (when PRODUCT_ADD_FORM_ADD_TO is set)
|
|
if ($object->id > 0 && $action == 'addin') { // Test on permission not required here. it is done later according to object.
|
|
$thirdpartyid = 0;
|
|
$permissiontoaddline = false;
|
|
$propal = null;
|
|
$facture = null;
|
|
$commande = null;
|
|
|
|
// Get object and test permission
|
|
if (GETPOSTINT('propalid') > 0) {
|
|
$propal = new Propal($db);
|
|
$result = $propal->fetch(GETPOSTINT('propalid'));
|
|
if ($result <= 0) {
|
|
dol_print_error($db, $propal->error);
|
|
exit;
|
|
}
|
|
$thirdpartyid = $propal->socid;
|
|
$permissiontoaddline = $user->hasRight('propal', 'creer');
|
|
} elseif (GETPOSTINT('commandeid') > 0) {
|
|
$commande = new Commande($db);
|
|
$result = $commande->fetch(GETPOSTINT('commandeid'));
|
|
if ($result <= 0) {
|
|
dol_print_error($db, $commande->error);
|
|
exit;
|
|
}
|
|
$thirdpartyid = $commande->socid;
|
|
$permissiontoaddline = $user->hasRight('commande', 'creer');
|
|
} elseif (GETPOSTINT('factureid') > 0) {
|
|
$facture = new Facture($db);
|
|
$result = $facture->fetch(GETPOSTINT('factureid'));
|
|
if ($result <= 0) {
|
|
dol_print_error($db, $facture->error);
|
|
exit;
|
|
}
|
|
$thirdpartyid = $facture->socid;
|
|
$permissiontoaddline = $user->hasRight('facture', 'creer');
|
|
}
|
|
|
|
if ($thirdpartyid > 0) {
|
|
$soc = new Societe($db);
|
|
$result = $soc->fetch($thirdpartyid);
|
|
if ($result <= 0) {
|
|
dol_print_error($db, $soc->error);
|
|
exit;
|
|
}
|
|
|
|
$desc = $object->description;
|
|
|
|
$tva_tx = get_default_tva($mysoc, $soc, $object->id);
|
|
$tva_npr = get_default_npr($mysoc, $soc, $object->id);
|
|
if (empty($tva_tx)) {
|
|
$tva_npr = 0;
|
|
}
|
|
$localtax1_tx = get_localtax($tva_tx, 1, $soc, $mysoc, $tva_npr);
|
|
$localtax2_tx = get_localtax($tva_tx, 2, $soc, $mysoc, $tva_npr);
|
|
|
|
$pu_ht = $object->price;
|
|
$pu_ttc = $object->price_ttc;
|
|
$price_base_type = $object->price_base_type;
|
|
|
|
// If multiprice
|
|
if (getDolGlobalString('PRODUIT_MULTIPRICES') && $soc->price_level) {
|
|
$pu_ht = $object->multiprices[$soc->price_level];
|
|
$pu_ttc = $object->multiprices_ttc[$soc->price_level];
|
|
$price_base_type = $object->multiprices_base_type[$soc->price_level];
|
|
} elseif (getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
|
|
require_once DOL_DOCUMENT_ROOT.'/product/class/productcustomerprice.class.php';
|
|
|
|
$prodcustprice = new ProductCustomerPrice($db);
|
|
|
|
$filter = array('t.fk_product' => (string) $object->id, 't.fk_soc' => (string) $soc->id);
|
|
|
|
$result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
|
|
if ($result) {
|
|
if (count($prodcustprice->lines) > 0) {
|
|
$date_now = (int) floor(dol_now() / 86400) * 86400; // date without hours
|
|
foreach ($prodcustprice->lines as $k => $custprice_line) {
|
|
if ($custprice_line->date_begin <= $date_now && (empty($custprice_line->date_end) || $date_now <= $custprice_line->date_end)) {
|
|
$pu_ht = price($custprice_line->price);
|
|
$pu_ttc = price($custprice_line->price_ttc);
|
|
$price_base_type = $custprice_line->price_base_type;
|
|
$tva_tx = $custprice_line->tva_tx;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$tmpvat = price2num(preg_replace('/\s*\(.*\)/', '', $tva_tx));
|
|
$tmpprodvat = price2num(preg_replace('/\s*\(.*\)/', '', $object->tva_tx));
|
|
|
|
// We reevaluate price according to vat rate because vat rate of transaction can be different
|
|
// of the one of the product by default (for example when country of seller and buyer differ).
|
|
if ($tmpvat != $tmpprodvat) {
|
|
if ($price_base_type != 'HT') {
|
|
$pu_ht = price2num($pu_ttc / (1 + ((float) $tmpvat / 100)), 'MU');
|
|
} else {
|
|
$pu_ttc = price2num($pu_ht * (1 + ((float) $tmpvat / 100)), 'MU');
|
|
}
|
|
}
|
|
|
|
if (GETPOSTINT('propalid') > 0 && $permissiontoaddline && is_object($propal)) {
|
|
// Define cost price for margin calculation
|
|
$buyprice = 0;
|
|
if (($result = $propal->defineBuyPrice($pu_ht, (float) price2num(GETPOST('remise_percent'), '', 2), $object->id)) < 0) {
|
|
dol_syslog($langs->trans('FailedToGetCostPrice'));
|
|
setEventMessages($langs->trans('FailedToGetCostPrice'), null, 'errors');
|
|
} else {
|
|
$buyprice = $result;
|
|
}
|
|
|
|
$result = $propal->addline(
|
|
$desc,
|
|
$pu_ht,
|
|
(float) price2num(GETPOST('qty'), 'MS'),
|
|
$tva_tx,
|
|
$localtax1_tx, // localtax1
|
|
$localtax2_tx, // localtax2
|
|
$object->id,
|
|
(float) price2num(GETPOST('remise_percent'), '', 2),
|
|
$price_base_type,
|
|
$pu_ttc,
|
|
0,
|
|
0,
|
|
-1,
|
|
0,
|
|
0,
|
|
0,
|
|
$buyprice,
|
|
'',
|
|
'',
|
|
'',
|
|
array(),
|
|
$object->fk_unit
|
|
);
|
|
if ($result > 0) {
|
|
header("Location: ".DOL_URL_ROOT."/comm/propal/card.php?id=".$propal->id);
|
|
return;
|
|
}
|
|
|
|
setEventMessages($langs->trans("ErrorUnknown").": $result", null, 'errors');
|
|
} elseif (GETPOST('commandeid') > 0 && $permissiontoaddline && is_object($commande)) {
|
|
// Define cost price for margin calculation
|
|
$buyprice = 0;
|
|
if (($result = $commande->defineBuyPrice($pu_ht, (float) price2num(GETPOST('remise_percent'), '', 2), $object->id)) < 0) {
|
|
dol_syslog($langs->trans('FailedToGetCostPrice'));
|
|
setEventMessages($langs->trans('FailedToGetCostPrice'), null, 'errors');
|
|
} else {
|
|
$buyprice = $result;
|
|
}
|
|
|
|
$result = $commande->addline(
|
|
$desc,
|
|
(float) $pu_ht,
|
|
(float) price2num(GETPOST('qty'), 'MS'),
|
|
$tva_tx,
|
|
$localtax1_tx, // localtax1
|
|
$localtax2_tx, // localtax2
|
|
$object->id,
|
|
(float) price2num(GETPOST('remise_percent'), '', 2),
|
|
0,
|
|
0,
|
|
$price_base_type,
|
|
$pu_ttc,
|
|
'',
|
|
'',
|
|
0,
|
|
-1,
|
|
0,
|
|
0,
|
|
0,
|
|
$buyprice,
|
|
'',
|
|
array(),
|
|
$object->fk_unit
|
|
);
|
|
|
|
if ($result > 0) {
|
|
header("Location: ".DOL_URL_ROOT."/commande/card.php?id=".urlencode((string) ($commande->id)));
|
|
exit;
|
|
}
|
|
|
|
setEventMessages($langs->trans("ErrorUnknown").": $result", null, 'errors');
|
|
} elseif (GETPOST('factureid') > 0 && $permissiontoaddline && is_object($facture)) {
|
|
// Define cost price for margin calculation
|
|
$buyprice = 0;
|
|
if (($result = $facture->defineBuyPrice($pu_ht, (float) price2num(GETPOST('remise_percent'), '', 2), $object->id)) < 0) {
|
|
dol_syslog($langs->trans('FailedToGetCostPrice'));
|
|
setEventMessages($langs->trans('FailedToGetCostPrice'), null, 'errors');
|
|
} else {
|
|
$buyprice = $result;
|
|
}
|
|
|
|
$result = $facture->addline(
|
|
$desc,
|
|
(float) $pu_ht,
|
|
(float) price2num(GETPOST('qty'), 'MS'),
|
|
$tva_tx,
|
|
$localtax1_tx,
|
|
$localtax2_tx,
|
|
$object->id,
|
|
(float) price2num(GETPOST('remise_percent'), '', 2),
|
|
'',
|
|
'',
|
|
0,
|
|
0,
|
|
0,
|
|
$price_base_type,
|
|
$pu_ttc,
|
|
Facture::TYPE_STANDARD,
|
|
-1,
|
|
0,
|
|
'',
|
|
0,
|
|
0,
|
|
0,
|
|
$buyprice,
|
|
'',
|
|
array(),
|
|
100,
|
|
0,
|
|
$object->fk_unit
|
|
);
|
|
|
|
if ($result > 0) {
|
|
header("Location: ".DOL_URL_ROOT."/compta/facture/card.php?facid=".$facture->id);
|
|
exit;
|
|
}
|
|
|
|
setEventMessages($langs->trans("ErrorUnknown").": $result", null, 'errors');
|
|
}
|
|
} else {
|
|
$action = "";
|
|
setEventMessages($langs->trans("WarningSelectOneDocument"), null, 'warnings');
|
|
}
|
|
}
|
|
|
|
// Actions when printing a doc from card
|
|
include DOL_DOCUMENT_ROOT.'/core/actions_printing.inc.php';
|
|
|
|
// Actions to send emails
|
|
$triggersendname = 'PRODUCT_SENTBYMAIL';
|
|
$paramname = 'id';
|
|
$autocopy = 'MAIN_MAIL_AUTOCOPY_PRODUCT_TO';
|
|
$trackid = 'prod'.$object->id;
|
|
include DOL_DOCUMENT_ROOT.'/core/actions_sendmails.inc.php';
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* View
|
|
*/
|
|
|
|
$form = new Form($db);
|
|
$formfile = new FormFile($db);
|
|
$formproduct = new FormProduct($db);
|
|
$formcompany = new FormCompany($db);
|
|
$formaccounting = null;
|
|
if (isModEnabled('accounting')) {
|
|
$formaccounting = new FormAccounting($db);
|
|
}
|
|
$sellOrEatByMandatoryList = null;
|
|
if (isModEnabled('productbatch')) {
|
|
$sellOrEatByMandatoryList = Product::getSellOrEatByMandatoryList();
|
|
|
|
$disableSellBy = getDolGlobalString('PRODUCT_DISABLE_SELLBY');
|
|
$disableEatBy = getDolGlobalString('PRODUCT_DISABLE_EATBY');
|
|
|
|
if ($disableSellBy) {
|
|
unset($sellOrEatByMandatoryList[Product::SELL_OR_EAT_BY_MANDATORY_ID_SELL_BY]);
|
|
unset($sellOrEatByMandatoryList[Product::SELL_OR_EAT_BY_MANDATORY_ID_SELL_AND_EAT]);
|
|
}
|
|
if ($disableEatBy) {
|
|
unset($sellOrEatByMandatoryList[Product::SELL_OR_EAT_BY_MANDATORY_ID_EAT_BY]);
|
|
unset($sellOrEatByMandatoryList[Product::SELL_OR_EAT_BY_MANDATORY_ID_SELL_AND_EAT]);
|
|
}
|
|
}
|
|
|
|
$title = $langs->trans('ProductServiceCard');
|
|
|
|
$help_url = '';
|
|
$shortlabel = dol_trunc($object->label, 16);
|
|
if (GETPOST("type") == '0' || ($object->type == Product::TYPE_PRODUCT)) {
|
|
if ($action == 'create') {
|
|
$title = $langs->trans("NewProduct");
|
|
} else {
|
|
$title = $langs->trans('Product')." ".$shortlabel." - ".$langs->trans('Card');
|
|
$help_url = 'EN:Module_Products|FR:Module_Produits|ES:Módulo_Productos|DE:Modul_Produkte';
|
|
}
|
|
}
|
|
if (GETPOST("type") == '1' || ($object->type == Product::TYPE_SERVICE)) {
|
|
if ($action == 'create') {
|
|
$title = $langs->trans("NewService");
|
|
} else {
|
|
$title = $langs->trans('Service')." ".$shortlabel." - ".$langs->trans('Card');
|
|
$help_url = 'EN:Module_Services_En|FR:Module_Services|ES:Módulo_Servicios|DE:Modul_Leistungen';
|
|
}
|
|
}
|
|
|
|
llxHeader('', $title, $help_url, '', 0, 0, '', '', '', 'mod-product page-card');
|
|
|
|
// Load object modBarCodeProduct
|
|
$res = 0;
|
|
$modBarCodeProduct = null;
|
|
if (isModEnabled('barcode') && getDolGlobalString('BARCODE_USE_ON_PRODUCT') && getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM')) {
|
|
$module = strtolower(getDolGlobalString('BARCODE_PRODUCT_ADDON_NUM'));
|
|
$dirbarcode = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
|
|
foreach ($dirbarcode as $dirroot) {
|
|
$res = dol_include_once($dirroot.$module.'.php');
|
|
if ($res) {
|
|
break;
|
|
}
|
|
}
|
|
if ($res > 0) {
|
|
$modBarCodeProduct = new $module();
|
|
'@phan-var-force ModeleNumRefBarCode $modBarCodeProduct';
|
|
}
|
|
}
|
|
|
|
$canvasdisplayaction = $action;
|
|
if (in_array($canvasdisplayaction, array('merge', 'confirm_merge'))) {
|
|
$canvasdisplayaction = 'view';
|
|
}
|
|
|
|
if (is_object($objcanvas) && $objcanvas->displayCanvasExists($canvasdisplayaction)) {
|
|
// -----------------------------------------
|
|
// When used with CANVAS
|
|
// -----------------------------------------
|
|
$objcanvas->assign_values($canvasdisplayaction, $object->id, $object->ref); // Set value for templates
|
|
$objcanvas->display_canvas($canvasdisplayaction); // Show template
|
|
} else {
|
|
// -----------------------------------------
|
|
// When used in standard mode
|
|
// -----------------------------------------
|
|
if ($action == 'create' && $usercancreate) {
|
|
//WYSIWYG Editor
|
|
require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
|
|
|
|
if (!empty($conf->use_javascript_ajax)) {
|
|
print '<script type="text/javascript">';
|
|
print '$(document).ready(function () {
|
|
$("#selectcountry_id").change(function() {
|
|
console.log("selectcountry_id change");
|
|
document.formprod.action.value="create";
|
|
document.formprod.submit();
|
|
});
|
|
});';
|
|
print '</script>'."\n";
|
|
}
|
|
|
|
// Load object modCodeProduct
|
|
$module = getDolGlobalString('PRODUCT_CODEPRODUCT_ADDON', 'mod_codeproduct_leopard');
|
|
if (substr($module, 0, 16) == 'mod_codeproduct_' && substr($module, -3) == 'php') {
|
|
$module = substr($module, 0, dol_strlen($module) - 4);
|
|
}
|
|
$result = dol_include_once('/core/modules/product/'.$module.'.php');
|
|
if ($result > 0) {
|
|
$modCodeProduct = new $module();
|
|
/** @var ModeleProductCode $modCodeProduct */
|
|
}
|
|
|
|
dol_set_focus('input[name="ref"]');
|
|
|
|
print '<form action="'.$_SERVER["PHP_SELF"].'" method="POST" name="formprod">';
|
|
print '<input type="hidden" name="token" value="'.newToken().'">';
|
|
print '<input type="hidden" name="action" value="add">';
|
|
print '<input type="hidden" name="type" value="'.$type.'">'."\n";
|
|
if (!empty($modCodeProduct->code_auto)) {
|
|
print '<input type="hidden" name="code_auto" value="1">';
|
|
}
|
|
if (!empty($modBarCodeProduct->code_auto)) {
|
|
print '<input type="hidden" name="barcode_auto" value="1">';
|
|
}
|
|
print '<input type="hidden" name="backtopage" value="'.$backtopage.'">';
|
|
print '<input type="hidden" name="dol_openinpopup" value="'.dol_escape_htmltag($dol_openinpopup).'">';
|
|
|
|
if ($type == 1) {
|
|
$picto = 'service';
|
|
$title = $langs->trans("NewService");
|
|
} else {
|
|
$picto = 'product';
|
|
$title = $langs->trans("NewProduct");
|
|
}
|
|
$linkback = "";
|
|
print load_fiche_titre($title, $linkback, $picto);
|
|
|
|
// We set country_id, country_code and country for the selected country
|
|
$object->country_id = GETPOSTISSET('country_id') ? GETPOSTINT('country_id') : null;
|
|
if ($object->country_id > 0) {
|
|
$tmparray = getCountry($object->country_id, 'all');
|
|
$object->country_code = $tmparray['code'];
|
|
$object->country = $tmparray['label'];
|
|
}
|
|
|
|
print dol_get_fiche_head();
|
|
|
|
// Call Hook tabContentCreateProduct
|
|
$parameters = array();
|
|
// Note that $action and $object may be modified by hook
|
|
$reshook = $hookmanager->executeHooks('tabContentCreateProduct', $parameters, $object, $action);
|
|
if (empty($reshook)) {
|
|
print '<table class="border centpercent">';
|
|
|
|
if (!getDolGlobalString('PRODUCT_GENERATE_REF_AFTER_FORM')) {
|
|
print '<tr>';
|
|
$tmpcode = '';
|
|
if (!empty($modCodeProduct->code_auto)) {
|
|
/** @var ModeleProductCode $modCodeProduct */
|
|
$tmpcode = $modCodeProduct->getNextValue($object, $type);
|
|
}
|
|
print '<td class="titlefieldcreate fieldrequired">'.$langs->trans($refLabelKey).'</td><td><input id="ref" name="ref" class="maxwidth200" maxlength="128" value="'.dol_escape_htmltag(GETPOSTISSET('ref') ? GETPOST('ref', 'alphanohtml') : $tmpcode).'">';
|
|
if ($refalreadyexists) {
|
|
print $langs->trans("RefAlreadyExists");
|
|
}
|
|
print '</td></tr>';
|
|
}
|
|
|
|
// Label
|
|
print '<tr><td class="fieldrequired">'.$langs->trans("Label").'</td><td><input id="label" name="label" class="minwidth300 maxwidth400onsmartphone" maxlength="255" value="'.dol_escape_htmltag(GETPOST('label', $label_security_check)).'"></td></tr>';
|
|
|
|
// On sell
|
|
print '<tr><td class="fieldrequired">'.$langs->trans("Status").' ('.$langs->trans("Sell").')</td><td>';
|
|
$statutarray = array('1' => $langs->trans("OnSell"), '0' => $langs->trans("NotOnSell"));
|
|
print $form->selectarray('statut', $statutarray, GETPOST('statut'));
|
|
print '</td></tr>';
|
|
|
|
// To buy
|
|
print '<tr><td class="fieldrequired">'.$langs->trans("Status").' ('.$langs->trans("Buy").')</td><td>';
|
|
$statutarray = array('1' => $langs->trans("ProductStatusOnBuy"), '0' => $langs->trans("ProductStatusNotOnBuy"));
|
|
print $form->selectarray('statut_buy', $statutarray, GETPOST('statut_buy'));
|
|
print '</td></tr>';
|
|
|
|
// Batch number management
|
|
if (isModEnabled('productbatch')) {
|
|
print '<tr><td>'.$langs->trans("ManageLotSerial").'</td><td>';
|
|
$statutarray = array('0' => $langs->trans("ProductStatusNotOnBatch"), '1' => $langs->trans("ProductStatusOnBatch"), '2' => $langs->trans("ProductStatusOnSerial"));
|
|
print $form->selectarray('status_batch', $statutarray, GETPOSTINT('status_batch'));
|
|
print '</td></tr>';
|
|
// Product specific batch number management
|
|
$status_batch = GETPOSTINT('status_batch');
|
|
if ($status_batch !== '0') {
|
|
$langs->load("admin");
|
|
$tooltip = $langs->trans("GenericMaskCodes", $langs->transnoentities("Batch"), $langs->transnoentities("Batch"));
|
|
$tooltip .= $langs->trans("GenericMaskCodes1");
|
|
$tooltip .= '<br>'.$langs->trans("GenericMaskCodes2");
|
|
$tooltip .= '<br>'.$langs->trans("GenericMaskCodes3");
|
|
$tooltip .= '<br>'.$langs->trans("GenericMaskCodes4a", $langs->transnoentities("Batch"), $langs->transnoentities("Batch"));
|
|
$tooltip .= '<br>'.$langs->trans("GenericMaskCodes5");
|
|
//$tooltip .= '<br>'.$langs->trans("GenericMaskCodes5b");
|
|
|
|
if ((getDolGlobalString('PRODUCTBATCH_LOT_ADDON') == 'mod_lot_advanced')
|
|
|| (getDolGlobalString('PRODUCTBATCH_SN_ADDON') == 'mod_sn_advanced')) {
|
|
print '<tr><td id="mask_option">'.$langs->trans("ManageLotMask").'</td>';
|
|
$inherited_mask_lot = getDolGlobalString('LOT_ADVANCED_MASK');
|
|
$inherited_mask_sn = getDolGlobalString('SN_ADVANCED_MASK');
|
|
print '<td id="field_mask">';
|
|
print $form->textwithpicto('<input type="text" class="flat minwidth175" name="batch_mask" id="batch_mask_input">', $tooltip, 1, 'help');
|
|
print '<script type="text/javascript">
|
|
$(document).ready(function() {
|
|
$("#field_mask, #mask_option").addClass("hideobject");
|
|
$("#status_batch").on("change", function () {
|
|
console.log("We change batch status");
|
|
var optionSelected = $("option:selected", this);
|
|
var valueSelected = this.value;
|
|
$("#field_mask, #mask_option").addClass("hideobject");
|
|
';
|
|
if (getDolGlobalString('PRODUCTBATCH_LOT_USE_PRODUCT_MASKS') && getDolGlobalString('PRODUCTBATCH_LOT_ADDON') == 'mod_lot_advanced') {
|
|
print '
|
|
if (this.value == 1) {
|
|
$("#field_mask, #mask_option").toggleClass("hideobject");
|
|
$("#batch_mask_input").val("'.$inherited_mask_lot.'");
|
|
}
|
|
';
|
|
}
|
|
if (getDolGlobalString('PRODUCTBATCH_SN_USE_PRODUCT_MASKS') && getDolGlobalString('PRODUCTBATCH_SN_ADDON') == 'mod_sn_advanced') {
|
|
print '
|
|
if (this.value == 2) {
|
|
$("#field_mask, #mask_option").toggleClass("hideobject");
|
|
$("#batch_mask_input").val("'.$inherited_mask_sn.'");
|
|
}
|
|
';
|
|
}
|
|
print '
|
|
})
|
|
})
|
|
</script>';
|
|
print '</td></tr>';
|
|
}
|
|
}
|
|
|
|
// SellBy / EatBy mandatory list
|
|
if (!empty($sellOrEatByMandatoryList)) {
|
|
print '<tr><td>'.$langs->trans('BatchSellOrEatByMandatoryList', $langs->transnoentities('SellByDate'), $langs->transnoentities('EatByDate')).'</td><td>';
|
|
print $form->selectarray('sell_or_eat_by_mandatory', $sellOrEatByMandatoryList, GETPOSTINT('sell_or_eat_by_mandatory'));
|
|
print '</td></tr>';
|
|
}
|
|
}
|
|
|
|
$showbarcode = (isModEnabled('barcode') && getDolGlobalString('BARCODE_USE_ON_PRODUCT'));
|
|
if (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && !$user->hasRight('barcode', 'lire_advance')) {
|
|
$showbarcode = 0;
|
|
}
|
|
|
|
if ($showbarcode) {
|
|
//var_dump($modBarCodeProduct); exit;
|
|
|
|
print '<tr><td>'.$langs->trans('BarcodeType').'</td><td>';
|
|
if (GETPOSTISSET('fk_barcode_type')) {
|
|
$fk_barcode_type = GETPOST('fk_barcode_type') ? GETPOST('fk_barcode_type') : 0;
|
|
} else {
|
|
if (empty($fk_barcode_type) && getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE')) {
|
|
$fk_barcode_type = getDolGlobalInt("PRODUIT_DEFAULT_BARCODE_TYPE");
|
|
} else {
|
|
$fk_barcode_type = 0;
|
|
}
|
|
}
|
|
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formbarcode.class.php';
|
|
$formbarcode = new FormBarCode($db);
|
|
print $formbarcode->selectBarcodeType($fk_barcode_type, 'fk_barcode_type', 1);
|
|
print '</td>';
|
|
print '</tr>';
|
|
|
|
print '<tr>';
|
|
print '<td'.((is_object($modBarCodeProduct) && $modBarCodeProduct->code_null) ? '' : ' class="fieldrequired"').'>'.$langs->trans("BarcodeValue").'</td><td>';
|
|
$tmpcode = GETPOSTISSET('barcode') ? GETPOST('barcode') : $object->barcode;
|
|
if (empty($tmpcode) && is_object($modBarCodeProduct) && !empty($modBarCodeProduct->code_auto)) {
|
|
$tmpcode = $modBarCodeProduct->getNextValue($object, $fk_barcode_type);
|
|
}
|
|
print img_picto('', 'barcode', 'class="pictofixedwidth"');
|
|
print '<input class="maxwidth150" type="text" name="barcode" value="'.dol_escape_htmltag($tmpcode).'">';
|
|
print '</td></tr>';
|
|
}
|
|
|
|
// Description (used in invoice, propal...)
|
|
print '<tr><td class="tdtop">'.$langs->trans("Description").'</td><td>';
|
|
$doleditor = new DolEditor('desc', GETPOST('desc', 'restricthtml'), '', 160, 'dolibarr_details', '', false, true, getDolGlobalString('FCKEDITOR_ENABLE_DETAILS'), ROWS_4, '90%');
|
|
$doleditor->Create();
|
|
print "</td></tr>";
|
|
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_PUBLIC_URL')) {
|
|
// Public URL
|
|
print '<tr><td>'.$langs->trans("PublicUrl").'</td><td>';
|
|
print img_picto('', 'globe', 'class="pictofixedwidth"');
|
|
print '<input type="text" name="url" class="quatrevingtpercent" value="'.GETPOST('url').'">';
|
|
print '</td></tr>';
|
|
}
|
|
|
|
if (($type != 1 || getDolGlobalInt('STOCK_SUPPORTS_SERVICES')) && isModEnabled('stock')) {
|
|
// Default warehouse
|
|
print '<tr><td>'.$langs->trans("DefaultWarehouse").'</td><td>';
|
|
print img_picto($langs->trans("DefaultWarehouse"), 'stock', 'class="pictofixedwidth"');
|
|
print $formproduct->selectWarehouses(GETPOSTINT('fk_default_warehouse'), 'fk_default_warehouse', 'warehouseopen', 1, 0, 0, '', 0, 0, array(), 'minwidth300 widthcentpercentminusxx maxwidth500');
|
|
print ' <a href="'.DOL_URL_ROOT.'/product/stock/card.php?action=create&token='.newToken().'&backtopage='.urlencode($_SERVER['PHP_SELF'].'?&action=create&type='.GETPOSTINT('type')).'">';
|
|
print '<span class="fa fa-plus-circle valignmiddle paddingleft" title="'.$langs->trans("AddWarehouse").'"></span>';
|
|
print '</a>';
|
|
|
|
print '</td>';
|
|
print '</tr>';
|
|
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_STOCK_LEVELS')) {
|
|
// Stock min level
|
|
print '<tr><td>'.$form->textwithpicto($langs->trans("StockLimit"), $langs->trans("StockLimitDesc"), 1).'</td><td>';
|
|
print '<input name="seuil_stock_alerte" class="maxwidth50" value="'.GETPOST('seuil_stock_alerte').'">';
|
|
print '</td>';
|
|
print '</tr>';
|
|
|
|
// Stock desired level
|
|
print '<tr><td>'.$form->textwithpicto($langs->trans("DesiredStock"), $langs->trans("DesiredStockDesc"), 1).'</td><td>';
|
|
print '<input name="desiredstock" class="maxwidth50" value="'.GETPOST('desiredstock').'">';
|
|
print '</td></tr>';
|
|
}
|
|
} else {
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_STOCK_LEVELS')) {
|
|
print '<input name="seuil_stock_alerte" type="hidden" value="0">';
|
|
print '<input name="desiredstock" type="hidden" value="0">';
|
|
}
|
|
}
|
|
|
|
if ($type == $object::TYPE_SERVICE && isModEnabled("workstation")) {
|
|
// Default workstation
|
|
print '<tr><td>'.$langs->trans("DefaultWorkstation").'</td><td>';
|
|
print img_picto($langs->trans("DefaultWorkstation"), 'workstation', 'class="pictofixedwidth"');
|
|
print $formproduct->selectWorkstations((GETPOSTISSET('fk_default_workstation') ? GETPOSTINT('fk_default_workstation') : ''), 'fk_default_workstation', 1);
|
|
print '</td></tr>';
|
|
}
|
|
|
|
// Duration
|
|
if ($type == 1) {
|
|
print '<tr><td>'.$langs->trans("Duration").'</td><td>';
|
|
print img_picto('', 'clock', 'class="pictofixedwidth"');
|
|
print '<input name="duration_value" class="width50 valignmiddle" value="'.(GETPOSTISSET('duration_value') ? GETPOST('duration_value') : '').'">';
|
|
print $formproduct->selectMeasuringUnits("duration_unit", "time", (GETPOSTISSET('duration_unit') ? GETPOST('duration_unit', 'alpha') : 'h'), 0, 1);
|
|
|
|
// Mandatory period
|
|
if ($object->duration_value > 0) {
|
|
print ' ';
|
|
}
|
|
print '<input type="checkbox" class="marginleftonly valignmiddle" id="mandatoryperiod" name="mandatoryperiod"'.(GETPOSTISSET('mandatoryperiod') ? ' checked="checked"' : '').'>';
|
|
print '<label for="mandatoryperiod">';
|
|
$htmltooltip = $langs->trans("mandatoryHelper");
|
|
if (!getDolGlobalString('SERVICE_STRICT_MANDATORY_PERIOD')) {
|
|
$htmltooltip .= '<br>'.$langs->trans("mandatoryHelper2");
|
|
}
|
|
print $form->textwithpicto($langs->trans("mandatoryperiod"), $htmltooltip, 1, 'info');
|
|
print '</label>';
|
|
|
|
print '</td></tr>';
|
|
}
|
|
|
|
if ($type != 1) { // Nature, Weight and volume only applies to products and not to services
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_NATURE')) {
|
|
// Nature
|
|
print '<tr><td>'.$form->textwithpicto($langs->trans("NatureOfProductShort"), $langs->trans("NatureOfProductDesc")).'</td><td>';
|
|
print $formproduct->selectProductNature('finished', (GETPOSTISSET('finished') ? GETPOST('finished') : (string) $object->finished));
|
|
print '</td></tr>';
|
|
}
|
|
}
|
|
|
|
if ($type != 1) {
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_WEIGHT')) {
|
|
// Brut Weight
|
|
print '<tr><td>'.$langs->trans("Weight").'</td><td>';
|
|
print img_picto('', 'fa-balance-scale', 'class="pictofixedwidth"');
|
|
print '<input name="weight" size="4" value="'.GETPOST('weight').'">';
|
|
print $formproduct->selectMeasuringUnits("weight_units", "weight", GETPOSTISSET('weight_units') ? GETPOST('weight_units', 'alpha') : (GETPOST('weight_units', 'alpha') ?: getDolGlobalString('MAIN_WEIGHT_DEFAULT_UNIT', 0)), 0, 2);
|
|
print '</td></tr>';
|
|
}
|
|
|
|
// Brut Length
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_SIZE')) {
|
|
print '<tr><td>'.$langs->trans("Length").' x '.$langs->trans("Width").' x '.$langs->trans("Height").'</td><td>';
|
|
print img_picto('', 'fa-ruler', 'class="pictofixedwidth"');
|
|
print '<input name="size" class="width50" value="'.GETPOST('size').'"> x ';
|
|
print '<input name="sizewidth" class="width50" value="'.GETPOST('sizewidth').'"> x ';
|
|
print '<input name="sizeheight" class="width50" value="'.GETPOST('sizeheight').'">';
|
|
print $formproduct->selectMeasuringUnits("size_units", "size", GETPOSTISSET('size_units') ? GETPOST('size_units', 'alpha') : (GETPOST('size_units', 'alpha') ?: '0'), 0, 2);
|
|
print '</td></tr>';
|
|
}
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_SURFACE')) {
|
|
// Brut Surface
|
|
print '<tr><td>'.$langs->trans("Surface").'</td><td>';
|
|
print '<input name="surface" size="4" value="'.GETPOST('surface').'">';
|
|
print $formproduct->selectMeasuringUnits("surface_units", "surface", GETPOSTISSET('surface_units') ? GETPOST('surface_units', 'alpha') : (GETPOST('surface_units', 'alpha') ?: '0'), 0, 2);
|
|
print '</td></tr>';
|
|
}
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_VOLUME')) {
|
|
// Brut Volume
|
|
print '<tr><td>'.$langs->trans("Volume").'</td><td>';
|
|
print '<input name="volume" size="4" value="'.GETPOST('volume').'">';
|
|
print $formproduct->selectMeasuringUnits("volume_units", "volume", GETPOSTISSET('volume_units') ? GETPOST('volume_units', 'alpha') : (GETPOST('volume_units', 'alpha') ?: '0'), 0, 2);
|
|
print '</td></tr>';
|
|
}
|
|
|
|
if (getDolGlobalString('PRODUCT_ADD_NET_MEASURE')) {
|
|
// Net Measure
|
|
print '<tr><td>'.$langs->trans("NetMeasure").'</td><td>';
|
|
print '<input name="net_measure" size="4" value="'.GETPOST('net_measure').'">';
|
|
print $formproduct->selectMeasuringUnits("net_measure_units", '', GETPOSTISSET('net_measure_units') ? GETPOST('net_measure_units', 'alpha') : (GETPOST('net_measure_units', 'alpha') ?: getDolGlobalString('MAIN_WEIGHT_DEFAULT_UNIT', 0)), 0, 0);
|
|
print '</td></tr>';
|
|
}
|
|
}
|
|
|
|
// Units
|
|
if (getDolGlobalString('PRODUCT_USE_UNITS')) {
|
|
print '<tr><td>'.$langs->trans('DefaultUnitToShow').'</td>';
|
|
print '<td>';
|
|
print $form->selectUnits(GETPOSTISSET('units') ? GETPOST('units', 'alpha') : (GETPOST('units', 'alpha') ?: '0'), 'units');
|
|
print '</td></tr>';
|
|
}
|
|
|
|
// Custom code
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_CUSTOMS_INFO') && empty($type)) {
|
|
print '<tr><td class="wordbreak">'.$form->textwithpicto($langs->trans("CustomsCode"), $langs->trans("CustomsCodeHelp")).'</td><td><input name="customcode" class="maxwidth100onsmartphone" value="'.GETPOST('customcode').'"></td></tr>';
|
|
|
|
// Origin country
|
|
print '<tr><td>'.$langs->trans("CountryOrigin").'</td>';
|
|
print '<td>';
|
|
print img_picto('', 'globe-americas', 'class="pictofixedwidth"');
|
|
print $form->select_country((GETPOSTISSET('country_id') ? GETPOST('country_id') : $object->country_id), 'country_id', '', 0, 'minwidth300 widthcentpercentminusx maxwidth500');
|
|
if ($user->admin) {
|
|
print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
|
|
}
|
|
print '</td></tr>';
|
|
|
|
// State
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_STATE')) {
|
|
print '<tr>';
|
|
if (getDolGlobalString('MAIN_SHOW_REGION_IN_STATE_SELECT') && (getDolGlobalInt('MAIN_SHOW_REGION_IN_STATE_SELECT') == 1 || getDolGlobalInt('MAIN_SHOW_REGION_IN_STATE_SELECT') == 2)) {
|
|
print '<td>'.$form->editfieldkey('RegionStateOrigin', 'state_id', '', $object, 0).'</td><td>';
|
|
} else {
|
|
print '<td>'.$form->editfieldkey('StateOrigin', 'state_id', '', $object, 0).'</td><td>';
|
|
}
|
|
|
|
print img_picto('', 'state', 'class="pictofixedwidth"');
|
|
print $formcompany->select_state($object->state_id, $object->country_code);
|
|
print '</tr>';
|
|
}
|
|
}
|
|
|
|
// Quality control
|
|
if (getDolGlobalString('PRODUCT_LOT_ENABLE_QUALITY_CONTROL')) {
|
|
print '<tr><td>'.$langs->trans("LifeTime").'</td><td><input name="lifetime" class="maxwidth50" value="'.GETPOST('lifetime').'"></td></tr>';
|
|
print '<tr><td>'.$langs->trans("QCFrequency").'</td><td><input name="qc_frequency" class="maxwidth50" value="'.GETPOST('qc_frequency').'"></td></tr>';
|
|
}
|
|
|
|
// Other attributes
|
|
$parameters = array('colspan' => ' colspan="2"', 'cols' => 2);
|
|
$reshook = $hookmanager->executeHooks('formObjectOptions', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
|
|
print $hookmanager->resPrint;
|
|
if (empty($reshook)) {
|
|
print $object->showOptionals($extrafields, 'create', $parameters);
|
|
}
|
|
|
|
// Note (private, no output on invoices, propales...)
|
|
print '<tr><td class="tdtop">'.$langs->trans("NoteNotVisibleOnBill").'</td><td>';
|
|
|
|
// We use dolibarr_details as type of DolEditor here, because we must not accept images as description is included into PDF and not accepted by TCPDF.
|
|
$doleditor = new DolEditor('note_private', GETPOST('note_private', 'restricthtml'), '', 140, 'dolibarr_details', '', false, true, getDolGlobalString('FCKEDITOR_ENABLE_NOTE_PRIVATE'), ROWS_8, '90%');
|
|
$doleditor->Create();
|
|
|
|
print "</td></tr>";
|
|
|
|
if (isModEnabled('category')) {
|
|
// Categories
|
|
print '<tr><td>'.$langs->trans("Categories").'</td><td>';
|
|
print $form->selectCategories(Categorie::TYPE_PRODUCT, 'categories', $object);
|
|
print "</td></tr>";
|
|
}
|
|
|
|
print '</table>';
|
|
|
|
print '<hr>';
|
|
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_PRICES')) {
|
|
if (getDolGlobalString('PRODUIT_MULTIPRICES') || getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) {
|
|
// We do no show price array on create when multiprices enabled.
|
|
// We must set them on prices tab.
|
|
print '<table class="border centpercent">';
|
|
// VAT
|
|
print '<tr><td class="titlefieldcreate">'.$langs->trans("VATRate").'</td><td>';
|
|
$defaultva = get_default_tva($mysoc, $mysoc);
|
|
print $form->load_tva("tva_tx", $defaultva, $mysoc, $mysoc, 0, 0, '', false, 1);
|
|
print ajax_combobox("tva_tx");
|
|
print '</td></tr>';
|
|
|
|
print '</table>';
|
|
|
|
print '<br>';
|
|
} else {
|
|
print '<table class="border centpercent">';
|
|
|
|
// Price
|
|
print '<tr><td class="titlefieldcreate">'.$langs->trans("SellingPrice").'</td>';
|
|
print '<td><input name="price" class="maxwidth50" value="'.(GETPOSTISSET('price') ? GETPOST('price') : $object->price).'">';
|
|
print $form->selectPriceBaseType(getDolGlobalString('PRODUCT_PRICE_BASE_TYPE'), "price_base_type");
|
|
print ajax_combobox("select_price_base_type");
|
|
print '</td></tr>';
|
|
|
|
// Min price
|
|
print '<tr><td>'.$langs->trans("MinPrice").'</td>';
|
|
print '<td><input name="price_min" class="maxwidth50" value="'.(GETPOSTISSET('price_min') ? GETPOST('price_min') : $object->price_min).'">';
|
|
print '</td></tr>';
|
|
|
|
// VAT
|
|
print '<tr><td>'.$langs->trans("VATRate").'</td><td>';
|
|
$defaultva = GETPOSTISSET('tva_tx') ? GETPOST('tva_tx') : get_default_tva($mysoc, $mysoc);
|
|
print $form->load_tva("tva_tx", $defaultva, $mysoc, $mysoc, 0, 0, '', false, 1);
|
|
print ajax_combobox("tva_tx");
|
|
print '</td></tr>';
|
|
|
|
print '</table>';
|
|
|
|
print '<br>';
|
|
}
|
|
}
|
|
|
|
// Accountancy codes
|
|
print '<!-- accountancy codes -->'."\n";
|
|
print '<table class="border centpercent">';
|
|
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_ACCOUNTING')) {
|
|
if (isModEnabled('accounting') && is_object($formaccounting)) {
|
|
/** @var FormAccounting $formaccounting */
|
|
// Accountancy_code_sell
|
|
print '<tr><td class="titlefieldcreate">'.$langs->trans("ProductAccountancySellCode").'</td>';
|
|
print '<td>';
|
|
if ($type == 0) {
|
|
$accountancy_code_sell = (GETPOSTISSET('accountancy_code_sell') ? GETPOST('accountancy_code_sell', 'alpha') : getDolGlobalString("ACCOUNTING_PRODUCT_SOLD_ACCOUNT"));
|
|
} else {
|
|
$accountancy_code_sell = (GETPOSTISSET('accountancy_code_sell') ? GETPOST('accountancy_code_sell', 'alpha') : getDolGlobalString("ACCOUNTING_SERVICE_SOLD_ACCOUNT"));
|
|
}
|
|
print $formaccounting->select_account($accountancy_code_sell, 'accountancy_code_sell', 1, array(), 1, 1, 'minwidth150 maxwidth300', '1');
|
|
print '</td></tr>';
|
|
|
|
// Accountancy_code_sell_intra
|
|
if ($mysoc->isInEEC()) {
|
|
print '<tr><td class="titlefieldcreate">'.$langs->trans("ProductAccountancySellIntraCode").'</td>';
|
|
print '<td>';
|
|
if ($type == 0) {
|
|
$accountancy_code_sell_intra = (GETPOSTISSET('accountancy_code_sell_intra') ? GETPOST('accountancy_code_sell_intra', 'alpha') : getDolGlobalString("ACCOUNTING_PRODUCT_SOLD_INTRA_ACCOUNT"));
|
|
} else {
|
|
$accountancy_code_sell_intra = (GETPOSTISSET('accountancy_code_sell_intra') ? GETPOST('accountancy_code_sell_intra', 'alpha') : getDolGlobalString("ACCOUNTING_SERVICE_SOLD_INTRA_ACCOUNT"));
|
|
}
|
|
print $formaccounting->select_account($accountancy_code_sell_intra, 'accountancy_code_sell_intra', 1, array(), 1, 1, 'minwidth150 maxwidth300', '1');
|
|
print '</td></tr>';
|
|
}
|
|
|
|
// Accountancy_code_sell_export
|
|
print '<tr><td class="titlefieldcreate">'.$langs->trans("ProductAccountancySellExportCode").'</td>';
|
|
print '<td>';
|
|
if ($type == 0) {
|
|
$accountancy_code_sell_export = (GETPOST('accountancy_code_sell_export') ? GETPOST('accountancy_code_sell_export', 'alpha') : getDolGlobalString("ACCOUNTING_PRODUCT_SOLD_EXPORT_ACCOUNT"));
|
|
} else {
|
|
$accountancy_code_sell_export = (GETPOST('accountancy_code_sell_export') ? GETPOST('accountancy_code_sell_export', 'alpha') : getDolGlobalString("ACCOUNTING_SERVICE_SOLD_EXPORT_ACCOUNT"));
|
|
}
|
|
print $formaccounting->select_account($accountancy_code_sell_export, 'accountancy_code_sell_export', 1, array(), 1, 1, 'minwidth150 maxwidth300', '1');
|
|
print '</td></tr>';
|
|
|
|
// Accountancy_code_buy
|
|
print '<tr><td>'.$langs->trans("ProductAccountancyBuyCode").'</td>';
|
|
print '<td>';
|
|
if ($type == 0) {
|
|
$accountancy_code_buy = (GETPOST('accountancy_code_buy', 'alpha') ? (GETPOST('accountancy_code_buy', 'alpha')) : getDolGlobalString("ACCOUNTING_PRODUCT_BUY_ACCOUNT"));
|
|
} else {
|
|
$accountancy_code_buy = (GETPOST('accountancy_code_buy', 'alpha') ? (GETPOST('accountancy_code_buy', 'alpha')) : getDolGlobalString("ACCOUNTING_SERVICE_BUY_ACCOUNT"));
|
|
}
|
|
print $formaccounting->select_account($accountancy_code_buy, 'accountancy_code_buy', 1, array(), 1, 1, 'minwidth150 maxwidth300', '1');
|
|
print '</td></tr>';
|
|
|
|
// Accountancy_code_buy_intra
|
|
if ($mysoc->isInEEC()) {
|
|
print '<tr><td class="titlefieldcreate">'.$langs->trans("ProductAccountancyBuyIntraCode").'</td>';
|
|
print '<td>';
|
|
if ($type == 0) {
|
|
$accountancy_code_buy_intra = (GETPOSTISSET('accountancy_code_buy_intra') ? GETPOST('accountancy_code_buy_intra', 'alpha') : getDolGlobalString("ACCOUNTING_PRODUCT_BUY_INTRA_ACCOUNT"));
|
|
} else {
|
|
$accountancy_code_buy_intra = (GETPOSTISSET('accountancy_code_buy_intra') ? GETPOST('accountancy_code_buy_intra', 'alpha') : getDolGlobalString("ACCOUNTING_SERVICE_BUY_INTRA_ACCOUNT"));
|
|
}
|
|
print $formaccounting->select_account($accountancy_code_buy_intra, 'accountancy_code_buy_intra', 1, array(), 1, 1, 'minwidth150 maxwidth300', '1');
|
|
print '</td></tr>';
|
|
}
|
|
|
|
// Accountancy_code_buy_export
|
|
print '<tr><td class="titlefieldcreate">'.$langs->trans("ProductAccountancyBuyExportCode").'</td>';
|
|
print '<td>';
|
|
if ($type == 0) {
|
|
$accountancy_code_buy_export = (GETPOST('accountancy_code_buy_export') ? GETPOST('accountancy_code_buy_export', 'alpha') : getDolGlobalString("ACCOUNTING_PRODUCT_BUY_EXPORT_ACCOUNT"));
|
|
} else {
|
|
$accountancy_code_buy_export = (GETPOST('accountancy_code_buy_export') ? GETPOST('accountancy_code_buy_export', 'alpha') : getDolGlobalString("ACCOUNTING_SERVICE_BUY_EXPORT_ACCOUNT"));
|
|
}
|
|
print $formaccounting->select_account($accountancy_code_buy_export, 'accountancy_code_buy_export', 1, array(), 1, 1, 'minwidth150 maxwidth300', '1');
|
|
print '</td></tr>';
|
|
} else {// For external software
|
|
if (!empty($accountancy_code_sell)) {
|
|
$object->accountancy_code_sell = $accountancy_code_sell;
|
|
}
|
|
if (!empty($accountancy_code_sell_intra)) {
|
|
$object->accountancy_code_sell_intra = $accountancy_code_sell_intra;
|
|
}
|
|
if (!empty($accountancy_code_sell_export)) {
|
|
$object->accountancy_code_sell_export = $accountancy_code_sell_export;
|
|
}
|
|
if (!empty($accountancy_code_buy)) {
|
|
$object->accountancy_code_buy = $accountancy_code_buy;
|
|
}
|
|
if (!empty($accountancy_code_buy_intra)) {
|
|
$object->accountancy_code_buy_intra = $accountancy_code_buy_intra;
|
|
}
|
|
if (!empty($accountancy_code_buy_export)) {
|
|
$object->accountancy_code_buy_export = $accountancy_code_buy_export;
|
|
}
|
|
|
|
// Accountancy_code_sell
|
|
print '<tr><td class="titlefieldcreate">'.$langs->trans("ProductAccountancySellCode").'</td>';
|
|
print '<td class="maxwidthonsmartphone"><input class="minwidth150" name="accountancy_code_sell" value="'.$object->accountancy_code_sell.'">';
|
|
print '</td></tr>';
|
|
|
|
// Accountancy_code_sell_intra
|
|
if ($mysoc->isInEEC()) {
|
|
print '<tr><td class="titlefieldcreate">'.$langs->trans("ProductAccountancySellIntraCode").'</td>';
|
|
print '<td class="maxwidthonsmartphone"><input class="minwidth150" name="accountancy_code_sell_intra" value="'.$object->accountancy_code_sell_intra.'">';
|
|
print '</td></tr>';
|
|
}
|
|
|
|
// Accountancy_code_sell_export
|
|
print '<tr><td class="titlefieldcreate">'.$langs->trans("ProductAccountancySellExportCode").'</td>';
|
|
print '<td class="maxwidthonsmartphone"><input class="minwidth150" name="accountancy_code_sell_export" value="'.$object->accountancy_code_sell_export.'">';
|
|
print '</td></tr>';
|
|
|
|
// Accountancy_code_buy
|
|
print '<tr><td>'.$langs->trans("ProductAccountancyBuyCode").'</td>';
|
|
print '<td class="maxwidthonsmartphone"><input class="minwidth150" name="accountancy_code_buy" value="'.$object->accountancy_code_buy.'">';
|
|
print '</td></tr>';
|
|
|
|
// Accountancy_code_buy_intra
|
|
if ($mysoc->isInEEC()) {
|
|
print '<tr><td class="titlefieldcreate">'.$langs->trans("ProductAccountancyBuyIntraCode").'</td>';
|
|
print '<td class="maxwidthonsmartphone"><input class="minwidth150" name="accountancy_code_buy_intra" value="'.$object->accountancy_code_buy_intra.'">';
|
|
print '</td></tr>';
|
|
}
|
|
|
|
// Accountancy_code_buy_export
|
|
print '<tr><td class="titlefieldcreate">'.$langs->trans("ProductAccountancyBuyExportCode").'</td>';
|
|
print '<td class="maxwidthonsmartphone"><input class="minwidth150" name="accountancy_code_buy_export" value="'.$object->accountancy_code_buy_export.'">';
|
|
print '</td></tr>';
|
|
}
|
|
}
|
|
print '</table>';
|
|
}
|
|
|
|
print dol_get_fiche_end();
|
|
|
|
print $form->buttonsSaveCancel("Create", "Cancel", array(), false, '', $dol_openinpopup);
|
|
|
|
print '</form>';
|
|
} elseif ($object->id > 0) {
|
|
$iskit = $object->hasFatherOrChild(1);
|
|
|
|
// Product card in edit mode
|
|
if ($action == 'edit' && $usercancreate) {
|
|
//WYSIWYG Editor
|
|
require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
|
|
|
|
if (!empty($conf->use_javascript_ajax)) {
|
|
print '<script type="text/javascript">';
|
|
print '$(document).ready(function () {
|
|
$("#selectcountry_id").change(function () {
|
|
document.formprod.action.value="edit";
|
|
document.formprod.submit();
|
|
});
|
|
});';
|
|
print '</script>'."\n";
|
|
}
|
|
|
|
// We set country_id, country_code and country for the selected country
|
|
$object->country_id = GETPOST('country_id') ? GETPOST('country_id') : $object->country_id;
|
|
if ($object->country_id) {
|
|
$tmparray = getCountry($object->country_id, 'all');
|
|
$object->country_code = $tmparray['code'];
|
|
$object->country = $tmparray['label'];
|
|
}
|
|
|
|
$type = $langs->trans('Product');
|
|
if ($object->isService()) {
|
|
$type = $langs->trans('Service');
|
|
}
|
|
// print load_fiche_titre($langs->trans('Modify').' '.$type.' : '.(is_object($object->oldcopy)?$object->oldcopy->ref:$object->ref), "");
|
|
|
|
// Main official, simple, and not duplicated code
|
|
print '<form action="'.$_SERVER['PHP_SELF'].'?id='.$object->id.'" method="POST" name="formprod">'."\n";
|
|
print '<input type="hidden" name="token" value="'.newToken().'">';
|
|
print '<input type="hidden" name="action" value="update">';
|
|
print '<input type="hidden" name="id" value="'.$object->id.'">';
|
|
print '<input type="hidden" name="canvas" value="'.$object->canvas.'">';
|
|
|
|
$head = product_prepare_head($object);
|
|
$titre = $langs->trans("CardProduct".$object->type);
|
|
$picto = ($object->type == Product::TYPE_SERVICE ? 'service' : 'product');
|
|
print dol_get_fiche_head($head, 'card', $titre, 0, $picto, 0, '', '', 0, '', 0); // No drag and drop on the edit form, dropping a file reloads the page and discards it
|
|
|
|
// Call Hook tabContentEditProduct
|
|
$parameters = array();
|
|
// Note that $action and $object may be modified by hook
|
|
$reshook = $hookmanager->executeHooks('tabContentEditProduct', $parameters, $object, $action);
|
|
|
|
if (empty($reshook)) {
|
|
print '<table class="border allwidth">';
|
|
|
|
// Ref
|
|
if (!getDolGlobalString('MAIN_PRODUCT_REF_NOT_EDITABLE')) {
|
|
print '<tr><td class="titlefieldcreate fieldrequired">'.$langs->trans("Ref").'</td><td colspan="3"><input name="ref" class="maxwidth200" maxlength="128" value="'.dol_escape_htmltag(GETPOSTISSET('ref') ? GETPOST('ref') : $object->ref).'"></td></tr>';
|
|
} else {
|
|
print '<tr><td class="titlefieldcreate fieldrequired">'.$langs->trans("Ref").'</td><td colspan="3"><input name="ref" class="maxwidth200" maxlength="128" value="'.dol_escape_htmltag($object->ref).'" readonly="true"></td></tr>';
|
|
}
|
|
|
|
// Label
|
|
print '<tr><td class="fieldrequired">'.$langs->trans("Label").'</td><td colspan="3"><input name="label" class="minwidth300 maxwidth400onsmartphone" maxlength="255" value="'.dol_escape_htmltag(GETPOSTISSET('label') ? GETPOST('label') : $object->label).'"></td></tr>';
|
|
|
|
// Status To sell
|
|
print '<tr><td class="fieldrequired">'.$langs->trans("Status").' ('.$langs->trans("Sell").')</td><td colspan="3">';
|
|
print '<select class="flat" name="statut">';
|
|
if ((GETPOSTISSET('statut') && GETPOST('statut')) || (!GETPOSTISSET('statut') && $object->status)) {
|
|
print '<option value="1" selected>'.$langs->trans("OnSell").'</option>';
|
|
print '<option value="0">'.$langs->trans("NotOnSell").'</option>';
|
|
} else {
|
|
print '<option value="1">'.$langs->trans("OnSell").'</option>';
|
|
print '<option value="0" selected>'.$langs->trans("NotOnSell").'</option>';
|
|
}
|
|
print '</select>';
|
|
print '</td></tr>';
|
|
|
|
// Status To Buy
|
|
print '<tr><td class="fieldrequired">'.$langs->trans("Status").' ('.$langs->trans("Buy").')</td><td colspan="3">';
|
|
print '<select class="flat" name="statut_buy">';
|
|
if ((GETPOSTISSET('statut_buy') && GETPOST('statut_buy')) || (!GETPOSTISSET('statut_buy') && $object->status_buy)) {
|
|
print '<option value="1" selected>'.$langs->trans("ProductStatusOnBuy").'</option>';
|
|
print '<option value="0">'.$langs->trans("ProductStatusNotOnBuy").'</option>';
|
|
} else {
|
|
print '<option value="1">'.$langs->trans("ProductStatusOnBuy").'</option>';
|
|
print '<option value="0" selected>'.$langs->trans("ProductStatusNotOnBuy").'</option>';
|
|
}
|
|
print '</select>';
|
|
print '</td></tr>';
|
|
|
|
// Batch number management
|
|
if (isModEnabled('productbatch')) {
|
|
if ($object->isProduct() || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
|
|
print '<tr><td>'.$langs->trans("ManageLotSerial").'</td><td>';
|
|
$statutarray = array('0' => $langs->trans("ProductStatusNotOnBatch"), '1' => $langs->trans("ProductStatusOnBatch"), '2' => $langs->trans("ProductStatusOnSerial"));
|
|
|
|
print $form->selectarray('status_batch', $statutarray, GETPOSTISSET('status_batch') ? GETPOSTINT('status_batch') : $object->status_batch);
|
|
|
|
print '<span id="statusBatchWarning" class="warning" style="display: none;">';
|
|
print img_warning().' '.$langs->trans("WarningConvertFromBatchToSerial").'</span>';
|
|
|
|
print '<span id="statusBatchMouvToGlobal" class="warning" style="display: none;">';
|
|
print img_warning().' '.$langs->trans("WarningTransferBatchStockMouvToGlobal").'</span>';
|
|
|
|
if ($object->status_batch) {
|
|
// Display message to make user know that all batch will be move into global stock
|
|
print '<script type="text/javascript">
|
|
$(document).ready(function() {
|
|
console.log($("#statusBatchWarning"))
|
|
$("#status_batch").on("change", function() {
|
|
if ($("#status_batch")[0].value == 0) {
|
|
$("#statusBatchMouvToGlobal").show()
|
|
} else {
|
|
$("#statusBatchMouvToGlobal").hide()
|
|
}
|
|
})
|
|
})</script>';
|
|
|
|
// Display message to explain that if the product currently have a quantity higher or equal to 2, switching to this choice means we will still have a product with different objects of the same batch (while we want a unique serial number)
|
|
if ($object->status_batch == 1) {
|
|
print '<script type="text/javascript">
|
|
$(document).ready(function() {
|
|
console.log($("#statusBatchWarning"))
|
|
$("#status_batch").on("change", function() {
|
|
if ($("#status_batch")[0].value == 2) {
|
|
$("#statusBatchWarning").show()
|
|
} else {
|
|
$("#statusBatchWarning").hide()
|
|
}
|
|
})
|
|
})</script>';
|
|
}
|
|
}
|
|
|
|
print '</td></tr>';
|
|
if (!empty($object->status_batch) || !empty($conf->use_javascript_ajax)) {
|
|
$langs->load("admin");
|
|
$tooltip = $langs->trans("GenericMaskCodes", $langs->transnoentities("Batch"), $langs->transnoentities("Batch"));
|
|
$tooltip .= '<br>'.$langs->trans("GenericMaskCodes2");
|
|
$tooltip .= '<br>'.$langs->trans("GenericMaskCodes3");
|
|
$tooltip .= '<br>'.$langs->trans("GenericMaskCodes4a", $langs->transnoentities("Batch"), $langs->transnoentities("Batch"));
|
|
$tooltip .= '<br>'.$langs->trans("GenericMaskCodes5");
|
|
//$tooltip .= '<br>'.$langs->trans("GenericMaskCodes5b");
|
|
|
|
print '<tr><td id="mask_option">'.$langs->trans("ManageLotMask").'</td>';
|
|
$mask = '';
|
|
if ($object->status_batch == '1' && getDolGlobalString('PRODUCTBATCH_LOT_USE_PRODUCT_MASKS') && getDolGlobalString('PRODUCTBATCH_LOT_ADDON') == 'mod_lot_advanced') {
|
|
$mask = !empty($object->batch_mask) ? $object->batch_mask : getDolGlobalString('LOT_ADVANCED_MASK');
|
|
}
|
|
if ($object->status_batch == '2' && getDolGlobalString('PRODUCTBATCH_SN_USE_PRODUCT_MASKS') && getDolGlobalString('PRODUCTBATCH_SN_ADDON') == 'mod_sn_advanced') {
|
|
$mask = !empty($object->batch_mask) ? $object->batch_mask : getDolGlobalString('SN_ADVANCED_MASK');
|
|
}
|
|
$inherited_mask_lot = getDolGlobalString('LOT_ADVANCED_MASK');
|
|
$inherited_mask_sn = getDolGlobalString('SN_ADVANCED_MASK');
|
|
print '<td id="field_mask">';
|
|
print $form->textwithpicto('<input type="text" class="flat minwidth175" name="batch_mask" id="batch_mask_input" value="'.$mask.'">', $tooltip, 1, 'help');
|
|
// Add javascript to sho/hide field for custom mask
|
|
if (!empty($conf->use_javascript_ajax)) {
|
|
print '<script type="text/javascript">
|
|
$(document).ready(function() {
|
|
$("#field_mask").parent().addClass("hideobject");
|
|
var preselect = document.getElementById("status_batch");';
|
|
if (getDolGlobalString('PRODUCTBATCH_SN_USE_PRODUCT_MASKS')) {
|
|
print 'if (preselect.value == "2") {
|
|
$("#field_mask").parent().removeClass("hideobject");
|
|
}';
|
|
}
|
|
if (getDolGlobalString('PRODUCTBATCH_LOT_USE_PRODUCT_MASKS')) {
|
|
print 'if (preselect.value == "1") {
|
|
$("#field_mask").parent().removeClass("hideobject");
|
|
}';
|
|
}
|
|
print '$("#status_batch").on("change", function () {
|
|
var optionSelected = $("option:selected", this);
|
|
var valueSelected = this.value;
|
|
$("#field_mask").parent().addClass("hideobject");
|
|
';
|
|
if (getDolGlobalString('PRODUCTBATCH_LOT_USE_PRODUCT_MASKS') && getDolGlobalString('PRODUCTBATCH_LOT_ADDON') == 'mod_lot_advanced') {
|
|
print '
|
|
if (this.value == 1) {
|
|
$("#field_mask").parent().removeClass("hideobject");
|
|
$("#batch_mask_input").val("'.$inherited_mask_lot.'");
|
|
}
|
|
';
|
|
}
|
|
if (getDolGlobalString('PRODUCTBATCH_SN_USE_PRODUCT_MASKS') && getDolGlobalString('PRODUCTBATCH_SN_ADDON') == 'mod_sn_advanced') {
|
|
print '
|
|
if (this.value == 2) {
|
|
$("#field_mask").parent().removeClass("hideobject");
|
|
$("#batch_mask_input").val("'.$inherited_mask_sn.'");
|
|
}
|
|
';
|
|
}
|
|
print '
|
|
})
|
|
})
|
|
</script>';
|
|
}
|
|
print '</td></tr>';
|
|
}
|
|
}
|
|
|
|
// SellBy / EatBy mandatory list
|
|
if (!empty($sellOrEatByMandatoryList)) {
|
|
if (GETPOSTISSET('sell_or_eat_by_mandatory')) {
|
|
$sellOrEatByMandatorySelectedId = GETPOSTINT('sell_or_eat_by_mandatory');
|
|
} else {
|
|
$sellOrEatByMandatorySelectedId = $object->sell_or_eat_by_mandatory;
|
|
}
|
|
print '<tr><td>'.$langs->trans('BatchSellOrEatByMandatoryList', $langs->transnoentities('SellByDate'), $langs->transnoentities('EatByDate')).'</td><td>';
|
|
print $form->selectarray('sell_or_eat_by_mandatory', $sellOrEatByMandatoryList, $sellOrEatByMandatorySelectedId);
|
|
print '</td></tr>';
|
|
}
|
|
}
|
|
|
|
// Barcode
|
|
$showbarcode = (isModEnabled('barcode') && getDolGlobalString('BARCODE_USE_ON_PRODUCT'));
|
|
if (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && !$user->hasRight('barcode', 'lire_advance')) {
|
|
$showbarcode = 0;
|
|
}
|
|
|
|
if ($showbarcode) {
|
|
print '<tr><td>'.$langs->trans('BarcodeType').'</td><td>';
|
|
if (GETPOSTISSET('fk_barcode_type')) {
|
|
$fk_barcode_type = GETPOST('fk_barcode_type');
|
|
} else {
|
|
$fk_barcode_type = $object->barcode_type;
|
|
if (empty($fk_barcode_type) && getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE')) {
|
|
$fk_barcode_type = getDolGlobalString('PRODUIT_DEFAULT_BARCODE_TYPE');
|
|
}
|
|
}
|
|
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formbarcode.class.php';
|
|
$formbarcode = new FormBarCode($db);
|
|
print $formbarcode->selectBarcodeType($fk_barcode_type, 'fk_barcode_type', 1);
|
|
print '</td></tr>';
|
|
print '<tr><td>'.$langs->trans("BarcodeValue").'</td><td>';
|
|
$tmpcode = GETPOSTISSET('barcode') ? GETPOST('barcode') : $object->barcode;
|
|
if (empty($tmpcode) && !empty($modBarCodeProduct->code_auto)) {
|
|
$tmpcode = $modBarCodeProduct->getNextValue($object, $fk_barcode_type);
|
|
}
|
|
print '<input class="maxwidth150 maxwidthonsmartphone" type="text" name="barcode" value="'.dol_escape_htmltag($tmpcode).'">';
|
|
print '</td></tr>';
|
|
}
|
|
|
|
// Description (used in invoice, propal...)
|
|
print '<tr><td class="tdtop">'.$langs->trans("Description").'</td><td>';
|
|
|
|
// We use dolibarr_details as type of DolEditor here, because we must not accept images, as description is included into PDF and external links are not accepted by TCPDF.
|
|
$doleditor = new DolEditor('desc', GETPOSTISSET('desc') ? GETPOST('desc', 'restricthtml') : $object->description, '', 160, 'dolibarr_details', '', false, true, getDolGlobalInt('FCKEDITOR_ENABLE_DETAILS'), ROWS_4, '90%');
|
|
$doleditor->Create();
|
|
|
|
print "</td></tr>";
|
|
print "\n";
|
|
|
|
// Public Url
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_PUBLIC_URL')) {
|
|
print '<tr><td>'.$langs->trans("PublicUrl").'</td><td>';
|
|
print img_picto('', 'globe', 'class="pictofixedwidth"');
|
|
print '<input type="text" name="url" class="maxwidth500 widthcentpercentminusx" value="'.(GETPOSTISSET('url') ? GETPOST('url') : $object->url).'">';
|
|
print '</td></tr>';
|
|
}
|
|
|
|
// Stock
|
|
if (isModEnabled('stock')) {
|
|
if (isModEnabled('productbatch') && $object->hasbatch()) {
|
|
print '<tr><td><input type="hidden" id="stockable_product" name="stockable_product" value="on" /></td><td></td></tr>';
|
|
} else {
|
|
print '<tr><td><label for="stockable_product">' . $langs->trans("StockableProduct") . '</label></td>';
|
|
$checked = empty($object->stockable_product) ? "" : "checked";
|
|
print '<td><input type="checkbox" id="stockable_product" name="stockable_product" '. $checked . ' /></td></tr>';
|
|
}
|
|
|
|
// Default warehouse
|
|
print '<tr class="showifstockable'.(empty($object->stockable_product) ? ' hidden' : '').'"><td>'.$langs->trans("DefaultWarehouse").'</td><td>';
|
|
print img_picto($langs->trans("DefaultWarehouse"), 'stock', 'class="pictofixedwidth"');
|
|
print $formproduct->selectWarehouses((GETPOSTISSET('fk_default_warehouse') ? GETPOST('fk_default_warehouse') : $object->fk_default_warehouse), 'fk_default_warehouse', 'warehouseopen', 1, 0, 0, '', 0, 0, array(), 'minwidth200 maxwidth500 widthcentpercentminusxx');
|
|
print ' <a href="'.DOL_URL_ROOT.'/product/stock/card.php?action=create&backtopage='.urlencode($_SERVER['PHP_SELF'].'?action=edit&id='.((int) $object->id)).'">';
|
|
print '<span class="fa fa-plus-circle valignmiddle paddingleft" title="'.$langs->trans("AddWarehouse").'"></span></a>';
|
|
print '</td></tr>';
|
|
|
|
/*
|
|
print "<tr>".'<td>'.$langs->trans("StockLimit").'</td><td>';
|
|
print '<input name="seuil_stock_alerte" size="4" value="'.$object->seuil_stock_alerte.'">';
|
|
print '</td>';
|
|
|
|
print '<td>'.$langs->trans("DesiredStock").'</td><td>';
|
|
print '<input name="desiredstock" size="4" value="'.$object->desiredstock.'">';
|
|
print '</td></tr>';
|
|
*/
|
|
}
|
|
|
|
if ($object->isService() && isModEnabled('workstation')) {
|
|
// Default workstation
|
|
print '<tr><td>'.$langs->trans("DefaultWorkstation").'</td><td>';
|
|
print img_picto($langs->trans("DefaultWorkstation"), 'workstation', 'class="pictofixedwidth"');
|
|
print $formproduct->selectWorkstations($object->fk_default_workstation, 'fk_default_workstation', 1);
|
|
print '</td></tr>';
|
|
}
|
|
|
|
/*
|
|
else
|
|
{
|
|
print '<input name="seuil_stock_alerte" type="hidden" value="'.$object->seuil_stock_alerte.'">';
|
|
print '<input name="desiredstock" type="hidden" value="'.$object->desiredstock.'">';
|
|
}*/
|
|
|
|
if ($object->isService()) {
|
|
// Duration
|
|
print '<tr><td>'.$langs->trans("Duration").'</td><td>';
|
|
print '<input name="duration_value" class="width50" value="'.($object->duration_value ? $object->duration_value : '').'"> ';
|
|
|
|
print $formproduct->selectMeasuringUnits("duration_unit", "time", ($object->duration_unit ? $object->duration_unit : 'h'), 0, 1);
|
|
|
|
// Mandatory period
|
|
//if ($object->duration_value > 0) {
|
|
print ' ';
|
|
//}
|
|
print '<input type="checkbox" class="valignmiddle" id="mandatoryperiod" name="mandatoryperiod"'.($object->mandatory_period == 1 ? ' checked="checked"' : '').'>';
|
|
print '<label for="mandatoryperiod">';
|
|
$htmltooltip = $langs->trans("mandatoryHelper");
|
|
if (!getDolGlobalString('SERVICE_STRICT_MANDATORY_PERIOD')) {
|
|
$htmltooltip .= '<br>'.$langs->trans("mandatoryHelper2");
|
|
}
|
|
print $form->textwithpicto($langs->trans("mandatoryperiod"), $htmltooltip, 1, 'info');
|
|
print '</label>';
|
|
|
|
print '</td></tr>';
|
|
} else {
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_NATURE')) {
|
|
// Nature
|
|
print '<tr><td>'.$form->textwithpicto($langs->trans("NatureOfProductShort"), $langs->trans("NatureOfProductDesc")).'</td><td>';
|
|
print $formproduct->selectProductNature('finished', (GETPOSTISSET('finished') ? GETPOST('finished') : $object->finished));
|
|
print '</td></tr>';
|
|
}
|
|
}
|
|
|
|
if (!$object->isService() && isModEnabled('bom')) {
|
|
print '<tr><td>'.$form->textwithpicto($langs->trans("DefaultBOM"), $langs->trans("DefaultBOMDesc", $langs->transnoentitiesnoconv("Finished"))).'</td><td>';
|
|
$bomkey = "Bom:bom/class/bom.class.php:0:(t.status:=:1) AND (t.fk_product:=:".((int) $object->id).')';
|
|
print img_picto($langs->trans("DefaultBOM"), 'bom', 'class="pictofixedwidth"');
|
|
print $form->selectForForms($bomkey, 'fk_default_bom', (GETPOSTISSET('fk_default_bom') ? GETPOST('fk_default_bom') : $object->fk_default_bom), 1);
|
|
print '</td></tr>';
|
|
}
|
|
|
|
if (!$object->isService()) {
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_WEIGHT')) {
|
|
// Brut Weight
|
|
print '<tr><td>'.$langs->trans("Weight").'</td><td>';
|
|
print '<input name="weight" size="5" value="'.(GETPOSTISSET('weight') ? GETPOST('weight') : $object->weight).'"> ';
|
|
print $formproduct->selectMeasuringUnits("weight_units", "weight", GETPOSTISSET('weight_units') ? GETPOST('weight_units') : (int) $object->weight_units, 0, 2);
|
|
print '</td></tr>';
|
|
}
|
|
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_SIZE')) {
|
|
// Brut Length
|
|
print '<tr><td>'.$langs->trans("Length").' x '.$langs->trans("Width").' x '.$langs->trans("Height").'</td><td>';
|
|
print '<input name="size" size="5" value="'.(GETPOSTISSET('size') ? GETPOST('size') : $object->length).'">x';
|
|
print '<input name="sizewidth" size="5" value="'.(GETPOSTISSET('sizewidth') ? GETPOST('sizewidth') : $object->width).'">x';
|
|
print '<input name="sizeheight" size="5" value="'.(GETPOSTISSET('sizeheight') ? GETPOST('sizeheight') : $object->height).'"> ';
|
|
print $formproduct->selectMeasuringUnits("size_units", "size", GETPOSTISSET('size_units') ? GETPOST('size_units') : (int) $object->length_units, 0, 2);
|
|
print '</td></tr>';
|
|
}
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_SURFACE')) {
|
|
// Brut Surface
|
|
print '<tr><td>'.$langs->trans("Surface").'</td><td>';
|
|
print '<input name="surface" size="5" value="'.(GETPOSTISSET('surface') ? GETPOST('surface') : $object->surface).'"> ';
|
|
print $formproduct->selectMeasuringUnits("surface_units", "surface", GETPOSTISSET('surface_units') ? GETPOST('surface_units') : (int) $object->surface_units, 0, 2);
|
|
print '</td></tr>';
|
|
}
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_VOLUME')) {
|
|
// Brut Volume
|
|
print '<tr><td>'.$langs->trans("Volume").'</td><td>';
|
|
print '<input name="volume" size="5" value="'.(GETPOSTISSET('volume') ? GETPOST('volume') : $object->volume).'"> ';
|
|
print $formproduct->selectMeasuringUnits("volume_units", "volume", GETPOSTISSET('volume_units') ? GETPOST('volume_units') : (int) $object->volume_units, 0, 2);
|
|
print '</td></tr>';
|
|
}
|
|
|
|
if (getDolGlobalString('PRODUCT_ADD_NET_MEASURE')) {
|
|
// Net Measure
|
|
print '<tr><td>'.$langs->trans("NetMeasure").'</td><td>';
|
|
print '<input name="net_measure" size="5" value="'.(GETPOSTISSET('net_measure') ? GETPOST('net_measure') : $object->net_measure).'"> ';
|
|
print $formproduct->selectMeasuringUnits("net_measure_units", "", GETPOSTISSET('net_measure_units') ? GETPOST('net_measure_units') : (int) $object->net_measure_units, 0, 0);
|
|
print '</td></tr>';
|
|
}
|
|
}
|
|
// Units
|
|
if (getDolGlobalString('PRODUCT_USE_UNITS')) {
|
|
print '<tr><td>'.$langs->trans('DefaultUnitToShow').'</td>';
|
|
print '<td>';
|
|
print $form->selectUnits($object->fk_unit, 'units');
|
|
print '</td></tr>';
|
|
}
|
|
|
|
// Customs code
|
|
if (!$object->isService() && !getDolGlobalString('PRODUCT_DISABLE_CUSTOMS_INFO')) {
|
|
print '<tr><td class="wordbreak">'.$form->textwithpicto($langs->trans("CustomsCode"), $langs->trans("CustomsCodeHelp")).'</td>';
|
|
print '<td>' . img_picto('', 'fa-clipboard-check', 'class="pictofixedwidth"') . '<input name="customcode" class="maxwidth100onsmartphone" value="'.(GETPOSTISSET('customcode') ? GETPOST('customcode') : $object->customcode).'"></td></tr>';
|
|
// Origin country
|
|
print '<tr><td>'.$langs->trans("CountryOrigin").'</td>';
|
|
print '<td>';
|
|
print img_picto('', 'globe-americas', 'class="pictofixedwidth"');
|
|
print $form->select_country((string) (GETPOSTISSET('country_id') ? GETPOSTINT('country_id') : $object->country_id), 'country_id', '', 0, 'minwidth100 maxwidthonsmartphone');
|
|
if ($user->admin) {
|
|
print info_admin($langs->trans("YouCanChangeValuesForThisListFromDictionarySetup"), 1);
|
|
}
|
|
print '</td></tr>';
|
|
|
|
// State
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_STATE')) {
|
|
print '<tr>';
|
|
if (getDolGlobalString('MAIN_SHOW_REGION_IN_STATE_SELECT') && (getDolGlobalInt('MAIN_SHOW_REGION_IN_STATE_SELECT') == 1 || getDolGlobalInt('MAIN_SHOW_REGION_IN_STATE_SELECT') == 2)) {
|
|
print '<td>'.$form->editfieldkey('RegionStateOrigin', 'state_id', '', $object, 0).'</td><td>';
|
|
} else {
|
|
print '<td>'.$form->editfieldkey('StateOrigin', 'state_id', '', $object, 0).'</td><td>';
|
|
}
|
|
|
|
print img_picto('', 'state', 'class="pictofixedwidth"');
|
|
print $formcompany->select_state(GETPOSTISSET('state_id') ? GETPOSTINT('state_id') : $object->state_id, $object->country_code);
|
|
print '</td>';
|
|
print '</tr>';
|
|
}
|
|
}
|
|
|
|
// Quality control
|
|
if (getDolGlobalString('PRODUCT_LOT_ENABLE_QUALITY_CONTROL')) {
|
|
print '<tr><td>'.$langs->trans("LifeTime").'</td><td><input name="lifetime" class="maxwidth100onsmartphone" value="'.$object->lifetime.'"></td></tr>';
|
|
print '<tr><td>'.$langs->trans("QCFrequency").'</td><td><input name="qc_frequency" class="maxwidth100onsmartphone" value="'.$object->qc_frequency.'"></td></tr>';
|
|
}
|
|
|
|
// Other attributes
|
|
$parameters = array('colspan' => ' colspan="2"', 'cols' => 2);
|
|
$reshook = $hookmanager->executeHooks('formObjectOptions', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
|
|
print $hookmanager->resPrint;
|
|
if (empty($reshook)) {
|
|
print $object->showOptionals($extrafields, 'edit', $parameters);
|
|
}
|
|
|
|
// Tags-Categories
|
|
if (isModEnabled('category')) {
|
|
print '<tr><td>'.$langs->trans("Categories").'</td><td>';
|
|
print $form->selectCategories(Categorie::TYPE_PRODUCT, 'categories', $object);
|
|
print "</td></tr>";
|
|
}
|
|
|
|
// Note private
|
|
if (getDolGlobalString('MAIN_DISABLE_NOTES_TAB')) {
|
|
print '<tr><td class="tdtop">'.$langs->trans("NoteNotVisibleOnBill").'</td><td>';
|
|
|
|
$doleditor = new DolEditor('note_private', $object->note_private, '', 140, 'dolibarr_notes', '', false, true, getDolGlobalInt('FCKEDITOR_ENABLE_NOTE_PRIVATE'), ROWS_4, '90%');
|
|
$doleditor->Create();
|
|
|
|
print "</td></tr>";
|
|
}
|
|
|
|
print '</table>';
|
|
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_ACCOUNTING')) {
|
|
print '<hr>';
|
|
|
|
print '<table class="border centpercent">';
|
|
|
|
if (isModEnabled('accounting')) {
|
|
/** @var FormAccounting $formaccounting */
|
|
|
|
// Accountancy_code_sell
|
|
print '<tr><td class="titlefieldcreate">'.$langs->trans("ProductAccountancySellCode").'</td>';
|
|
print '<td>';
|
|
print $formaccounting->select_account((GETPOSTISSET('accountancy_code_sell') ? GETPOST('accountancy_code_sell') : $object->accountancy_code_sell), 'accountancy_code_sell', 1, array(), 1, 1, 'minwidth150 maxwidth300');
|
|
$nb = $formaccounting->nbaccounts;
|
|
if ($nb == 0) {
|
|
print ' <a href="'.DOL_URL_ROOT.'/accountancy/admin/account.php">'.$langs->trans("SetupOn", $langs->trans("Chartofaccounts")).'</a>';
|
|
}
|
|
print '</td></tr>';
|
|
|
|
// Accountancy_code_sell_intra
|
|
if ($mysoc->isInEEC()) {
|
|
print '<tr><td class="titlefieldcreate">'.$langs->trans("ProductAccountancySellIntraCode").'</td>';
|
|
print '<td>';
|
|
print $formaccounting->select_account((GETPOSTISSET('accountancy_code_sell_intra') ? GETPOST('accountancy_code_sell_intra') : $object->accountancy_code_sell_intra), 'accountancy_code_sell_intra', 1, array(), 1, 1, 'minwidth150 maxwidth300');
|
|
print '</td></tr>';
|
|
}
|
|
|
|
// Accountancy_code_sell_export
|
|
print '<tr><td class="titlefieldcreate">'.$langs->trans("ProductAccountancySellExportCode").'</td>';
|
|
print '<td>';
|
|
print $formaccounting->select_account((GETPOSTISSET('accountancy_code_sell_export') ? GETPOST('accountancy_code_sell_export') : $object->accountancy_code_sell_export), 'accountancy_code_sell_export', 1, array(), 1, 1, 'minwidth150 maxwidth300');
|
|
print '</td></tr>';
|
|
|
|
// Accountancy_code_buy
|
|
print '<tr><td>'.$langs->trans("ProductAccountancyBuyCode").'</td>';
|
|
print '<td>';
|
|
print $formaccounting->select_account((GETPOSTISSET('accountancy_code_buy') ? GETPOST('accountancy_code_buy') : $object->accountancy_code_buy), 'accountancy_code_buy', 1, array(), 1, 1, 'minwidth150 maxwidth300');
|
|
print '</td></tr>';
|
|
|
|
// Accountancy_code_buy_intra
|
|
if ($mysoc->isInEEC()) {
|
|
print '<tr><td class="titlefieldcreate">'.$langs->trans("ProductAccountancyBuyIntraCode").'</td>';
|
|
print '<td>';
|
|
print $formaccounting->select_account((GETPOSTISSET('accountancy_code_buy_intra') ? GETPOST('accountancy_code_buy_intra') : $object->accountancy_code_buy_intra), 'accountancy_code_buy_intra', 1, array(), 1, 1, 'minwidth150 maxwidth300');
|
|
print '</td></tr>';
|
|
}
|
|
|
|
// Accountancy_code_buy_export
|
|
print '<tr><td class="titlefieldcreate">'.$langs->trans("ProductAccountancyBuyExportCode").'</td>';
|
|
print '<td>';
|
|
print $formaccounting->select_account((GETPOSTISSET('accountancy_code_buy_export') ? GETPOST('accountancy_code_buy_export') : $object->accountancy_code_buy_export), 'accountancy_code_buy_export', 1, array(), 1, 1, 'minwidth150 maxwidth300');
|
|
print '</td></tr>';
|
|
} else {
|
|
// For external software
|
|
// Accountancy_code_sell
|
|
print '<tr><td class="titlefieldcreate">'.$langs->trans("ProductAccountancySellCode").'</td>';
|
|
print '<td><input name="accountancy_code_sell" class="maxwidth200" value="'.(GETPOSTISSET('accountancy_code_sell') ? GETPOST('accountancy_code_sell') : $object->accountancy_code_sell).'">';
|
|
print '</td></tr>';
|
|
|
|
// Accountancy_code_sell_intra
|
|
if ($mysoc->isInEEC()) {
|
|
print '<tr><td class="titlefieldcreate">'.$langs->trans("ProductAccountancySellIntraCode").'</td>';
|
|
print '<td><input name="accountancy_code_sell_intra" class="maxwidth200" value="'.(GETPOSTISSET('accountancy_code_sell_intra') ? GETPOST('accountancy_code_sell_intra') : $object->accountancy_code_sell_intra).'">';
|
|
print '</td></tr>';
|
|
}
|
|
|
|
// Accountancy_code_sell_export
|
|
print '<tr><td class="titlefieldcreate">'.$langs->trans("ProductAccountancySellExportCode").'</td>';
|
|
print '<td><input name="accountancy_code_sell_export" class="maxwidth200" value="'.(GETPOSTISSET('accountancy_code_sell_export') ? GETPOST('accountancy_code_sell_export') : $object->accountancy_code_sell_export).'">';
|
|
print '</td></tr>';
|
|
|
|
// Accountancy_code_buy
|
|
print '<tr><td>'.$langs->trans("ProductAccountancyBuyCode").'</td>';
|
|
print '<td><input name="accountancy_code_buy" class="maxwidth200" value="'.(GETPOSTISSET('accountancy_code_buy') ? GETPOST('accountancy_code_buy') : $object->accountancy_code_buy).'">';
|
|
print '</td></tr>';
|
|
|
|
// Accountancy_code_buy_intra
|
|
if ($mysoc->isInEEC()) {
|
|
print '<tr><td class="titlefieldcreate">'.$langs->trans("ProductAccountancyBuyIntraCode").'</td>';
|
|
print '<td><input name="accountancy_code_buy_intra" class="maxwidth200" value="'.(GETPOSTISSET('accountancy_code_buy_intra') ? GETPOST('accountancy_code_buy_intra') : $object->accountancy_code_buy_intra).'">';
|
|
print '</td></tr>';
|
|
}
|
|
|
|
// Accountancy_code_buy_export
|
|
print '<tr><td class="titlefieldcreate">'.$langs->trans("ProductAccountancyBuyExportCode").'</td>';
|
|
print '<td><input name="accountancy_code_buy_export" class="maxwidth200" value="'.(GETPOSTISSET('accountancy_code_buy_export') ? GETPOST('accountancy_code_buy_export') : $object->accountancy_code_buy_export).'">';
|
|
print '</td></tr>';
|
|
}
|
|
|
|
print '</table>';
|
|
}
|
|
}
|
|
|
|
print '<script>';
|
|
print '$(document).ready(function() {
|
|
$("#stockable_product").change(function() {
|
|
$(".showifstockable").toggle(this.checked);
|
|
});
|
|
});';
|
|
|
|
print '</script>';
|
|
|
|
|
|
print dol_get_fiche_end();
|
|
|
|
print $form->buttonsSaveCancel();
|
|
|
|
print '</form>';
|
|
} else {
|
|
// Product card in view mode
|
|
|
|
$showbarcode = (isModEnabled('barcode') && getDolGlobalString('BARCODE_USE_ON_PRODUCT'));
|
|
if (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && !$user->hasRight('barcode', 'lire_advance')) {
|
|
$showbarcode = 0;
|
|
}
|
|
|
|
$head = product_prepare_head($object);
|
|
$titre = $langs->trans("CardProduct".$object->type);
|
|
$picto = ($object->type == Product::TYPE_SERVICE ? 'service' : 'product');
|
|
|
|
print dol_get_fiche_head($head, 'card', $titre, -1, $picto, 0, '', '', 0, '', 1);
|
|
|
|
$linkback = '<a href="'.DOL_URL_ROOT.'/product/list.php?restore_lastsearch_values=1&type='.$object->type.'">'.$langs->trans("BackToList").'</a>';
|
|
$object->next_prev_filter = "(te.fk_product_type:=:".((int) $object->type).")";
|
|
|
|
$shownav = 1;
|
|
if ($user->socid && !in_array('product', explode(',', getDolGlobalString('MAIN_MODULES_FOR_EXTERNAL')))) {
|
|
$shownav = 0;
|
|
}
|
|
|
|
dol_banner_tab($object, 'ref', $linkback, $shownav, 'ref');
|
|
|
|
// Call Hook tabContentViewProduct
|
|
$parameters = array();
|
|
// Note that $action and $object may be modified by hook
|
|
$reshook = $hookmanager->executeHooks('tabContentViewProduct', $parameters, $object, $action);
|
|
if (empty($reshook)) {
|
|
print '<div class="fichecenter">';
|
|
print '<div class="fichehalfleft">';
|
|
|
|
print '<div class="underbanner clearboth"></div>';
|
|
print '<table class="border tableforfield centpercent">';
|
|
|
|
// Type
|
|
if (isModEnabled("product") && isModEnabled("service")) {
|
|
$typeformat = 'select;0:'.$langs->trans("Product").',1:'.$langs->trans("Service");
|
|
print '<tr><td class="titlefieldmiddle">';
|
|
print (!getDolGlobalString('PRODUCT_DENY_CHANGE_PRODUCT_TYPE')) ? $form->editfieldkey("Type", 'fk_product_type', (string) $object->type, $object, (int) $usercancreate, $typeformat) : $langs->trans('Type');
|
|
print '</td><td>';
|
|
print $form->editfieldval("Type", 'fk_product_type', $object->type, $object, $usercancreate, $typeformat);
|
|
print '</td></tr>';
|
|
}
|
|
|
|
if ($showbarcode) {
|
|
// Barcode type
|
|
print '<tr><td class="titlefieldmiddle nowrap">';
|
|
print '<table class="centpercent nobordernopadding"><tr><td class="nowrap">';
|
|
print $langs->trans("BarcodeType");
|
|
print '</td>';
|
|
if (($action != 'editbarcodetype') && $usercancreate && $createbarcode) {
|
|
print '<td class="right"><a class="editfielda" href="'.$_SERVER["PHP_SELF"].'?action=editbarcodetype&id='.$object->id.'&token='.newToken().'">'.img_edit($langs->trans('Edit'), 1).'</a></td>';
|
|
}
|
|
print '</tr></table>';
|
|
print '</td><td>';
|
|
if ($action == 'editbarcodetype' || $action == 'editbarcode') {
|
|
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formbarcode.class.php';
|
|
$formbarcode = new FormBarCode($db);
|
|
}
|
|
|
|
$fk_barcode_type = '';
|
|
if ($action == 'editbarcodetype' && is_object($formbarcode)) {
|
|
print $formbarcode->formBarcodeType($_SERVER['PHP_SELF'].'?id='.$object->id, $object->barcode_type, 'fk_barcode_type');
|
|
$fk_barcode_type = $object->barcode_type;
|
|
} else {
|
|
$object->fetchBarCode();
|
|
$fk_barcode_type = $object->barcode_type;
|
|
print $object->barcode_type_label ? $object->barcode_type_label : ($object->barcode ? '<div class="warning">'.$langs->trans("SetDefaultBarcodeType").'<div>' : '');
|
|
}
|
|
print '</td></tr>'."\n";
|
|
|
|
// Barcode value
|
|
print '<tr><td class="nowrap">';
|
|
print '<table class="centpercent nobordernopadding"><tr><td class="nowrap">';
|
|
print $langs->trans("BarcodeValue");
|
|
print '</td>';
|
|
if (($action != 'editbarcode') && $usercancreate && $createbarcode) {
|
|
print '<td class="right"><a class="editfielda" href="'.$_SERVER["PHP_SELF"].'?action=editbarcode&id='.$object->id.'&token='.newToken().'">'.img_edit($langs->trans('Edit'), 1).'</a></td>';
|
|
}
|
|
print '</tr></table>';
|
|
print '</td><td class="wordbreak">';
|
|
if ($action == 'editbarcode') {
|
|
$tmpcode = GETPOSTISSET('barcode') ? GETPOST('barcode') : $object->barcode;
|
|
if (empty($tmpcode) && !empty($modBarCodeProduct->code_auto)) {
|
|
$tmpcode = $modBarCodeProduct->getNextValue($object, (string) $fk_barcode_type);
|
|
}
|
|
|
|
print '<form method="post" action="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'">';
|
|
print '<input type="hidden" name="token" value="'.newToken().'">';
|
|
print '<input type="hidden" name="action" value="setbarcode">';
|
|
print '<input type="hidden" name="barcode_type_code" value="'.$object->barcode_type_code.'">';
|
|
print '<input class="width300" class="maxwidthonsmartphone" type="text" name="barcode" value="'.$tmpcode.'">';
|
|
print ' <input type="submit" class="button smallpaddingimp" value="'.$langs->trans("Modify").'">';
|
|
print '</form>';
|
|
} else {
|
|
print showValueWithClipboardCPButton($object->barcode);
|
|
}
|
|
print '</td></tr>'."\n";
|
|
}
|
|
|
|
// Stockable product / default warehouse
|
|
if (($object->isProduct() || getDolGlobalInt('STOCK_SUPPORTS_SERVICES')) && isModEnabled('stock')) { // Do not use isStockManaged here.We must sow info even if stock not managed
|
|
print '<tr><td class="titlefieldmiddle">' . $form->textwithpicto($langs->trans("StockableProduct"), $langs->trans('StockableProductDescription')) . '</td>';
|
|
print '<td>';
|
|
if ($iskit) {
|
|
print '<input type="checkbox" readonly disabled> <span class="opacitymedium">' . $langs->trans("NotSupportedOnKits").'</span>';
|
|
} else {
|
|
print '<input type="checkbox" readonly disabled '.($object->stockable_product == 1 ? 'checked' : '').'>';
|
|
}
|
|
print '</td></tr>';
|
|
|
|
if ($object->isStockManaged() && !$iskit) {
|
|
$warehouse = new Entrepot($db);
|
|
$warehouse->fetch($object->fk_default_warehouse);
|
|
|
|
print '<tr><td>'.$langs->trans("DefaultWarehouse").'</td><td>';
|
|
print(!empty($warehouse->id) ? $warehouse->getNomUrl(1) : '');
|
|
print '</td>';
|
|
}
|
|
}
|
|
|
|
// Batch number management (to batch)
|
|
if (isModEnabled('productbatch')) {
|
|
if ($object->isProduct() || getDolGlobalString('STOCK_SUPPORTS_SERVICES')) {
|
|
print '<tr><td class="titlefieldmiddle">'.$langs->trans("ManageLotSerial").'</td><td>';
|
|
if ($object->status_batch == 0) {
|
|
print '<span class="opacitymedium">'.yn(0).'</span>';
|
|
} else {
|
|
print $object->getLibStatut(0, 2);
|
|
}
|
|
print '</td></tr>';
|
|
if ((($object->status_batch == '1' && getDolGlobalString('PRODUCTBATCH_LOT_USE_PRODUCT_MASKS') && getDolGlobalString('PRODUCTBATCH_LOT_ADDON') == 'mod_lot_advanced')
|
|
|| ($object->status_batch == '2' && getDolGlobalString('PRODUCTBATCH_SN_ADDON') == 'mod_sn_advanced' && getDolGlobalString('PRODUCTBATCH_SN_USE_PRODUCT_MASKS')))) {
|
|
print '<tr><td>'.$langs->trans("ManageLotMask").'</td><td>';
|
|
print $object->batch_mask;
|
|
print '</td></tr>';
|
|
}
|
|
}
|
|
|
|
print '<tr><td class="titlefieldmiddle">'.$langs->trans('BatchSellOrEatByMandatoryList', $langs->transnoentities('SellByDate'), $langs->transnoentities('EatByDate')).'</td><td>';
|
|
if ($object->sell_or_eat_by_mandatory == $object::SELL_OR_EAT_BY_MANDATORY_ID_NONE) {
|
|
print '<span class="opacitymedium">'.yn(0).'</span>';
|
|
} else {
|
|
print $object->getSellOrEatByMandatoryLabel();
|
|
}
|
|
print '</td></tr>';
|
|
}
|
|
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_ACCOUNTING')) {
|
|
// Line to group accounting codes
|
|
$collapse = !empty($_COOKIE['DOLUSER_COLLAPSE_product_separator_accounting']);
|
|
|
|
print '<tr id="trseparatoraccounting" class="trseparator trseparatoraccounting">';
|
|
print '<td class="titlefieldmiddle firstcol"><span class="cursorpointer far fa-'.($collapse ? 'plus' : 'minus').'-square"></span> <strong>'.$langs->trans("AccountancyCodes").'</strong></td>';
|
|
print '<td class="secondcol">';
|
|
$nbOfAccountingCodes = 0;
|
|
$nbOfAccountingCodes += (isModEnabled('accounting') && !empty($object->accountancy_code_sell)) ? 1 : 0;
|
|
$nbOfAccountingCodes += (isModEnabled('accounting') && !empty($object->accountancy_code_sell_intra)) ? 1 : 0;
|
|
$nbOfAccountingCodes += (isModEnabled('accounting') && !empty($object->accountancy_code_sell_export)) ? 1 : 0;
|
|
$nbOfAccountingCodes += (isModEnabled('accounting') && !empty($object->accountancy_code_buy)) ? 1 : 0;
|
|
$nbOfAccountingCodes += (isModEnabled('accounting') && !empty($object->accountancy_code_buy_intra)) ? 1 : 0;
|
|
$nbOfAccountingCodes += (isModEnabled('accounting') && !empty($object->accountancy_code_buy_export)) ? 1 : 0;
|
|
print '<span class="badge badge-secondary'.($collapse ? '' : ' hidden').'">'.$nbOfAccountingCodes.'</span>';
|
|
print '</td>';
|
|
print '</tr>';
|
|
print '<!-- Add js script to manage the collapse/uncollapse of separator accounting -->
|
|
<script nonce="'.getNonce().'" type="text/javascript">
|
|
jQuery(document).ready(function(){
|
|
console.log("Inject js for collapsing of separatoraccounting - keep visible and set cookie");
|
|
/* document.cookie = "DOLUSER_COLLAPSE_product_separator_accounting=1; path='.DOL_URL_ROOT.'/product/card.php"; */
|
|
jQuery("#trseparatoraccounting").click(function(){
|
|
console.log("We click on collapse/uncollapse to hide/show .tr_collapseaccounting");
|
|
jQuery(".tr_collapseaccounting").toggle(100, function(){
|
|
if (jQuery(".tr_collapseaccounting").first().is(":hidden")) {
|
|
jQuery("#trseparatoraccounting td.firstcol span").addClass("fa-plus-square").removeClass("fa-minus-square");
|
|
jQuery("#trseparatoraccounting td.secondcol span").removeClass("hidden");
|
|
document.cookie = "DOLUSER_COLLAPSE_product_separator_accounting=1; path='.DOL_URL_ROOT.'/product/card.php"
|
|
} else {
|
|
jQuery("#trseparatoraccounting td.firstcol span").addClass("fa-minus-square").removeClass("fa-plus-square");
|
|
jQuery("#trseparatoraccounting td.secondcol span").addClass("hidden");
|
|
document.cookie = "DOLUSER_COLLAPSE_product_separator_accounting=0; path='.DOL_URL_ROOT.'/product/card.php"
|
|
}
|
|
});
|
|
});
|
|
});
|
|
</script>';
|
|
|
|
// Accountancy sell code
|
|
print '<tr class="tr_collapseaccounting'.($collapse ? ' hidden' : '').'"><td class="nowrap">';
|
|
print $langs->trans("ProductAccountancySellCode");
|
|
print '</td><td>';
|
|
if (isModEnabled('accounting')) {
|
|
if (!empty($object->accountancy_code_sell)) {
|
|
$accountingaccount = new AccountingAccount($db);
|
|
$accountingaccount->fetch(0, $object->accountancy_code_sell, 1);
|
|
|
|
print $accountingaccount->getNomUrl(0, 1, 1, '', 1);
|
|
}
|
|
} else {
|
|
print dolPrintHTML($object->accountancy_code_sell);
|
|
}
|
|
print '</td></tr>';
|
|
|
|
// Accountancy sell code intra-community
|
|
if ($mysoc->isInEEC()) {
|
|
print '<tr class="tr_collapseaccounting'.($collapse ? ' hidden' : '').'"><td class="nowrap">';
|
|
print $langs->trans("ProductAccountancySellIntraCode");
|
|
print '</td><td>';
|
|
if (isModEnabled('accounting')) {
|
|
if (!empty($object->accountancy_code_sell_intra)) {
|
|
$accountingaccount2 = new AccountingAccount($db);
|
|
$accountingaccount2->fetch(0, $object->accountancy_code_sell_intra, 1);
|
|
|
|
print $accountingaccount2->getNomUrl(0, 1, 1, '', 1);
|
|
}
|
|
} else {
|
|
print dolPrintHTML($object->accountancy_code_sell_intra);
|
|
}
|
|
print '</td></tr>';
|
|
}
|
|
|
|
// Accountancy sell code export
|
|
print '<tr class="tr_collapseaccounting'.($collapse ? ' hidden' : '').'"><td class="nowrap">';
|
|
print $langs->trans("ProductAccountancySellExportCode");
|
|
print '</td><td>';
|
|
if (isModEnabled('accounting')) {
|
|
if (!empty($object->accountancy_code_sell_export)) {
|
|
$accountingaccount3 = new AccountingAccount($db);
|
|
$accountingaccount3->fetch(0, $object->accountancy_code_sell_export, 1);
|
|
|
|
print $accountingaccount3->getNomUrl(0, 1, 1, '', 1);
|
|
}
|
|
} else {
|
|
print dolPrintHTML($object->accountancy_code_sell_export);
|
|
}
|
|
print '</td></tr>';
|
|
|
|
// Accountancy buy code
|
|
print '<tr class="tr_collapseaccounting'.($collapse ? ' hidden' : '').'"><td class="nowrap">';
|
|
print $langs->trans("ProductAccountancyBuyCode");
|
|
print '</td><td>';
|
|
if (isModEnabled('accounting')) {
|
|
if (!empty($object->accountancy_code_buy)) {
|
|
$accountingaccount4 = new AccountingAccount($db);
|
|
$accountingaccount4->fetch(0, $object->accountancy_code_buy, 1);
|
|
|
|
print $accountingaccount4->getNomUrl(0, 1, 1, '', 1);
|
|
}
|
|
} else {
|
|
print dolPrintHTML($object->accountancy_code_buy);
|
|
}
|
|
print '</td></tr>';
|
|
|
|
// Accountancy buy code intra-community
|
|
if ($mysoc->isInEEC()) {
|
|
print '<tr class="tr_collapseaccounting'.($collapse ? ' hidden' : '').'"><td class="nowrap">';
|
|
print $langs->trans("ProductAccountancyBuyIntraCode");
|
|
print '</td><td>';
|
|
if (isModEnabled('accounting')) {
|
|
if (!empty($object->accountancy_code_buy_intra)) {
|
|
$accountingaccount5 = new AccountingAccount($db);
|
|
$accountingaccount5->fetch(0, $object->accountancy_code_buy_intra, 1);
|
|
|
|
print $accountingaccount5->getNomUrl(0, 1, 1, '', 1);
|
|
}
|
|
} else {
|
|
print dolPrintHTML($object->accountancy_code_buy_intra);
|
|
}
|
|
print '</td></tr>';
|
|
}
|
|
|
|
// Accountancy buy code export
|
|
print '<tr class="tr_collapseaccounting'.($collapse ? ' hidden' : '').'"><td class="nowrap">';
|
|
print $langs->trans("ProductAccountancyBuyExportCode");
|
|
print '</td><td>';
|
|
if (isModEnabled('accounting')) {
|
|
if (!empty($object->accountancy_code_buy_export)) {
|
|
$accountingaccount6 = new AccountingAccount($db);
|
|
$accountingaccount6->fetch(0, $object->accountancy_code_buy_export, 1);
|
|
|
|
print $accountingaccount6->getNomUrl(0, 1, 1, '', 1);
|
|
}
|
|
} else {
|
|
print dolPrintHTML($object->accountancy_code_buy_export);
|
|
}
|
|
print '</td></tr>';
|
|
}
|
|
|
|
// Description
|
|
print '<tr><td class="tdtop">'.$langs->trans("Description").'</td>';
|
|
print '<td class="wordbreakimp wrapimp"><div class="longmessagecut">'.(dol_textishtml($object->description) ? $object->description : dol_nl2br($object->description, 1, true)).'</div></td></tr>';
|
|
|
|
// Public URL
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_PUBLIC_URL')) {
|
|
print '<tr><td>'.$langs->trans("PublicUrl").'</td><td>';
|
|
print dol_print_url($object->url, '_blank', 128);
|
|
print '</td></tr>';
|
|
}
|
|
|
|
if ($object->isService() && isModEnabled('workstation')) {
|
|
$workstation = new Workstation($db);
|
|
$res = $workstation->fetch($object->fk_default_workstation);
|
|
|
|
print '<tr><td>'.$langs->trans("DefaultWorkstation").'</td><td>';
|
|
print(!empty($workstation->id) ? $workstation->getNomUrl(1) : '');
|
|
print '</td>';
|
|
}
|
|
|
|
// Parent product.
|
|
if (isModEnabled('variants') && ($object->isProduct() || $object->isService())) {
|
|
$combination = new ProductCombination($db);
|
|
|
|
if ($combination->fetchByFkProductChild($object->id) > 0) {
|
|
$prodstatic = new Product($db);
|
|
$prodstatic->fetch($combination->fk_product_parent);
|
|
|
|
// Parent product
|
|
print '<tr><td>'.$langs->trans("ParentProduct").'</td><td>';
|
|
print $prodstatic->getNomUrl(1);
|
|
print '</td></tr>';
|
|
}
|
|
}
|
|
|
|
print '</table>';
|
|
|
|
print '</div>';
|
|
print '<div class="fichehalfright">';
|
|
print '<div class="underbanner clearboth"></div>';
|
|
|
|
print '<table class="border tableforfield centpercent">';
|
|
|
|
// Categories
|
|
if (isModEnabled('category')) {
|
|
print '<tr><td class="valignmiddle">'.$langs->trans("Categories").'</td><td>';
|
|
print $form->showCategories($object->id, Categorie::TYPE_PRODUCT, 1);
|
|
print "</td></tr>";
|
|
}
|
|
|
|
if ($object->isService()) {
|
|
// Duration
|
|
require_once DOL_DOCUMENT_ROOT.'/core/class/cunits.class.php';
|
|
$measuringUnits = new CUnits($db);
|
|
$durations = [];
|
|
$plural = '';
|
|
if ($object->duration_value > 1) {
|
|
$plural = 's';
|
|
}
|
|
$result = $measuringUnits->fetchAll('', 'scale', 0, 0, ['t.active' => 1, 't.unit_type' => 'time']);
|
|
if ($result !== -1) {
|
|
foreach ($measuringUnits->records as $record) {
|
|
$durations[$record->short_label] = dol_ucfirst((string) $record->label) . $plural;
|
|
}
|
|
}
|
|
print '<tr><td class="titlefieldmiddle">'.$langs->trans("Duration").'</td><td>';
|
|
if ($object->duration_value) {
|
|
print $object->duration_value;
|
|
print(!empty($object->duration_unit) && isset($durations[$object->duration_unit]) ? " ".$langs->trans($durations[$object->duration_unit])." " : '');
|
|
}
|
|
|
|
// Mandatory period
|
|
$htmltooltip = $langs->trans("mandatoryHelper");
|
|
if (!getDolGlobalString('SERVICE_STRICT_MANDATORY_PERIOD')) {
|
|
$htmltooltip .= '<br>'.$langs->trans("mandatoryHelper2");
|
|
}
|
|
if ($object->duration_value > 0) {
|
|
print ' ';
|
|
}
|
|
print '<input type="checkbox" class="valignmiddle" name="mandatoryperiod"'.($object->mandatory_period == 1 ? ' checked="checked"' : '').' disabled>';
|
|
print $form->textwithpicto($langs->trans("mandatoryperiod"), $htmltooltip, 1, 'info');
|
|
|
|
print '</td></tr>';
|
|
} else {
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_NATURE')) {
|
|
// Nature
|
|
print '<tr><td class="titlefieldmiddle">'.$form->textwithpicto($langs->trans("NatureOfProductShort"), $langs->trans("NatureOfProductDesc")).'</td><td>';
|
|
print $object->getLibFinished();
|
|
print '</td></tr>';
|
|
}
|
|
}
|
|
|
|
if (!$object->isService() && isModEnabled('bom') && $object->finished) {
|
|
print '<tr><td class="titlefieldmiddle">'.$form->textwithpicto($langs->trans("DefaultBOM"), $langs->trans("DefaultBOMDesc", $langs->transnoentitiesnoconv("Finished"))).'</td><td>';
|
|
if ($object->fk_default_bom) {
|
|
$bom_static = new BOM($db);
|
|
$bom_static->fetch($object->fk_default_bom);
|
|
print $bom_static->getNomUrl(1);
|
|
}
|
|
print '</td></tr>';
|
|
}
|
|
|
|
if (!$object->isService()) {
|
|
// Brut Weight
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_WEIGHT')) {
|
|
print '<tr><td class="titlefieldmiddle">'.$langs->trans("Weight").'</td><td>';
|
|
if ($object->weight != '') {
|
|
print $object->weight." ".measuringUnitString(0, "weight", $object->weight_units);
|
|
} else {
|
|
print ' ';
|
|
}
|
|
print "</td></tr>\n";
|
|
}
|
|
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_SIZE')) {
|
|
// Brut Length
|
|
print '<tr><td>'.$langs->trans("Length").' x '.$langs->trans("Width").' x '.$langs->trans("Height").'</td><td>';
|
|
if ($object->length != '' || $object->width != '' || $object->height != '') {
|
|
print $object->length;
|
|
if ($object->width) {
|
|
print " x ".$object->width;
|
|
}
|
|
if ($object->height) {
|
|
print " x ".$object->height;
|
|
}
|
|
print ' '.measuringUnitString(0, "size", $object->length_units);
|
|
} else {
|
|
print ' ';
|
|
}
|
|
print "</td></tr>\n";
|
|
}
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_SURFACE') || !getDolGlobalString('PRODUCT_DISABLE_VOLUME')) {
|
|
// Brut Surface / Volume
|
|
print '<tr><td>';
|
|
print(getDolGlobalString('PRODUCT_DISABLE_SURFACE') ? '' : $langs->trans("Surface"));
|
|
print (!getDolGlobalString('PRODUCT_DISABLE_SURFACE') && !getDolGlobalString('PRODUCT_DISABLE_VOLUME')) ? ' / ' : '';
|
|
print (getDolGlobalString('PRODUCT_DISABLE_VOLUME') ? '' : $langs->trans("Volume")).'</td><td>';
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_SURFACE') && $object->surface != '') {
|
|
print $object->surface." ".measuringUnitString(0, "surface", $object->surface_units);
|
|
}
|
|
print (!getDolGlobalString('PRODUCT_DISABLE_SURFACE') && !getDolGlobalString('PRODUCT_DISABLE_VOLUME') && $object->surface && $object->volume) ? ' / ' : '';
|
|
if (!getDolGlobalString('PRODUCT_DISABLE_VOLUME') && $object->volume != '') {
|
|
print $object->volume." ".measuringUnitString(0, "volume", $object->volume_units);
|
|
}
|
|
print "</td></tr>\n";
|
|
}
|
|
|
|
if (getDolGlobalString('PRODUCT_ADD_NET_MEASURE')) {
|
|
// Net Measure
|
|
print '<tr><td class="titlefieldmiddle">'.$langs->trans("NetMeasure").'</td><td>';
|
|
if ($object->net_measure != '') {
|
|
print $object->net_measure." ".measuringUnitString((int) $object->net_measure_units);
|
|
} else {
|
|
print ' ';
|
|
}
|
|
print '</td></tr>';
|
|
}
|
|
}
|
|
|
|
// Unit
|
|
if (getDolGlobalString('PRODUCT_USE_UNITS')) {
|
|
$unit = $object->getLabelOfUnit('long', $langs);
|
|
|
|
print '<tr><td>'.$langs->trans('DefaultUnitToShow').'</td><td>';
|
|
print $unit;
|
|
print '</td></tr>';
|
|
}
|
|
|
|
// Custom code
|
|
if (!$object->isService() && !getDolGlobalString('PRODUCT_DISABLE_CUSTOMS_INFO')) {
|
|
print '<tr><td>'.$form->textwithpicto($langs->trans("CustomsCode"), $langs->trans("CustomsCodeHelp")).'</td><td>'.dolPrintHTML($object->customcode).'</td></tr>';
|
|
|
|
// Origin country code
|
|
print '<tr><td>'.$langs->trans("Origin").'</td><td>'.getCountry($object->country_id, '', $db);
|
|
if (!empty($object->state_id)) {
|
|
print ' - '.getState($object->state_id, '0', $db);
|
|
}
|
|
print '</td></tr>';
|
|
}
|
|
|
|
// Quality Control
|
|
if (getDolGlobalString('PRODUCT_LOT_ENABLE_QUALITY_CONTROL')) {
|
|
print '<tr><td>'.$langs->trans("LifeTime").'</td><td>'.$object->lifetime.'</td></tr>';
|
|
print '<tr><td>'.$langs->trans("QCFrequency").'</td><td>'.$object->qc_frequency.'</td></tr>';
|
|
}
|
|
|
|
// Other attributes
|
|
$parameters = array();
|
|
include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_view.tpl.php';
|
|
|
|
// Note private
|
|
if (getDolGlobalString('MAIN_DISABLE_NOTES_TAB')) {
|
|
print '<!-- show Note --> '."\n";
|
|
print '<tr><td class="tdtop">'.$langs->trans("NotePrivate").'</td><td>'.(dol_textishtml($object->note_private) ? $object->note_private : dol_nl2br($object->note_private, 1, true)).'</td></tr>'."\n";
|
|
print '<!-- End show Note --> '."\n";
|
|
}
|
|
|
|
print "</table>\n";
|
|
print '</div>';
|
|
|
|
print '</div>';
|
|
print '<div class="clearboth"></div>';
|
|
}
|
|
|
|
print dol_get_fiche_end();
|
|
}
|
|
} elseif ($action != 'create') {
|
|
exit;
|
|
}
|
|
}
|
|
|
|
$tmpcode = '';
|
|
if (!empty($modCodeProduct->code_auto)) {
|
|
/** @var ModeleProductCode $modCodeProduct */
|
|
$tmpcode = $modCodeProduct->getNextValue($object, $object->type);
|
|
}
|
|
|
|
$formconfirm = '';
|
|
|
|
// Confirm delete product
|
|
if (($action == 'delete' && (empty($conf->use_javascript_ajax) || !empty($conf->dol_use_jmobile))) // Output when action = clone if jmobile or no js
|
|
|| (!empty($conf->use_javascript_ajax) && empty($conf->dol_use_jmobile))) { // Always output when not jmobile nor js
|
|
$formconfirm = $form->formconfirm("card.php?id=".$object->id, $langs->trans("DeleteProduct"), $langs->trans("ConfirmDeleteProduct"), "confirm_delete", '', 0, "action-delete");
|
|
}
|
|
if ($action == 'merge') {
|
|
$formquestion = array(
|
|
array(
|
|
'name' => 'product_origin',
|
|
'label' => $langs->trans('MergeOriginProduct'),
|
|
'type' => 'other',
|
|
'value' => $form->select_produits(0, 'product_origin', '', 0, 0, 1, 2, '', 1, array(), 0, 1, 0, 'minwidth200', 0, '', null, 1),
|
|
)
|
|
);
|
|
$formconfirm = $form->formconfirm($_SERVER["PHP_SELF"]."?id=".$object->id, $langs->trans("MergeProducts"), $langs->trans("ConfirmMergeProducts"), "confirm_merge", $formquestion, 'no', 1, 250);
|
|
}
|
|
|
|
// Clone confirmation
|
|
if (($action == 'clone' && (empty($conf->use_javascript_ajax) || !empty($conf->dol_use_jmobile))) // Output when action = clone if jmobile or no js
|
|
|| (!empty($conf->use_javascript_ajax) && empty($conf->dol_use_jmobile))) { // Always output when not jmobile nor js
|
|
// Define confirmation messages
|
|
$formquestionclone = array(
|
|
'text' => $langs->trans("ConfirmClone"),
|
|
0 => array('type' => 'text', 'name' => 'clone_ref', 'label' => $langs->trans("NewRefForClone"), 'value' => empty($tmpcode) ? $langs->trans("CopyOf").' '.$object->ref : $tmpcode, 'morecss' => 'width150'),
|
|
1 => array('type' => 'checkbox', 'name' => 'clone_content', 'label' => $langs->trans("CloneContentProduct"), 'value' => 1),
|
|
2 => array('type' => 'checkbox', 'name' => 'clone_categories', 'label' => $langs->trans("CloneCategoriesProduct"), 'value' => 1),
|
|
);
|
|
if (getDolGlobalString('PRODUIT_MULTIPRICES')) {
|
|
$formquestionclone[] = array('type' => 'checkbox', 'name' => 'clone_prices', 'label' => $langs->trans("ClonePricesProduct").' ('.$langs->trans("CustomerPrices").')', 'value' => 0);
|
|
}
|
|
if (getDolGlobalString('PRODUIT_SOUSPRODUITS')) {
|
|
$formquestionclone[] = array('type' => 'checkbox', 'name' => 'clone_composition', 'label' => $langs->trans('CloneCompositionProduct'), 'value' => 1);
|
|
}
|
|
if (isModEnabled('bom') && $user->hasRight('bom', 'write')) {
|
|
if ($object->fk_default_bom > 0) {
|
|
$formquestionclone[] = array('type' => 'checkbox', 'name' => 'clone_defbom', 'label' => $langs->trans("CloneDefBomProduct"), 'value' => getDolGlobalInt('BOM_CLONE_DEFBOM'));
|
|
}
|
|
$bomstatic = new BOM($db);
|
|
$bomlist = $bomstatic->fetchAll("", "", 0, 0, 'fk_product:=:'.(int) $object->id);
|
|
if (is_array($bomlist) && count($bomlist) > 0) {
|
|
$formquestionclone[] = array('type' => 'checkbox', 'name' => 'clone_otherboms', 'label' => $langs->trans("CloneOtherBomsProduct"), 'value' => 0);
|
|
}
|
|
}
|
|
$formconfirm .= $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('ToClone'), $langs->trans('ConfirmCloneProduct', $object->ref), 'confirm_clone', $formquestionclone, 'yes', 'action-clone', 350, 600);
|
|
}
|
|
|
|
// Call Hook formConfirm
|
|
$parameters = array('formConfirm' => $formconfirm, 'object' => $object);
|
|
$reshook = $hookmanager->executeHooks('formConfirm', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
|
|
if (empty($reshook)) {
|
|
$formconfirm .= $hookmanager->resPrint;
|
|
} elseif ($reshook > 0) {
|
|
$formconfirm = $hookmanager->resPrint;
|
|
}
|
|
|
|
// Print form confirm
|
|
print $formconfirm;
|
|
|
|
/*
|
|
* Action bar
|
|
*/
|
|
if ($action != 'create' && $action != 'edit') {
|
|
$cloneProductUrl = $_SERVER["PHP_SELF"].'?action=clone&token='.newToken();
|
|
$cloneButtonId = 'action-clone-no-ajax';
|
|
|
|
print "\n".'<div class="tabsAction">'."\n";
|
|
|
|
$parameters = array();
|
|
$reshook = $hookmanager->executeHooks('addMoreActionsButtons', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
|
|
if (empty($reshook)) {
|
|
if ($usercancreate) {
|
|
if (!isset($hookmanager->resArray['no_button_edit']) || $hookmanager->resArray['no_button_edit'] != 1) {
|
|
print dolGetButtonAction('', $langs->trans('Modify'), 'default', $_SERVER["PHP_SELF"].'?action=edit&token='.newToken().'&id='.$object->id, '', $usercancreate);
|
|
}
|
|
|
|
//Send
|
|
print dolGetButtonAction('', $langs->trans('SendMail'), 'email', $_SERVER["PHP_SELF"] . '?id=' . $object->id . '&action=presend&mode=init&token=' . newToken() . '#formmailbeforetitle');
|
|
|
|
if (!isset($hookmanager->resArray['no_button_copy']) || $hookmanager->resArray['no_button_copy'] != 1) {
|
|
if (!empty($conf->use_javascript_ajax) && empty($conf->dol_use_jmobile)) {
|
|
$cloneProductUrl = '';
|
|
$cloneButtonId = 'action-clone';
|
|
}
|
|
print dolGetButtonAction($langs->trans('ToClone'), '', 'clone', $cloneProductUrl, $cloneButtonId, $usercancreate);
|
|
}
|
|
}
|
|
$object_is_used = $object->isObjectUsed($object->id);
|
|
|
|
if ($usercandelete) {
|
|
if (empty($object_is_used)) {
|
|
if (!isset($hookmanager->resArray['no_button_delete']) || $hookmanager->resArray['no_button_delete'] != 1) {
|
|
if (!empty($conf->use_javascript_ajax) && empty($conf->dol_use_jmobile)) {
|
|
print dolGetButtonAction($langs->trans('Delete'), '', 'delete', '#', 'action-delete', true);
|
|
} else {
|
|
print dolGetButtonAction('', $langs->trans('Delete'), 'delete', $_SERVER["PHP_SELF"].'?action=delete&token='.newToken().'&id='.$object->id, '');
|
|
}
|
|
}
|
|
} else {
|
|
print dolGetButtonAction($langs->trans("ProductIsUsed"), $langs->trans('Delete'), 'delete', '#', '', false);
|
|
}
|
|
if (getDolGlobalInt('MAIN_FEATURES_LEVEL') > 1) {
|
|
print '<a class="butActionDelete" href="card.php?action=merge&id='.$object->id.'&token='.newToken().'" title="'.dol_escape_htmltag($langs->trans("MergeProducts")).'">'.$langs->trans('Merge').'</a>'."\n";
|
|
}
|
|
} else {
|
|
print dolGetButtonAction($langs->trans("NotEnoughPermissions"), $langs->trans('Delete'), 'delete', '#', '', false);
|
|
}
|
|
}
|
|
|
|
print "\n</div>\n";
|
|
}
|
|
|
|
|
|
/*
|
|
* All the "Add to" areas if PRODUCT_ADD_FORM_ADD_TO is set
|
|
*/
|
|
|
|
if (getDolGlobalString('PRODUCT_ADD_FORM_ADD_TO') && $object->id && ($action == '' || $action == 'view') && $object->status) {
|
|
//Variable used to check if any text is going to be printed
|
|
$html = '';
|
|
//print '<div class="fichecenter"><div class="fichehalfleft">';
|
|
|
|
// Propals
|
|
if (isModEnabled("propal") && $user->hasRight('propal', 'creer')) {
|
|
$propal = new Propal($db);
|
|
|
|
$langs->load("propal");
|
|
|
|
$otherprop = $propal->liste_array(2, 1, 0);
|
|
|
|
if (is_array($otherprop) && count($otherprop)) {
|
|
$html .= '<tr><td style="width: 200px;">';
|
|
$html .= $langs->trans("AddToDraftProposals").'</td><td>';
|
|
$html .= $form->selectarray("propalid", $otherprop, 0, 1);
|
|
$html .= '</td></tr>';
|
|
} else {
|
|
$html .= '<tr><td style="width: 200px;">';
|
|
$html .= $langs->trans("AddToDraftProposals").'</td><td>';
|
|
$html .= $langs->trans("NoDraftProposals");
|
|
$html .= '</td></tr>';
|
|
}
|
|
}
|
|
|
|
// Commande
|
|
if (isModEnabled('order') && $user->hasRight('commande', 'creer')) {
|
|
$commande = new Commande($db);
|
|
|
|
$langs->load("orders");
|
|
|
|
$othercom = $commande->liste_array(2, 1, null);
|
|
if (is_array($othercom) && count($othercom)) {
|
|
$html .= '<tr><td style="width: 200px;">';
|
|
$html .= $langs->trans("AddToDraftOrders").'</td><td>';
|
|
$html .= $form->selectarray("commandeid", $othercom, 0, 1);
|
|
$html .= '</td></tr>';
|
|
} else {
|
|
$html .= '<tr><td style="width: 200px;">';
|
|
$html .= $langs->trans("AddToDraftOrders").'</td><td>';
|
|
$html .= $langs->trans("NoDraftOrders");
|
|
$html .= '</td></tr>';
|
|
}
|
|
}
|
|
|
|
// Factures
|
|
if (isModEnabled('invoice') && $user->hasRight('facture', 'creer')) {
|
|
$invoice = new Facture($db);
|
|
|
|
$langs->load("bills");
|
|
|
|
$otherinvoice = $invoice->liste_array(2, 1, null);
|
|
if (is_array($otherinvoice) && count($otherinvoice)) {
|
|
$html .= '<tr><td style="width: 200px;">';
|
|
$html .= $langs->trans("AddToDraftInvoices").'</td><td>';
|
|
$html .= $form->selectarray("factureid", $otherinvoice, 0, 1);
|
|
$html .= '</td></tr>';
|
|
} else {
|
|
$html .= '<tr><td style="width: 200px;">';
|
|
$html .= $langs->trans("AddToDraftInvoices").'</td><td>';
|
|
$html .= $langs->trans("NoDraftInvoices");
|
|
$html .= '</td></tr>';
|
|
}
|
|
}
|
|
|
|
// If any text is going to be printed, then we show the table
|
|
if (!empty($html)) {
|
|
print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'">';
|
|
print '<input type="hidden" name="token" value="'.newToken().'">';
|
|
print '<input type="hidden" name="action" value="addin">';
|
|
|
|
print load_fiche_titre($langs->trans("AddToDraft"), '', '');
|
|
|
|
print dol_get_fiche_head();
|
|
|
|
$html .= '<tr><td class="nowrap">'.$langs->trans("Quantity").' ';
|
|
$html .= '<input type="text" class="flat" name="qty" size="1" value="1"></td>';
|
|
$html .= '<td class="nowrap">'.$langs->trans("ReductionShort").'(%) ';
|
|
$html .= '<input type="text" class="flat" name="remise_percent" size="1" value="0">';
|
|
$html .= '</td></tr>';
|
|
|
|
print '<table class="centpercent border">';
|
|
print $html;
|
|
print '</table>';
|
|
|
|
print '<div class="center">';
|
|
print '<input type="submit" class="button button-add" value="'.$langs->trans("Add").'">';
|
|
print '</div>';
|
|
|
|
print dol_get_fiche_end();
|
|
|
|
print '</form>';
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Generated documents
|
|
*/
|
|
|
|
if (GETPOST('modelselected')) {
|
|
$action = 'presend';
|
|
}
|
|
|
|
if ($action != 'create' && $action != 'edit' && $action != 'delete') {
|
|
print '<div class="fichecenter"><div class="fichehalfleft">';
|
|
print '<a name="builddoc"></a>'; // ancre
|
|
|
|
// Documents
|
|
$objectref = dol_sanitizeFileName((string) $object->ref);
|
|
if (!empty($conf->product->multidir_output[$object->entity ?? $conf->entity])) {
|
|
$filedir = $conf->product->multidir_output[$object->entity ?? $conf->entity].'/'.$objectref; //Check repertories of current entities
|
|
} else {
|
|
$filedir = $conf->product->dir_output.'/'.$objectref;
|
|
}
|
|
$urlsource = $_SERVER["PHP_SELF"]."?id=".$object->id;
|
|
$genallowed = $usercanread;
|
|
$delallowed = $usercancreate;
|
|
|
|
print $formfile->showdocuments($modulepart, $object->ref, $filedir, $urlsource, (int) $genallowed, (int) $delallowed, '', 0, 0, 0, 28, 0, '', '', '', $langs->getDefaultLang(), '', $object);
|
|
$somethingshown = $formfile->numoffiles;
|
|
|
|
print '</div><div class="fichehalfright">';
|
|
|
|
$MAXEVENT = 10;
|
|
$morehtmlcenter = '<div class="nowraponall">';
|
|
$morehtmlcenter .= dolGetButtonTitle($langs->trans('FullConversation'), '', 'fa fa-comments imgforviewmode', DOL_URL_ROOT.'/product/messaging.php?id='.$object->id);
|
|
$morehtmlcenter .= dolGetButtonTitle($langs->trans('SeeAll'), '', 'fa fa-bars imgforviewmode', DOL_URL_ROOT.'/product/agenda.php?id='.$object->id);
|
|
$morehtmlcenter .= '</div>';
|
|
|
|
// List of actions on element
|
|
include_once DOL_DOCUMENT_ROOT.'/core/class/html.formactions.class.php';
|
|
$formactions = new FormActions($db);
|
|
$somethingshown = $formactions->showactions($object, 'product', 0, 1, '', $MAXEVENT, '', $morehtmlcenter); // Show all action for product
|
|
|
|
print '</div></div>';
|
|
|
|
// Presend form
|
|
$modelmail = 'product_send';
|
|
$defaulttopic = $object->label;
|
|
$diroutput = $conf->product->multidir_output[$object->entity ?? $conf->entity];
|
|
$trackid = 'prod' . $object->id;
|
|
|
|
include DOL_DOCUMENT_ROOT.'/core/tpl/card_presend.tpl.php';
|
|
}
|
|
|
|
// End of page
|
|
llxFooter();
|
|
$db->close();
|