diff --git a/htdocs/admin/stocktransfer.php b/htdocs/admin/stocktransfer.php
new file mode 100644
index 00000000000..10d29453a49
--- /dev/null
+++ b/htdocs/admin/stocktransfer.php
@@ -0,0 +1,526 @@
+
+ * Copyright (C) 2021 SuperAdmin
+ *
+ * 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 .
+ */
+
+/**
+ * \file stocktransfer/admin/setup.php
+ * \ingroup stocktransfer
+ * \brief StockTransfer setup page.
+ */
+
+// Load Dolibarr environment
+$res = 0;
+// Try main.inc.php into web root known defined into CONTEXT_DOCUMENT_ROOT (not always defined)
+if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) $res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
+// Try main.inc.php into web root detected using web root calculated from SCRIPT_FILENAME
+$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME']; $tmp2 = realpath(__FILE__); $i = strlen($tmp) - 1; $j = strlen($tmp2) - 1;
+while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) { $i--; $j--; }
+if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) $res = @include substr($tmp, 0, ($i + 1))."/main.inc.php";
+if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) $res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php";
+// Try main.inc.php using relative path
+if (!$res && file_exists("../main.inc.php")) $res = @include "../main.inc.php";
+if (!$res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php";
+if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php";
+if (!$res) die("Include of main fails");
+
+global $langs, $user;
+
+// Libraries
+require_once DOL_DOCUMENT_ROOT."/core/lib/admin.lib.php";
+require_once DOL_DOCUMENT_ROOT.'/product/stock/stocktransfer/class/stocktransfer.class.php';
+require_once DOL_DOCUMENT_ROOT.'/product/stock/stocktransfer/lib/stocktransfer.lib.php';
+
+// Translations
+$langs->loadLangs(array("admin", "stocks"));
+
+// Access control
+if (!$user->admin) accessforbidden();
+
+// Parameters
+$action = GETPOST('action', 'alpha');
+$backtopage = GETPOST('backtopage', 'alpha');
+
+$value = GETPOST('value', 'alpha');
+
+$arrayofparameters = array(
+ 'STOCKTRANSFER_MYPARAM1'=>array('css'=>'minwidth200', 'enabled'=>1),
+ 'STOCKTRANSFER_MYPARAM2'=>array('css'=>'minwidth500', 'enabled'=>1)
+);
+
+$error = 0;
+$setupnotempty = 0;
+
+
+/*
+ * Actions
+ */
+
+if ((float) DOL_VERSION >= 6)
+{
+ include DOL_DOCUMENT_ROOT.'/core/actions_setmoduleoptions.inc.php';
+}
+
+if ($action == 'updateMask')
+{
+ $maskconststocktransfer = GETPOST('maskconststocktransfer', 'alpha');
+ $maskstocktransfer = GETPOST('maskStockTransfer', 'alpha');
+
+ if ($maskconststocktransfer) $res = dolibarr_set_const($db, $maskconststocktransfer, $maskstocktransfer, 'chaine', 0, '', $conf->entity);
+
+ if (!$res > 0) $error++;
+
+ if (!$error)
+ {
+ setEventMessages($langs->trans("SetupSaved"), null, 'mesgs');
+ } else {
+ setEventMessages($langs->trans("Error"), null, 'errors');
+ }
+} elseif ($action == 'specimen')
+{
+ $modele = GETPOST('module', 'alpha');
+ $tmpobjectkey = GETPOST('object');
+
+ $tmpobject = new $tmpobjectkey($db);
+ $tmpobject->initAsSpecimen();
+
+ // Search template files
+ $file = ''; $classname = ''; $filefound = 0;
+ $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
+ foreach ($dirmodels as $reldir)
+ {
+ $file = dol_buildpath($reldir."core/modules/stocktransfer/doc/pdf_".$modele."_".strtolower($tmpobjectkey).".modules.php", 0);
+ if (file_exists($file))
+ {
+ $filefound = 1;
+ $classname = "pdf_".$modele;
+ break;
+ }
+ }
+
+ if ($filefound)
+ {
+ require_once $file;
+
+ $module = new $classname($db);
+
+ if ($module->write_file($tmpobject, $langs) > 0)
+ {
+ header("Location: ".DOL_URL_ROOT."/document.php?modulepart=".strtolower($tmpobjectkey)."&file=SPECIMEN.pdf");
+ return;
+ } else {
+ setEventMessages($module->error, null, 'errors');
+ dol_syslog($module->error, LOG_ERR);
+ }
+ } else {
+ setEventMessages($langs->trans("ErrorModuleNotFound"), null, 'errors');
+ dol_syslog($langs->trans("ErrorModuleNotFound"), LOG_ERR);
+ }
+}
+
+// Activate a model
+elseif ($action == 'set')
+{
+ $ret = addDocumentModel($value, 'stocktransfer', $label, $scandir);
+} elseif ($action == 'del')
+{
+ $tmpobjectkey = GETPOST('object');
+
+ $ret = delDocumentModel($value, $type);
+ if ($ret > 0)
+ {
+ $constforval = strtoupper($tmpobjectkey).'_ADDON_PDF';
+ if ($conf->global->$constforval == "$value") dolibarr_del_const($db, $constforval, $conf->entity);
+ }
+}
+
+// Set default model
+elseif ($action == 'setdoc')
+{
+ $tmpobjectkey = GETPOST('object');
+ $constforval = strtoupper($tmpobjectkey).'_ADDON_PDF';
+ if (dolibarr_set_const($db, $constforval, $value, 'chaine', 0, '', $conf->entity))
+ {
+ // The constant that was read before the new set
+ // We therefore requires a variable to have a coherent view
+ $conf->global->$constforval = $value;
+ }
+
+ // On active le modele
+ $ret = delDocumentModel($value, $type);
+ if ($ret > 0)
+ {
+ $ret = addDocumentModel($value, $type, $label, $scandir);
+ }
+} elseif ($action == 'setmod')
+{
+ // TODO Check if numbering module chosen can be activated
+ // by calling method canBeActivated
+ $tmpobjectkey = GETPOST('object');
+ $constforval = 'STOCKTRANSFER_'.strtoupper($tmpobjectkey)."_ADDON";
+ dolibarr_set_const($db, $constforval, $value, 'chaine', 0, '', $conf->entity);
+}
+
+
+
+/*
+ * View
+ */
+
+$form = new Form($db);
+
+$dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
+
+$page_name = "StockTransferSetup";
+llxHeader('', $langs->trans($page_name));
+
+// Subheader
+$linkback = ''.$langs->trans("BackToModuleList").'';
+
+print load_fiche_titre($langs->trans($page_name), $linkback, 'stock');
+
+// Configuration header
+$head = stocktransferAdminPrepareHead();
+dol_fiche_head($head, 'settings', '', -1, "stocktransfer@stocktransfer");
+
+// Setup page goes here
+echo ''.$langs->trans("StockTransferSetupPage").'';
+
+
+/*if ($action == 'edit')
+{
+ print '
';
+ print '
';
+} else {
+ if (!empty($arrayofparameters))
+ {
+ print '';
+ print '| '.$langs->trans("Parameter").' | '.$langs->trans("Value").' |
';
+
+ foreach ($arrayofparameters as $key => $val)
+ {
+ $setupnotempty++;
+
+ print '| ';
+ $tooltiphelp = (($langs->trans($key.'Tooltip') != $key.'Tooltip') ? $langs->trans($key.'Tooltip') : '');
+ print $form->textwithpicto($langs->trans($key), $tooltiphelp);
+ print ' | '.$conf->global->$key.' |
';
+ }
+
+ print '
';
+
+ print '';
+ }
+ else
+ {
+ print '
'.$langs->trans("NothingToSetup");
+ }
+}*/
+
+
+$moduledir = 'stocktransfer';
+$myTmpObjects = array();
+$myTmpObjects[$moduledir]=array('includerefgeneration'=>1, 'includedocgeneration'=>1);
+
+foreach ($myTmpObjects as $myTmpObjectKey => $myTmpObjectArray) {
+ if ($myTmpObjectKey == 'MyObject') continue;
+ if ($myTmpObjectArray['includerefgeneration']) {
+ /*
+ * Orders Numbering model
+ */
+ $setupnotempty++;
+
+ print load_fiche_titre($langs->trans("NumberingModules", $myTmpObjectKey), '', '');
+
+ print '';
+ print '';
+ print '| '.$langs->trans("Name").' | ';
+ print ''.$langs->trans("Description").' | ';
+ print ''.$langs->trans("Example").' | ';
+ print ''.$langs->trans("Status").' | ';
+ print ''.$langs->trans("ShortInfo").' | ';
+ print '
'."\n";
+
+ clearstatcache();
+
+ foreach ($dirmodels as $reldir)
+ {
+ $dir = dol_buildpath($reldir."core/modules/".$moduledir);
+
+ if (is_dir($dir))
+ {
+ $handle = opendir($dir);
+ if (is_resource($handle))
+ {
+ while (($file = readdir($handle)) !== false)
+ {
+ if (strpos($file, 'mod_'.strtolower($myTmpObjectKey).'_') === 0 && substr($file, dol_strlen($file) - 3, 3) == 'php')
+ {
+ $file = substr($file, 0, dol_strlen($file) - 4);
+
+ require_once $dir.'/'.$file.'.php';
+
+ $module = new $file($db);
+
+ // Show modules according to features level
+ if ($module->version == 'development' && $conf->global->MAIN_FEATURES_LEVEL < 2) continue;
+ if ($module->version == 'experimental' && $conf->global->MAIN_FEATURES_LEVEL < 1) continue;
+
+ if ($module->isEnabled())
+ {
+ dol_include_once('/'.$moduledir.'/class/'.strtolower($myTmpObjectKey).'.class.php');
+
+ print '| '.$module->name." | \n";
+ print $module->info();
+ print ' | ';
+
+ // Show example of numbering model
+ print '';
+ $tmp = $module->getExample();
+ if (preg_match('/^Error/', $tmp)) print ' '.$langs->trans($tmp).' ';
+ elseif ($tmp == 'NotConfigured') print $langs->trans($tmp);
+ else print $tmp;
+ print ' | '."\n";
+
+ print '';
+ $constforvar = 'STOCKTRANSFER_'.strtoupper($myTmpObjectKey).'_ADDON';
+ if ($conf->global->$constforvar == $file)
+ {
+ print img_picto($langs->trans("Activated"), 'switch_on');
+ } else {
+ print '';
+ print img_picto($langs->trans("Disabled"), 'switch_off');
+ print '';
+ }
+ print ' | ';
+
+ $mytmpinstance = new $myTmpObjectKey($db);
+ $mytmpinstance->initAsSpecimen();
+
+ // Info
+ $htmltooltip = '';
+ $htmltooltip .= ''.$langs->trans("Version").': '.$module->getVersion().'
';
+
+ $nextval = $module->getNextValue($mytmpinstance);
+ if ("$nextval" != $langs->trans("NotAvailable")) { // Keep " on nextval
+ $htmltooltip .= ''.$langs->trans("NextValue").': ';
+ if ($nextval) {
+ if (preg_match('/^Error/', $nextval) || $nextval == 'NotConfigured')
+ $nextval = $langs->trans($nextval);
+ $htmltooltip .= $nextval.'
';
+ } else {
+ $htmltooltip .= $langs->trans($module->error).'
';
+ }
+ }
+
+ print '';
+ print $form->textwithpicto('', $htmltooltip, 1, 0);
+ print ' | ';
+
+ print "
\n";
+ }
+ }
+ }
+ closedir($handle);
+ }
+ }
+ }
+ print "
\n";
+ }
+
+ if ($myTmpObjectArray['includedocgeneration']) {
+ /*
+ * Document templates generators
+ */
+ $setupnotempty++;
+ $type = strtolower($myTmpObjectKey);
+
+ print load_fiche_titre($langs->trans("DocumentModules", $myTmpObjectKey), '', '');
+
+ // Load array def with activated templates
+ $def = array();
+ $sql = "SELECT nom";
+ $sql .= " FROM ".MAIN_DB_PREFIX."document_model";
+ $sql .= " WHERE type = '".$type."'";
+ $sql .= " AND entity = ".$conf->entity;
+ $resql = $db->query($sql);
+ if ($resql)
+ {
+ $i = 0;
+ $num_rows = $db->num_rows($resql);
+ while ($i < $num_rows)
+ {
+ $array = $db->fetch_array($resql);
+ array_push($def, $array[0]);
+ $i++;
+ }
+ } else {
+ dol_print_error($db);
+ }
+
+ print "\n";
+ print "\n";
+ print '| '.$langs->trans("Name").' | ';
+ print ''.$langs->trans("Description").' | ';
+ print ''.$langs->trans("Status")." | \n";
+ print ''.$langs->trans("Default")." | \n";
+ print ''.$langs->trans("ShortInfo").' | ';
+ print ''.$langs->trans("Preview").' | ';
+ print "
\n";
+
+ clearstatcache();
+
+ foreach ($dirmodels as $reldir)
+ {
+ foreach (array('', '/doc') as $valdir)
+ {
+ $realpath = $reldir."core/modules/".$moduledir.$valdir;
+ $dir = dol_buildpath($realpath);
+
+ if (is_dir($dir))
+ {
+ $handle = opendir($dir);
+ if (is_resource($handle))
+ {
+ while (($file = readdir($handle)) !== false)
+ {
+ $filelist[] = $file;
+ }
+ closedir($handle);
+ arsort($filelist);
+
+ foreach ($filelist as $file)
+ {
+ if (preg_match('/\.modules\.php$/i', $file) && preg_match('/^(pdf_|doc_)/', $file))
+ {
+ if (file_exists($dir.'/'.$file))
+ {
+ $name = substr($file, 4, dol_strlen($file) - 16);
+ $classname = substr($file, 0, dol_strlen($file) - 12);
+
+ require_once $dir.'/'.$file;
+ $module = new $classname($db);
+
+ $modulequalified = 1;
+ if ($module->version == 'development' && $conf->global->MAIN_FEATURES_LEVEL < 2) $modulequalified = 0;
+ if ($module->version == 'experimental' && $conf->global->MAIN_FEATURES_LEVEL < 1) $modulequalified = 0;
+
+ if ($modulequalified)
+ {
+ print '| ';
+ print (empty($module->name) ? $name : $module->name);
+ print " | \n";
+ if (method_exists($module, 'info')) print $module->info($langs);
+ else print $module->description;
+ print ' | ';
+
+ // Active
+ if (in_array($name, $def))
+ {
+ print ''."\n";
+ print '';
+ print img_picto($langs->trans("Enabled"), 'switch_on');
+ print '';
+ print ' | ';
+ } else {
+ print ''."\n";
+ print 'scandir.'&label='.urlencode($module->name).'">'.img_picto($langs->trans("Disabled"), 'switch_off').'';
+ print " | ";
+ }
+
+ // Default
+ print '';
+ $constforvar = 'STOCKTRANSFER_'.strtoupper($myTmpObjectKey).'_ADDON';
+ if ($conf->global->$constforvar == $name)
+ {
+ print img_picto($langs->trans("Default"), 'on');
+ } else {
+ print 'scandir.'&label='.urlencode($module->name).'" alt="'.$langs->trans("Default").'">'.img_picto($langs->trans("Disabled"), 'off').'';
+ }
+ print ' | ';
+
+ // Info
+ $htmltooltip = ''.$langs->trans("Name").': '.$module->name;
+ $htmltooltip .= '
'.$langs->trans("Type").': '.($module->type ? $module->type : $langs->trans("Unknown"));
+ if ($module->type == 'pdf')
+ {
+ $htmltooltip .= '
'.$langs->trans("Width").'/'.$langs->trans("Height").': '.$module->page_largeur.'/'.$module->page_hauteur;
+ }
+ $htmltooltip .= '
'.$langs->trans("Path").': '.preg_replace('/^\//', '', $realpath).'/'.$file;
+
+ $htmltooltip .= '
'.$langs->trans("FeaturesSupported").':';
+ $htmltooltip .= '
'.$langs->trans("Logo").': '.yn($module->option_logo, 1, 1);
+ $htmltooltip .= '
'.$langs->trans("MultiLanguage").': '.yn($module->option_multilang, 1, 1);
+
+ print '';
+ print $form->textwithpicto('', $htmltooltip, 1, 0);
+ print ' | ';
+
+ // Preview
+ print '';
+ if ($module->type == 'pdf')
+ {
+ print ''.img_object($langs->trans("Preview"), 'generic').'';
+ } else {
+ print img_object($langs->trans("PreviewNotAvailable"), 'generic');
+ }
+ print ' | ';
+
+ print "
\n";
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ print '
';
+ }
+}
+
+if (empty($setupnotempty)) {
+ print '
'.$langs->trans("NothingToSetup");
+}
+
+// Page end
+dol_fiche_end();
+
+llxFooter();
+$db->close();
diff --git a/htdocs/core/menus/standard/eldy.lib.php b/htdocs/core/menus/standard/eldy.lib.php
index c192d5e6ac6..5c82d1237f6 100644
--- a/htdocs/core/menus/standard/eldy.lib.php
+++ b/htdocs/core/menus/standard/eldy.lib.php
@@ -1641,6 +1641,12 @@ function print_left_eldy_menu($db, $menu_array_before, $menu_array_after, &$tabM
}
}
+ if($conf->stocktransfer->enabled) {
+ $newmenu->add('/product/stock/stocktransfer/stocktransfer_list.php', $langs->trans("ModuleStockTransferName"), 0, $user->rights->stocktransfer->stocktransfer->read, '', $mainmenu, 'stocktransfer', 0, '', '', '', img_picto('', 'stock', 'class="pictofixedwidth"'));
+ $newmenu->add('/product/stock/stocktransfer/stocktransfer_card.php?action=create', $langs->trans('StockTransferNew'), 1, $user->rights->stocktransfer->stocktransfer->write);
+ $newmenu->add('/product/stock/stocktransfer/stocktransfer_list.php', $langs->trans('List'), 1, $user->rights->stocktransfer->stocktransfer->read);
+ }
+
// Inventory
if (!empty($conf->stock->enabled)) {
$langs->load("stocks");
diff --git a/htdocs/core/modules/modStockTransfer.class.php b/htdocs/core/modules/modStockTransfer.class.php
new file mode 100644
index 00000000000..cc5479410e3
--- /dev/null
+++ b/htdocs/core/modules/modStockTransfer.class.php
@@ -0,0 +1,535 @@
+
+ * Copyright (C) 2018-2019 Nicolas ZABOURI
+ * Copyright (C) 2019-2020 Frédéric France
+ * Copyright (C) 2021 SuperAdmin
+ *
+ * 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 .
+ */
+
+/**
+ * \defgroup stocktransfer Module StockTransfer
+ * \brief StockTransfer module descriptor.
+ *
+ * \file htdocs/stocktransfer/core/modules/modStockTransfer.class.php
+ * \ingroup stocktransfer
+ * \brief Description and activation file for module StockTransfer
+ */
+include_once DOL_DOCUMENT_ROOT.'/core/modules/DolibarrModules.class.php';
+
+/**
+ * Description and activation class for module StockTransfer
+ */
+class modStockTransfer extends DolibarrModules
+{
+ /**
+ * Constructor. Define names, constants, directories, boxes, permissions
+ *
+ * @param DoliDB $db Database handler
+ */
+ public function __construct($db)
+ {
+ global $langs, $conf;
+ $this->db = $db;
+
+ $langs->load('stocks');
+ // Id for module (must be unique).
+ // Use here a free id (See in Home -> System information -> Dolibarr for list of used modules id).
+ $this->numero = 701; // TODO Go on page https://wiki.dolibarr.org/index.php/List_of_modules_id to reserve an id number for your module
+ // Key text used to identify module (for permissions, menus, etc...)
+ $this->rights_class = 'stocktransfer';
+ // Family can be 'base' (core modules),'crm','financial','hr','projects','products','ecm','technic' (transverse modules),'interface' (link with external tools),'other','...'
+ // It is used to group modules by family in module setup page
+ $this->family = "other";
+ // Module position in the family on 2 digits ('01', '10', '20', ...)
+ $this->module_position = '90';
+ // Gives the possibility for the module, to provide his own family info and position of this family (Overwrite $this->family and $this->module_position. Avoid this)
+ //$this->familyinfo = array('myownfamily' => array('position' => '01', 'label' => $langs->trans("MyOwnFamily")));
+ // Module label (no space allowed), used if translation string 'ModuleStockTransferName' not found (StockTransfer is name of module).
+ $this->name = preg_replace('/^mod/i', '', get_class($this));
+ // Module description, used if translation string 'ModuleStockTransferDesc' not found (StockTransfer is name of module).
+ $this->description = $langs->trans("ModuleStockTransferDesc");
+ // Used only if file README.md and README-LL.md not found.
+ $this->descriptionlong = "StockTransfer description (Long)";
+ $this->editor_name = 'Editor name';
+ $this->editor_url = 'https://www.example.com';
+ // Possible values for version are: 'development', 'experimental', 'dolibarr', 'dolibarr_deprecated' or a version string like 'x.y.z'
+ $this->version = 'dolibarr';
+ // Url to the file with your last numberversion of this module
+ //$this->url_last_version = 'http://www.example.com/versionmodule.txt';
+
+ // Key used in llx_const table to save module status enabled/disabled (where STOCKTRANSFER is value of property name of module in uppercase)
+ $this->const_name = 'MAIN_MODULE_'.strtoupper($this->name);
+ // Name of image file used for this module.
+ // If file is in theme/yourtheme/img directory under name object_pictovalue.png, use this->picto='pictovalue'
+ // If file is in module/img directory under name object_pictovalue.png, use this->picto='pictovalue@module'
+ $this->picto = 'stock';
+ // Define some features supported by module (triggers, login, substitutions, menus, css, etc...)
+ $this->module_parts = array(
+ // Set this to 1 if module has its own trigger directory (core/triggers)
+ 'triggers' => 0,
+ // Set this to 1 if module has its own login method file (core/login)
+ 'login' => 0,
+ // Set this to 1 if module has its own substitution function file (core/substitutions)
+ 'substitutions' => 0,
+ // Set this to 1 if module has its own menus handler directory (core/menus)
+ 'menus' => 0,
+ // Set this to 1 if module overwrite template dir (core/tpl)
+ 'tpl' => 0,
+ // Set this to 1 if module has its own barcode directory (core/modules/barcode)
+ 'barcode' => 0,
+ // Set this to 1 if module has its own models directory (core/modules/xxx)
+ 'models' => 1,
+ // Set this to 1 if module has its own theme directory (theme)
+ 'theme' => 0,
+ // Set this to relative path of css file if module has its own css file
+ 'css' => array(
+ // '/stocktransfer/css/stocktransfer.css.php',
+ ),
+ // Set this to relative path of js file if module must load a js on all pages
+ 'js' => array(
+ // '/stocktransfer/js/stocktransfer.js.php',
+ ),
+ // Set here all hooks context managed by module. To find available hook context, make a "grep -r '>initHooks(' *" on source code. You can also set hook context to 'all'
+ 'hooks' => array(
+ // 'data' => array(
+ // 'hookcontext1',
+ // 'hookcontext2',
+ // ),
+ // 'entity' => '0',
+ ),
+ // Set this to 1 if features of module are opened to external users
+ 'moduleforexternal' => 0,
+ 'contactelement'=>1
+ );
+ // Data directories to create when module is enabled.
+ // Example: this->dirs = array("/stocktransfer/temp","/stocktransfer/subdir");
+ $this->dirs = array("/stocktransfer/temp");
+ // Config pages. Put here list of php page, stored into stocktransfer/admin directory, to use to setup module.
+ $this->config_page_url = array("stocktransfer.php");
+ // Dependencies
+ // A condition to hide module
+ $this->hidden = false;
+ // List of module class names as string that must be enabled if this module is enabled. Example: array('always1'=>'modModuleToEnable1','always2'=>'modModuleToEnable2', 'FR1'=>'modModuleToEnableFR'...)
+ $this->depends = array('modStock', 'modProduct');
+ $this->requiredby = array(); // List of module class names as string to disable if this one is disabled. Example: array('modModuleToDisable1', ...)
+ $this->conflictwith = array(); // List of module class names as string this module is in conflict with. Example: array('modModuleToDisable1', ...)
+ $this->langfiles = array("stocktransfer@stocktransfer");
+ $this->phpmin = array(5, 5); // Minimum version of PHP required by module
+ $this->need_dolibarr_version = array(11, -3); // Minimum version of Dolibarr required by module
+ $this->warnings_activation = array(); // Warning to show when we activate module. array('always'='text') or array('FR'='textfr','ES'='textes'...)
+ $this->warnings_activation_ext = array(); // Warning to show when we activate an external module. array('always'='text') or array('FR'='textfr','ES'='textes'...)
+ //$this->automatic_activation = array('FR'=>'StockTransferWasAutomaticallyActivatedBecauseOfYourCountryChoice');
+ //$this->always_enabled = true; // If true, can't be disabled
+
+ // Constants
+ // List of particular constants to add when module is enabled (key, 'chaine', value, desc, visible, 'current' or 'allentities', deleteonunactive)
+ // Example: $this->const=array(1 => array('STOCKTRANSFER_MYNEWCONST1', 'chaine', 'myvalue', 'This is a constant to add', 1),
+ // 2 => array('STOCKTRANSFER_MYNEWCONST2', 'chaine', 'myvalue', 'This is another constant to add', 0, 'current', 1)
+ // );
+ $this->const = array();
+
+ // Some keys to add into the overwriting translation tables
+ /*$this->overwrite_translation = array(
+ 'en_US:ParentCompany'=>'Parent company or reseller',
+ 'fr_FR:ParentCompany'=>'Maison mère ou revendeur'
+ )*/
+
+ if (!isset($conf->stocktransfer) || !isset($conf->stocktransfer->enabled)) {
+ $conf->stocktransfer = new stdClass();
+ $conf->stocktransfer->enabled = 0;
+ }
+
+ // Array to add new pages in new tabs
+ $this->tabs = array();
+ // Example:
+ // $this->tabs[] = array('data'=>'objecttype:+tabname1:Title1:mylangfile@stocktransfer:$user->rights->stocktransfer->read:/stocktransfer/mynewtab1.php?id=__ID__'); // To add a new tab identified by code tabname1
+ // $this->tabs[] = array('data'=>'objecttype:+tabname2:SUBSTITUTION_Title2:mylangfile@stocktransfer:$user->rights->othermodule->read:/stocktransfer/mynewtab2.php?id=__ID__', // To add another new tab identified by code tabname2. Label will be result of calling all substitution functions on 'Title2' key.
+ // $this->tabs[] = array('data'=>'objecttype:-tabname:NU:conditiontoremove'); // To remove an existing tab identified by code tabname
+ //
+ // Where objecttype can be
+ // 'categories_x' to add a tab in category view (replace 'x' by type of category (0=product, 1=supplier, 2=customer, 3=member)
+ // 'contact' to add a tab in contact view
+ // 'contract' to add a tab in contract view
+ // 'group' to add a tab in group view
+ // 'intervention' to add a tab in intervention view
+ // 'invoice' to add a tab in customer invoice view
+ // 'invoice_supplier' to add a tab in supplier invoice view
+ // 'member' to add a tab in fundation member view
+ // 'opensurveypoll' to add a tab in opensurvey poll view
+ // 'order' to add a tab in customer order view
+ // 'order_supplier' to add a tab in supplier order view
+ // 'payment' to add a tab in payment view
+ // 'payment_supplier' to add a tab in supplier payment view
+ // 'product' to add a tab in product view
+ // 'propal' to add a tab in propal view
+ // 'project' to add a tab in project view
+ // 'stock' to add a tab in stock view
+ // 'thirdparty' to add a tab in third party view
+ // 'user' to add a tab in user view
+
+ // Dictionaries
+ $this->dictionaries = array();
+ /* Example:
+ $this->dictionaries=array(
+ 'langs'=>'stocktransfer@stocktransfer',
+ // List of tables we want to see into dictonnary editor
+ 'tabname'=>array(MAIN_DB_PREFIX."table1", MAIN_DB_PREFIX."table2", MAIN_DB_PREFIX."table3"),
+ // Label of tables
+ 'tablib'=>array("Table1", "Table2", "Table3"),
+ // Request to select fields
+ 'tabsql'=>array('SELECT f.rowid as rowid, f.code, f.label, f.active FROM '.MAIN_DB_PREFIX.'table1 as f', 'SELECT f.rowid as rowid, f.code, f.label, f.active FROM '.MAIN_DB_PREFIX.'table2 as f', 'SELECT f.rowid as rowid, f.code, f.label, f.active FROM '.MAIN_DB_PREFIX.'table3 as f'),
+ // Sort order
+ 'tabsqlsort'=>array("label ASC", "label ASC", "label ASC"),
+ // List of fields (result of select to show dictionary)
+ 'tabfield'=>array("code,label", "code,label", "code,label"),
+ // List of fields (list of fields to edit a record)
+ 'tabfieldvalue'=>array("code,label", "code,label", "code,label"),
+ // List of fields (list of fields for insert)
+ 'tabfieldinsert'=>array("code,label", "code,label", "code,label"),
+ // Name of columns with primary key (try to always name it 'rowid')
+ 'tabrowid'=>array("rowid", "rowid", "rowid"),
+ // Condition to show each dictionary
+ 'tabcond'=>array($conf->stocktransfer->enabled, $conf->stocktransfer->enabled, $conf->stocktransfer->enabled)
+ );
+ */
+
+ // Boxes/Widgets
+ // Add here list of php file(s) stored in stocktransfer/core/boxes that contains a class to show a widget.
+ $this->boxes = array(
+ // 0 => array(
+ // 'file' => 'stocktransferwidget1.php@stocktransfer',
+ // 'note' => 'Widget provided by StockTransfer',
+ // 'enabledbydefaulton' => 'Home',
+ // ),
+ // ...
+ );
+
+ // Cronjobs (List of cron jobs entries to add when module is enabled)
+ // unit_frequency must be 60 for minute, 3600 for hour, 86400 for day, 604800 for week
+ $this->cronjobs = array(
+ // 0 => array(
+ // 'label' => 'MyJob label',
+ // 'jobtype' => 'method',
+ // 'class' => '/stocktransfer/class/stocktransfer.class.php',
+ // 'objectname' => 'StockTransfer',
+ // 'method' => 'doScheduledJob',
+ // 'parameters' => '',
+ // 'comment' => 'Comment',
+ // 'frequency' => 2,
+ // 'unitfrequency' => 3600,
+ // 'status' => 0,
+ // 'test' => '$conf->stocktransfer->enabled',
+ // 'priority' => 50,
+ // ),
+ );
+ // Example: $this->cronjobs=array(
+ // 0=>array('label'=>'My label', 'jobtype'=>'method', 'class'=>'/dir/class/file.class.php', 'objectname'=>'MyClass', 'method'=>'myMethod', 'parameters'=>'param1, param2', 'comment'=>'Comment', 'frequency'=>2, 'unitfrequency'=>3600, 'status'=>0, 'test'=>'$conf->stocktransfer->enabled', 'priority'=>50),
+ // 1=>array('label'=>'My label', 'jobtype'=>'command', 'command'=>'', 'parameters'=>'param1, param2', 'comment'=>'Comment', 'frequency'=>1, 'unitfrequency'=>3600*24, 'status'=>0, 'test'=>'$conf->stocktransfer->enabled', 'priority'=>50)
+ // );
+
+ // Permissions provided by this module
+ $this->rights = array();
+ $r = 10;
+ // Add here entries to declare new permissions
+ /* BEGIN MODULEBUILDER PERMISSIONS */
+ $this->rights[$r][0] = $this->numero + $r; // Permission id (must not be already used)
+ $this->rights[$r][1] = $langs->trans('StockTransferRightRead'); // Permission label
+ $this->rights[$r][4] = 'stocktransfer'; // In php code, permission will be checked by test if ($user->rights->stocktransfer->level1->level2)
+ $this->rights[$r][5] = 'read'; // In php code, permission will be checked by test if ($user->rights->stocktransfer->level1->level2)
+ $r++;
+ $this->rights[$r][0] = $this->numero + $r; // Permission id (must not be already used)
+ $this->rights[$r][1] = $langs->trans('StockTransferRightCreateUpdate'); // Permission label
+ $this->rights[$r][4] = 'stocktransfer'; // In php code, permission will be checked by test if ($user->rights->stocktransfer->level1->level2)
+ $this->rights[$r][5] = 'write'; // In php code, permission will be checked by test if ($user->rights->stocktransfer->level1->level2)
+ $r++;
+ $this->rights[$r][0] = $this->numero + $r; // Permission id (must not be already used)
+ $this->rights[$r][1] = $langs->trans('StockTransferRightDelete'); // Permission label
+ $this->rights[$r][4] = 'stocktransfer'; // In php code, permission will be checked by test if ($user->rights->stocktransfer->level1->level2)
+ $this->rights[$r][5] = 'delete'; // In php code, permission will be checked by test if ($user->rights->stocktransfer->level1->level2)
+ $r++;
+ /* END MODULEBUILDER PERMISSIONS */
+
+ // Main menu entries to add
+ $langs->load('stocktransfer@stocktransfer');
+ $this->menu = array();
+ $r = 0;
+ // Add here entries to declare new menus
+ /* BEGIN MODULEBUILDER TOPMENU */
+ /*$this->menu[$r++] = array(
+ 'fk_menu'=>'', // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode
+ 'type'=>'top', // This is a Top menu entry
+ 'titre'=>'ModuleStockTransferName',
+ 'mainmenu'=>'stocktransfer',
+ 'leftmenu'=>'',
+ 'url'=>'/stocktransfer/stocktransferindex.php',
+ 'langs'=>'stocktransfer@stocktransfer', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory.
+ 'position'=>1000 + $r,
+ 'enabled'=>'$conf->stocktransfer->enabled', // Define condition to show or hide menu entry. Use '$conf->stocktransfer->enabled' if entry must be visible if module is enabled.
+ 'perms'=>'1', // Use 'perms'=>'$user->rights->stocktransfer->stocktransfer->read' if you want your menu with a permission rules
+ 'target'=>'',
+ 'user'=>2, // 0=Menu for internal users, 1=external users, 2=both
+ );*/
+ /* END MODULEBUILDER TOPMENU */
+ /* BEGIN MODULEBUILDER LEFTMENU STOCKTRANSFER
+ $this->menu[$r++]=array(
+ 'fk_menu'=>'fk_mainmenu=stocktransfer', // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode
+ 'type'=>'left', // This is a Top menu entry
+ 'titre'=>'StockTransfer',
+ 'mainmenu'=>'stocktransfer',
+ 'leftmenu'=>'stocktransfer',
+ 'url'=>'/stocktransfer/stocktransferindex.php',
+ 'langs'=>'stocktransfer@stocktransfer', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory.
+ 'position'=>1000+$r,
+ 'enabled'=>'$conf->stocktransfer->enabled', // Define condition to show or hide menu entry. Use '$conf->stocktransfer->enabled' if entry must be visible if module is enabled.
+ 'perms'=>'$user->rights->stocktransfer->stocktransfer->read', // Use 'perms'=>'$user->rights->stocktransfer->level1->level2' if you want your menu with a permission rules
+ 'target'=>'',
+ 'user'=>2, // 0=Menu for internal users, 1=external users, 2=both
+ );
+ $this->menu[$r++]=array(
+ 'fk_menu'=>'fk_mainmenu=stocktransfer,fk_leftmenu=stocktransfer', // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode
+ 'type'=>'left', // This is a Left menu entry
+ 'titre'=>'List StockTransfer',
+ 'mainmenu'=>'stocktransfer',
+ 'leftmenu'=>'stocktransfer_stocktransfer_list',
+ 'url'=>'/stocktransfer/stocktransfer_list.php',
+ 'langs'=>'stocktransfer@stocktransfer', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory.
+ 'position'=>1000+$r,
+ 'enabled'=>'$conf->stocktransfer->enabled', // Define condition to show or hide menu entry. Use '$conf->stocktransfer->enabled' if entry must be visible if module is enabled. Use '$leftmenu==\'system\'' to show if leftmenu system is selected.
+ 'perms'=>'$user->rights->stocktransfer->stocktransfer->read', // Use 'perms'=>'$user->rights->stocktransfer->level1->level2' if you want your menu with a permission rules
+ 'target'=>'',
+ 'user'=>2, // 0=Menu for internal users, 1=external users, 2=both
+ );
+ $this->menu[$r++]=array(
+ 'fk_menu'=>'fk_mainmenu=stocktransfer,fk_leftmenu=stocktransfer', // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode
+ 'type'=>'left', // This is a Left menu entry
+ 'titre'=>'New StockTransfer',
+ 'mainmenu'=>'stocktransfer',
+ 'leftmenu'=>'stocktransfer_stocktransfer_new',
+ 'url'=>'/stocktransfer/stocktransfer_card.php?action=create',
+ 'langs'=>'stocktransfer@stocktransfer', // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory.
+ 'position'=>1000+$r,
+ 'enabled'=>'$conf->stocktransfer->enabled', // Define condition to show or hide menu entry. Use '$conf->stocktransfer->enabled' if entry must be visible if module is enabled. Use '$leftmenu==\'system\'' to show if leftmenu system is selected.
+ 'perms'=>'$user->rights->stocktransfer->stocktransfer->write', // Use 'perms'=>'$user->rights->stocktransfer->level1->level2' if you want your menu with a permission rules
+ 'target'=>'',
+ 'user'=>2, // 0=Menu for internal users, 1=external users, 2=both
+ );
+ */
+
+ /*$this->menu[$r++]=array(
+ // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode
+ 'fk_menu'=>'fk_mainmenu=products,fk_leftmenu=stock',
+ // This is a Left menu entry
+ 'type'=>'left',
+ 'titre'=>$langs->trans('StockTransferNew'),
+ 'mainmenu'=>'products',
+ 'leftmenu'=>'stocktransfer_stocktransfer',
+ 'url'=>'/stocktransfer/stocktransfer_card.php?action=create',
+ // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory.
+ 'langs'=>'stocktransfer@stocktransfer',
+ 'position'=>1100+$r,
+ // Define condition to show or hide menu entry. Use '$conf->stocktransfer->enabled' if entry must be visible if module is enabled. Use '$leftmenu==\'system\'' to show if leftmenu system is selected.
+ 'enabled'=>'$conf->stocktransfer->enabled',
+ // Use 'perms'=>'$user->rights->stocktransfer->level1->level2' if you want your menu with a permission rules
+ 'perms'=>'1',
+ 'target'=>'',
+ // 0=Menu for internal users, 1=external users, 2=both
+ 'user'=>2
+ );
+ $this->menu[$r++]=array(
+ // '' if this is a top menu. For left menu, use 'fk_mainmenu=xxx' or 'fk_mainmenu=xxx,fk_leftmenu=yyy' where xxx is mainmenucode and yyy is a leftmenucode
+ 'fk_menu'=>'fk_mainmenu=products,fk_leftmenu=stock',
+ // This is a Left menu entry
+ 'type'=>'left',
+ 'titre'=>$langs->trans('StockTransferList'),
+ 'mainmenu'=>'products',
+ 'leftmenu'=>'stocktransfer_stocktransferlist',
+ 'url'=>'/stocktransfer/stocktransfer_list.php',
+ // Lang file to use (without .lang) by module. File must be in langs/code_CODE/ directory.
+ 'langs'=>'stocktransfer@stocktransfer',
+ 'position'=>1100+$r,
+ // Define condition to show or hide menu entry. Use '$conf->stocktransfer->enabled' if entry must be visible if module is enabled. Use '$leftmenu==\'system\'' to show if leftmenu system is selected.
+ 'enabled'=>'$conf->stocktransfer->enabled',
+ // Use 'perms'=>'$user->rights->stocktransfer->level1->level2' if you want your menu with a permission rules
+ 'perms'=>'1',
+ 'target'=>'',
+ // 0=Menu for internal users, 1=external users, 2=both
+ 'user'=>2,
+ );*/
+
+ /* END MODULEBUILDER LEFTMENU STOCKTRANSFER */
+
+ // Exports profiles provided by this module
+ $r = 1;
+ /* BEGIN MODULEBUILDER EXPORT STOCKTRANSFER */
+ /*
+ $langs->load("stocktransfer@stocktransfer");
+ $this->export_code[$r]=$this->rights_class.'_'.$r;
+ $this->export_label[$r]='StockTransferLines'; // Translation key (used only if key ExportDataset_xxx_z not found)
+ $this->export_icon[$r]='stocktransfer@stocktransfer';
+ // Define $this->export_fields_array, $this->export_TypeFields_array and $this->export_entities_array
+ $keyforclass = 'StockTransfer'; $keyforclassfile='/stocktransfer/class/stocktransfer.class.php'; $keyforelement='stocktransfer@stocktransfer';
+ include DOL_DOCUMENT_ROOT.'/core/commonfieldsinexport.inc.php';
+ //$this->export_fields_array[$r]['t.fieldtoadd']='FieldToAdd'; $this->export_TypeFields_array[$r]['t.fieldtoadd']='Text';
+ //unset($this->export_fields_array[$r]['t.fieldtoremove']);
+ //$keyforclass = 'StockTransferLine'; $keyforclassfile='/stocktransfer/class/stocktransfer.class.php'; $keyforelement='stocktransferline@stocktransfer'; $keyforalias='tl';
+ //include DOL_DOCUMENT_ROOT.'/core/commonfieldsinexport.inc.php';
+ $keyforselect='stocktransfer'; $keyforaliasextra='extra'; $keyforelement='stocktransfer@stocktransfer';
+ include DOL_DOCUMENT_ROOT.'/core/extrafieldsinexport.inc.php';
+ //$keyforselect='stocktransferline'; $keyforaliasextra='extraline'; $keyforelement='stocktransferline@stocktransfer';
+ //include DOL_DOCUMENT_ROOT.'/core/extrafieldsinexport.inc.php';
+ //$this->export_dependencies_array[$r] = array('stocktransferline'=>array('tl.rowid','tl.ref')); // To force to activate one or several fields if we select some fields that need same (like to select a unique key if we ask a field of a child to avoid the DISTINCT to discard them, or for computed field than need several other fields)
+ //$this->export_special_array[$r] = array('t.field'=>'...');
+ //$this->export_examplevalues_array[$r] = array('t.field'=>'Example');
+ //$this->export_help_array[$r] = array('t.field'=>'FieldDescHelp');
+ $this->export_sql_start[$r]='SELECT DISTINCT ';
+ $this->export_sql_end[$r] =' FROM '.MAIN_DB_PREFIX.'stocktransfer as t';
+ //$this->export_sql_end[$r] =' LEFT JOIN '.MAIN_DB_PREFIX.'stocktransfer_line as tl ON tl.fk_stocktransfer = t.rowid';
+ $this->export_sql_end[$r] .=' WHERE 1 = 1';
+ $this->export_sql_end[$r] .=' AND t.entity IN ('.getEntity('stocktransfer').')';
+ $r++; */
+ /* END MODULEBUILDER EXPORT STOCKTRANSFER */
+
+ // Imports profiles provided by this module
+ $r = 1;
+ /* BEGIN MODULEBUILDER IMPORT STOCKTRANSFER */
+ /*
+ $langs->load("stocktransfer@stocktransfer");
+ $this->export_code[$r]=$this->rights_class.'_'.$r;
+ $this->export_label[$r]='StockTransferLines'; // Translation key (used only if key ExportDataset_xxx_z not found)
+ $this->export_icon[$r]='stocktransfer@stocktransfer';
+ $keyforclass = 'StockTransfer'; $keyforclassfile='/stocktransfer/class/stocktransfer.class.php'; $keyforelement='stocktransfer@stocktransfer';
+ include DOL_DOCUMENT_ROOT.'/core/commonfieldsinexport.inc.php';
+ $keyforselect='stocktransfer'; $keyforaliasextra='extra'; $keyforelement='stocktransfer@stocktransfer';
+ include DOL_DOCUMENT_ROOT.'/core/extrafieldsinexport.inc.php';
+ //$this->export_dependencies_array[$r]=array('mysubobject'=>'ts.rowid', 't.myfield'=>array('t.myfield2','t.myfield3')); // To force to activate one or several fields if we select some fields that need same (like to select a unique key if we ask a field of a child to avoid the DISTINCT to discard them, or for computed field than need several other fields)
+ $this->export_sql_start[$r]='SELECT DISTINCT ';
+ $this->export_sql_end[$r] =' FROM '.MAIN_DB_PREFIX.'stocktransfer as t';
+ $this->export_sql_end[$r] .=' WHERE 1 = 1';
+ $this->export_sql_end[$r] .=' AND t.entity IN ('.getEntity('stocktransfer').')';
+ $r++; */
+ /* END MODULEBUILDER IMPORT STOCKTRANSFER */
+ }
+
+ /**
+ * Function called when module is enabled.
+ * The init function add constants, boxes, permissions and menus (defined in constructor) into Dolibarr database.
+ * It also creates data directories
+ *
+ * @param string $options Options when enabling module ('', 'noboxes')
+ * @return int 1 if OK, 0 if KO
+ */
+ public function init($options = '')
+ {
+ global $db, $conf, $langs;
+
+ $result = $this->_load_tables('/stocktransfer/sql/');
+ if ($result < 0) return -1; // Do not activate module if error 'not allowed' returned when loading module SQL queries (the _load_table run sql with run_sql with the error allowed parameter set to 'default')
+
+ // Create extrafields during init
+ //include_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
+ //$extrafields = new ExtraFields($this->db);
+ //$result1=$extrafields->addExtraField('stocktransfer_myattr1', "New Attr 1 label", 'boolean', 1, 3, 'thirdparty', 0, 0, '', '', 1, '', 0, 0, '', '', 'stocktransfer@stocktransfer', '$conf->stocktransfer->enabled');
+ //$result2=$extrafields->addExtraField('stocktransfer_myattr2', "New Attr 2 label", 'varchar', 1, 10, 'project', 0, 0, '', '', 1, '', 0, 0, '', '', 'stocktransfer@stocktransfer', '$conf->stocktransfer->enabled');
+ //$result3=$extrafields->addExtraField('stocktransfer_myattr3', "New Attr 3 label", 'varchar', 1, 10, 'bank_account', 0, 0, '', '', 1, '', 0, 0, '', '', 'stocktransfer@stocktransfer', '$conf->stocktransfer->enabled');
+ //$result4=$extrafields->addExtraField('stocktransfer_myattr4', "New Attr 4 label", 'select', 1, 3, 'thirdparty', 0, 1, '', array('options'=>array('code1'=>'Val1','code2'=>'Val2','code3'=>'Val3')), 1,'', 0, 0, '', '', 'stocktransfer@stocktransfer', '$conf->stocktransfer->enabled');
+ //$result5=$extrafields->addExtraField('stocktransfer_myattr5', "New Attr 5 label", 'text', 1, 10, 'user', 0, 0, '', '', 1, '', 0, 0, '', '', 'stocktransfer@stocktransfer', '$conf->stocktransfer->enabled');
+
+ // Permissions
+ $this->remove($options);
+
+ $sql = array();
+
+ // Document templates
+ $moduledir = 'stocktransfer';
+ $myTmpObjects = array();
+ $myTmpObjects['StockTransfer']=array('includerefgeneration'=>0, 'includedocgeneration'=>0);
+
+ foreach ($myTmpObjects as $myTmpObjectKey => $myTmpObjectArray) {
+ if ($myTmpObjectKey == 'StockTransfer') continue;
+ if ($myTmpObjectArray['includerefgeneration']) {
+ $src=DOL_DOCUMENT_ROOT.'/install/doctemplates/stocktransfer/template_stocktransfers.odt';
+ $dirodt=DOL_DATA_ROOT.'/doctemplates/stocktransfer';
+ $dest=$dirodt.'/template_stocktransfers.odt';
+
+ if (file_exists($src) && ! file_exists($dest))
+ {
+ require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
+ dol_mkdir($dirodt);
+ $result=dol_copy($src, $dest, 0, 0);
+ if ($result < 0)
+ {
+ $langs->load("errors");
+ $this->error=$langs->trans('ErrorFailToCopyFile', $src, $dest);
+ return 0;
+ }
+ }
+
+ $sql = array_merge($sql, array(
+ "DELETE FROM ".MAIN_DB_PREFIX."document_model WHERE nom = 'standard_".strtolower($myTmpObjectKey)."' AND type = '".strtolower($myTmpObjectKey)."' AND entity = ".$conf->entity,
+ "INSERT INTO ".MAIN_DB_PREFIX."document_model (nom, type, entity) VALUES('standard_".strtolower($myTmpObjectKey)."','".strtolower($myTmpObjectKey)."',".$conf->entity.")",
+ "DELETE FROM ".MAIN_DB_PREFIX."document_model WHERE nom = 'generic_".strtolower($myTmpObjectKey)."_odt' AND type = '".strtolower($myTmpObjectKey)."' AND entity = ".$conf->entity,
+ "INSERT INTO ".MAIN_DB_PREFIX."document_model (nom, type, entity) VALUES('generic_".strtolower($myTmpObjectKey)."_odt', '".strtolower($myTmpObjectKey)."', ".$conf->entity.")"
+ ));
+ }
+ }
+
+ // Rôles
+ $resql = $db->query('SELECT rowid FROM '.MAIN_DB_PREFIX.'c_type_contact WHERE code = "STDEST" AND element = "StockTransfer" AND source = "internal"');
+ $res = $db->fetch_object($resql);
+ if(empty($res)) $db->query('INSERT INTO '.MAIN_DB_PREFIX.'c_type_contact(rowid, element, source, code, libelle, active, module, position) VALUES('.$this->getNextId().', "StockTransfer", "internal", "STRESP", "Responsable du transfert de stocks", 1, NULL, 0)');
+
+ $resql = $db->query('SELECT rowid FROM '.MAIN_DB_PREFIX.'c_type_contact WHERE code = "STFROM" AND element = "StockTransfer" AND source = "external"');
+ $res = $db->fetch_object($resql);
+ if(empty($res)) $db->query('INSERT INTO '.MAIN_DB_PREFIX.'c_type_contact(rowid, element, source, code, libelle, active, module, position) VALUES('.$this->getNextId().', "StockTransfer", "external", "STFROM", "Contact expéditeur transfert de stocks", 1, NULL, 0)');
+
+ $resql = $db->query('SELECT rowid FROM '.MAIN_DB_PREFIX.'c_type_contact WHERE code = "STDEST" AND element = "StockTransfer" AND source = "external"');
+ $res = $db->fetch_object($resql);
+ if(empty($res)) $db->query('INSERT INTO '.MAIN_DB_PREFIX.'c_type_contact(rowid, element, source, code, libelle, active, module, position) VALUES('.$this->getNextId().', "StockTransfer", "external", "STDEST", "Contact destinataire transfert de stocks", 1, NULL, 0)');
+
+ return $this->_init($sql, $options);
+ }
+
+ function getNextId() {
+
+ global $db;
+
+ // Get free id for insert
+ $newid = 0;
+ $sql = "SELECT max(rowid) newid from ".MAIN_DB_PREFIX."c_type_contact";
+ $result = $db->query($sql);
+ if ($result)
+ {
+ $obj = $db->fetch_object($result);
+ $newid = ($obj->newid + 1);
+ } else {
+ dol_print_error($db);
+ }
+ return $newid;
+ }
+
+ /**
+ * Function called when module is disabled.
+ * Remove from database constants, boxes and permissions from Dolibarr database.
+ * Data directories are not deleted
+ *
+ * @param string $options Options when enabling module ('', 'noboxes')
+ * @return int 1 if OK, 0 if KO
+ */
+ public function remove($options = '')
+ {
+ $sql = array();
+ return $this->_remove($sql, $options);
+ }
+}
diff --git a/htdocs/core/modules/stocktransfer/doc/pdf_eagle.modules.php b/htdocs/core/modules/stocktransfer/doc/pdf_eagle.modules.php
new file mode 100644
index 00000000000..c2d595a026e
--- /dev/null
+++ b/htdocs/core/modules/stocktransfer/doc/pdf_eagle.modules.php
@@ -0,0 +1,1212 @@
+
+ * Copyright (C) 2005-2012 Laurent Destailleur
+ * Copyright (C) 2005-2012 Regis Houssin
+ * Copyright (C) 2014-2015 Marcos García
+ * Copyright (C) 2018 Frédéric France
+ *
+ * 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 .
+ * or see https://www.gnu.org/
+ */
+
+/**
+ * \file htdocs/core/modules/expedition/doc/pdf_rouget.modules.php
+ * \ingroup expedition
+ * \brief Class file used to generate the dispatch slips for the Rouget model
+ */
+
+require_once DOL_DOCUMENT_ROOT . '/core/modules/stocktransfer/modules_stocktransfer.php';
+require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
+require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
+require_once DOL_DOCUMENT_ROOT.'/core/lib/pdf.lib.php';
+require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
+
+
+/**
+ * Class to build sending documents with model Eagle
+ */
+class pdf_eagle extends ModelePdfStockTransfer
+{
+ /**
+ * @var DoliDb Database handler
+ */
+ public $db;
+
+ /**
+ * @var string model name
+ */
+ public $name;
+
+ /**
+ * @var string model description (short text)
+ */
+ public $description;
+
+ /**
+ * @var string document type
+ */
+ public $type;
+
+ /**
+ * @var array Minimum version of PHP required by module.
+ * e.g.: PHP ≥ 5.5 = array(5, 5)
+ */
+ public $phpmin = array(5, 5);
+
+ /**
+ * Dolibarr version of the loaded document
+ * @var string
+ */
+ public $version = 'dolibarr';
+
+ /**
+ * @var int page_largeur
+ */
+ public $page_largeur;
+
+ /**
+ * @var int page_hauteur
+ */
+ public $page_hauteur;
+
+ /**
+ * @var array format
+ */
+ public $format;
+
+ /**
+ * @var int marge_gauche
+ */
+ public $marge_gauche;
+
+ /**
+ * @var int marge_droite
+ */
+ public $marge_droite;
+
+ /**
+ * @var int marge_haute
+ */
+ public $marge_haute;
+
+ /**
+ * @var int marge_basse
+ */
+ public $marge_basse;
+
+ /**
+ * Issuer
+ * @var Societe object that emits
+ */
+ public $emetteur;
+
+
+ /**
+ * Constructor
+ *
+ * @param DoliDB $db Database handler
+ */
+ public function __construct($db = 0)
+ {
+ global $conf, $langs, $mysoc;
+
+ $this->db = $db;
+ $this->name = $langs->trans("StockTransferSheet");
+ $this->description = $langs->trans("DocumentModelStandardPDF");
+
+ $this->type = 'pdf';
+ $formatarray = pdf_getFormat();
+ $this->page_largeur = $formatarray['width'];
+ $this->page_hauteur = $formatarray['height'];
+ $this->format = array($this->page_largeur, $this->page_hauteur);
+ $this->marge_gauche = isset($conf->global->MAIN_PDF_MARGIN_LEFT) ? $conf->global->MAIN_PDF_MARGIN_LEFT : 10;
+ $this->marge_droite = isset($conf->global->MAIN_PDF_MARGIN_RIGHT) ? $conf->global->MAIN_PDF_MARGIN_RIGHT : 10;
+ $this->marge_haute = isset($conf->global->MAIN_PDF_MARGIN_TOP) ? $conf->global->MAIN_PDF_MARGIN_TOP : 10;
+ $this->marge_basse = isset($conf->global->MAIN_PDF_MARGIN_BOTTOM) ? $conf->global->MAIN_PDF_MARGIN_BOTTOM : 10;
+
+ $this->option_logo = 1; // Display logo
+
+ // Get source company
+ $this->emetteur = $mysoc;
+ if (!$this->emetteur->country_code) $this->emetteur->country_code = substr($langs->defaultlang, -2); // By default if not defined
+
+ // Define position of columns
+ $this->posxdesc = $this->marge_gauche + 1;
+ $this->posxlot = $this->page_largeur - $this->marge_droite - 125;
+ $this->posxqty = $this->page_largeur - $this->marge_droite - 105;
+ $this->posxwarehousesource = $this->page_largeur - $this->marge_droite - 90;
+ $this->posxwarehousedestination = $this->page_largeur - $this->marge_droite - 45;
+ $this->posxpuht = $this->page_largeur - $this->marge_droite;
+
+ /*if (!empty($conf->global->STOCKTRANSFER_PDF_DISPLAY_AMOUNT_HT)) { // Show also the prices
+ $this->posxqty = $this->page_largeur - $this->marge_droite - 118;
+ $this->posxwarehousesource = $this->page_largeur - $this->marge_droite - 96;
+ $this->posxwarehousedestination = $this->page_largeur - $this->marge_droite - 68;
+ $this->posxpuht = $this->page_largeur - $this->marge_droite - 40;
+ $this->posxtotalht = $this->page_largeur - $this->marge_droite - 20;
+ }*/
+
+ //if (!empty($conf->global->STOCKTRANSFER_PDF_HIDE_WEIGHT_AND_VOLUME)) $this->posxqty = $this->posxwarehousesource;
+
+ $this->posxpicture = $this->posxqty - (empty($conf->global->MAIN_DOCUMENTS_WITH_PICTURE_WIDTH) ? 20 : $conf->global->MAIN_DOCUMENTS_WITH_PICTURE_WIDTH); // width of images
+
+ // To work with US executive format
+ if ($this->page_largeur < 210) {
+ $this->posxqty -= 20;
+ $this->posxpicture -= 20;
+ $this->posxwarehousesource -= 20;
+ $this->posxwarehousedestination -= 20;
+ }
+
+ if (!empty($conf->global->STOCKTRANSFER_PDF_HIDE_ORDERED)) {
+ $this->posxqty += ($this->posxwarehousedestination - $this->posxwarehousesource);
+ $this->posxpicture += ($this->posxwarehousedestination - $this->posxwarehousesource);
+ $this->posxwarehousesource = $this->posxwarehousedestination;
+ }
+ }
+
+ // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
+ /**
+ * Function to build pdf onto disk
+ *
+ * @param Object $object Object StockTransfer to generate (or id if old method)
+ * @param Translate $outputlangs Lang output object
+ * @param string $srctemplatepath Full path of source filename for generator using a template file
+ * @param int $hidedetails Do not show line details
+ * @param int $hidedesc Do not show desc
+ * @param int $hideref Do not show ref
+ * @return int 1=OK, 0=KO
+ */
+ public function write_file($object, $outputlangs, $srctemplatepath = '', $hidedetails = 0, $hidedesc = 0, $hideref = 0)
+ {
+ // phpcs:enable
+ global $db, $user, $conf, $langs, $hookmanager;
+
+ $object->fetch_thirdparty();
+
+ $this->atLeastOneBatch = $this->atLeastOneBatch($object);
+
+ if (!is_object($outputlangs)) $outputlangs = $langs;
+ // For backward compatibility with FPDF, force output charset to ISO, because FPDF expect text to be encoded in ISO
+ if (!empty($conf->global->MAIN_USE_FPDF)) $outputlangs->charset_output = 'ISO-8859-1';
+
+ // Load traductions files required by page
+ $outputlangs->loadLangs(array("main", "bills", "products", "dict", "companies", "propal", "deliveries", "sendings", "productbatch", "stocks", "stocktransfer@stocktransfer"));
+
+ $nblines = count($object->lines);
+
+ // Loop on each lines to detect if there is at least one image to show
+ $realpatharray = array();
+ if (!empty($conf->global->MAIN_GENERATE_SHIPMENT_WITH_PICTURE))
+ {
+ $objphoto = new Product($this->db);
+
+ for ($i = 0; $i < $nblines; $i++)
+ {
+ if (empty($object->lines[$i]->fk_product)) continue;
+
+ $objphoto = new Product($this->db);
+ $objphoto->fetch($object->lines[$i]->fk_product);
+ if (!empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO))
+ {
+ $pdir = get_exdir($object->lines[$i]->fk_product, 2, 0, 0, $objphoto, 'product').$object->lines[$i]->fk_product."/photos/";
+ $dir = $conf->product->dir_output.'/'.$pdir;
+ }
+ else
+ {
+ $pdir = get_exdir(0, 2, 0, 0, $objphoto, 'product').dol_sanitizeFileName($objphoto->ref).'/';
+ $dir = $conf->product->dir_output.'/'.$pdir;
+ }
+
+ $realpath = '';
+
+ foreach ($objphoto->liste_photos($dir, 1) as $key => $obj) {
+ if (empty($conf->global->CAT_HIGH_QUALITY_IMAGES)) {
+ // If CAT_HIGH_QUALITY_IMAGES not defined, we use thumb if defined and then original photo
+ if ($obj['photo_vignette'])
+ {
+ $filename = $obj['photo_vignette'];
+ }
+ else
+ {
+ $filename = $obj['photo'];
+ }
+ }
+ else
+ {
+ $filename = $obj['photo'];
+ }
+
+ $realpath = $dir.$filename;
+ break;
+ }
+
+ if ($realpath) $realpatharray[$i] = $realpath;
+ }
+ }
+
+ if (count($realpatharray) == 0) $this->posxpicture = $this->posxqty;
+
+ if ($conf->stocktransfer->dir_output)
+ {
+ // Definition de $dir et $file
+ if ($object->specimen)
+ {
+ $dir = $conf->stocktransfer->dir_output;
+ $file = $dir."/SPECIMEN.pdf";
+ }
+ else
+ {
+ $stocktransferref = dol_sanitizeFileName($object->ref);
+ $dir = $conf->stocktransfer->dir_output.'/'.$object->element."/".$stocktransferref;
+ $file = $dir."/".$stocktransferref.".pdf";
+ }
+
+ if (!file_exists($dir))
+ {
+ if (dol_mkdir($dir) < 0)
+ {
+ $this->error = $langs->transnoentities("ErrorCanNotCreateDir", $dir);
+ return 0;
+ }
+ }
+
+ if (file_exists($dir))
+ {
+ // Add pdfgeneration hook
+ if (!is_object($hookmanager))
+ {
+ include_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php';
+ $hookmanager = new HookManager($this->db);
+ }
+ $hookmanager->initHooks(array('pdfgeneration'));
+ $parameters = array('file'=>$file, 'object'=>$object, 'outputlangs'=>$outputlangs);
+ global $action;
+ $reshook = $hookmanager->executeHooks('beforePDFCreation', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
+
+ // Set nblines with the new facture lines content after hook
+ $nblines = count($object->lines);
+
+ $pdf = pdf_getInstance($this->format);
+ $default_font_size = pdf_getPDFFontSize($outputlangs);
+ $heightforinfotot = 8; // Height reserved to output the info and total part
+ $heightforfreetext = (isset($conf->global->MAIN_PDF_FREETEXT_HEIGHT) ? $conf->global->MAIN_PDF_FREETEXT_HEIGHT : 5); // Height reserved to output the free text on last page
+ $heightforfooter = $this->marge_basse + 8; // Height reserved to output the footer (value include bottom margin)
+ if ($conf->global->MAIN_GENERATE_DOCUMENTS_SHOW_FOOT_DETAILS > 0) $heightforfooter += 6;
+ $pdf->SetAutoPageBreak(1, 0);
+
+ if (class_exists('TCPDF'))
+ {
+ $pdf->setPrintHeader(false);
+ $pdf->setPrintFooter(false);
+ }
+ $pdf->SetFont(pdf_getPDFFont($outputlangs));
+ // Set path to the background PDF File
+ if (!empty($conf->global->MAIN_ADD_PDF_BACKGROUND))
+ {
+ $pagecount = $pdf->setSourceFile($conf->mycompany->dir_output.'/'.$conf->global->MAIN_ADD_PDF_BACKGROUND);
+ $tplidx = $pdf->importPage(1);
+ }
+
+ $pdf->Open();
+ $pagenb = 0;
+ $pdf->SetDrawColor(128, 128, 128);
+
+ if (method_exists($pdf, 'AliasNbPages')) $pdf->AliasNbPages();
+
+ $pdf->SetTitle($outputlangs->convToOutputCharset($object->ref));
+ $pdf->SetSubject($outputlangs->transnoentities("Shipment"));
+ $pdf->SetCreator("Dolibarr ".DOL_VERSION);
+ $pdf->SetAuthor($outputlangs->convToOutputCharset($user->getFullName($outputlangs)));
+ $pdf->SetKeyWords($outputlangs->convToOutputCharset($object->ref)." ".$outputlangs->transnoentities("Shipment"));
+ if (!empty($conf->global->MAIN_DISABLE_PDF_COMPRESSION)) $pdf->SetCompression(false);
+
+ $pdf->SetMargins($this->marge_gauche, $this->marge_haute, $this->marge_droite); // Left, Top, Right
+
+ // New page
+ $pdf->AddPage();
+ if (!empty($tplidx)) $pdf->useTemplate($tplidx);
+ $pagenb++;
+ $this->_pagehead($pdf, $object, 1, $outputlangs);
+ $pdf->SetFont('', '', $default_font_size - 1);
+ $pdf->MultiCell(0, 3, ''); // Set interline to 3
+ $pdf->SetTextColor(0, 0, 0);
+
+ $tab_top = 90;
+ $tab_top_newpage = (empty($conf->global->MAIN_PDF_DONOTREPEAT_HEAD) ? 42 : 10);
+ $tab_height = 130;
+ $tab_height_newpage = 150;
+
+ // Incoterm
+ $height_incoterms = 0;
+ if ($conf->incoterm->enabled)
+ {
+ $desc_incoterms = $object->getIncotermsForPDF();
+ if ($desc_incoterms)
+ {
+ $tab_top = 88;
+
+ $pdf->SetFont('', '', $default_font_size - 1);
+ $pdf->writeHTMLCell(190, 3, $this->posxdesc - 1, $tab_top - 1, dol_htmlentitiesbr($desc_incoterms), 0, 1);
+ $nexY = $pdf->GetY();
+ $height_incoterms = $nexY - $tab_top;
+
+ // Rect takes a length in 3rd parameter
+ $pdf->SetDrawColor(192, 192, 192);
+ $pdf->Rect($this->marge_gauche, $tab_top - 1, $this->page_largeur - $this->marge_gauche - $this->marge_droite, $height_incoterms + 1);
+
+ $tab_top = $nexY + 6;
+ $height_incoterms += 4;
+ }
+ }
+
+ if (!empty($object->note_public) || !empty($object->tracking_number))
+ {
+ $tab_top = 88 + $height_incoterms;
+ $tab_top_alt = $tab_top;
+
+ $pdf->SetFont('', 'B', $default_font_size - 2);
+
+ //$tab_top_alt += 1;
+
+ // Tracking number
+ if (!empty($object->tracking_number))
+ {
+ $pdf->writeHTMLCell(60, 4, $this->posxdesc - 1, $tab_top - 1, $outputlangs->transnoentities("TrackingNumber")." : ".$object->tracking_number, 0, 1, false, true, 'L');
+ $tab_top_alt = $pdf->GetY();
+
+ $object->getUrlTrackingStatus($object->tracking_number);
+ if (!empty($object->tracking_url))
+ {
+ if ($object->shipping_method_id > 0)
+ {
+ // Get code using getLabelFromKey
+ $code = $outputlangs->getLabelFromKey($this->db, $object->shipping_method_id, 'c_shipment_mode', 'rowid', 'code');
+ $label = '';
+ if ($object->tracking_url != $object->tracking_number) $label .= $outputlangs->trans("LinkToTrackYourPackage")."
";
+ $label .= $outputlangs->trans("SendingMethod").": ".$outputlangs->trans("SendingMethod".strtoupper($code));
+ //var_dump($object->tracking_url != $object->tracking_number);exit;
+ if ($object->tracking_url != $object->tracking_number)
+ {
+ $label .= " : ";
+ $label .= $object->tracking_url;
+ }
+ $pdf->SetFont('', 'B', $default_font_size - 2);
+ $pdf->writeHTMLCell(60, 4, $this->posxdesc - 1, $tab_top_alt, $label, 0, 1, false, true, 'L');
+
+ $tab_top_alt = $pdf->GetY();
+ }
+ }
+ }
+
+ // Notes
+ if (!empty($object->note_public))
+ {
+ $pdf->SetFont('', '', $default_font_size - 1); // Dans boucle pour gerer multi-page
+ $pdf->writeHTMLCell(190, 3, $this->posxdesc - 1, $tab_top_alt, dol_htmlentitiesbr($object->note_public), 0, 1);
+ }
+
+ $nexY = $pdf->GetY();
+ $height_note = $nexY - $tab_top;
+
+ // Rect takes a length in 3rd parameter
+ $pdf->SetDrawColor(192, 192, 192);
+ $pdf->Rect($this->marge_gauche, $tab_top - 1, $this->page_largeur - $this->marge_gauche - $this->marge_droite, $height_note + 1);
+
+ $tab_height = $tab_height - $height_note;
+ $tab_top = $nexY + 6;
+ }
+ else
+ {
+ $height_note = 0;
+ }
+
+ $iniY = $tab_top + 7;
+ $curY = $tab_top + 7;
+ $nexY = $tab_top + 7;
+
+ $TCacheEntrepots=array();
+ // Loop on each lines
+ for ($i = 0; $i < $nblines; $i++)
+ {
+ $curY = $nexY;
+ $pdf->SetFont('', '', $default_font_size - 1); // Into loop to work with multipage
+ $pdf->SetTextColor(0, 0, 0);
+
+ // Define size of image if we need it
+ $imglinesize = array();
+ if (!empty($realpatharray[$i])) $imglinesize = pdf_getSizeForImage($realpatharray[$i]);
+
+ $pdf->setTopMargin($tab_top_newpage);
+ $pdf->setPageOrientation('', 1, $heightforfooter + $heightforfreetext + $heightforinfotot); // The only function to edit the bottom margin of current page to set it.
+ $pageposbefore = $pdf->getPage();
+
+ $showpricebeforepagebreak = 1;
+ $posYAfterImage = 0;
+ $posYAfterDescription = 0;
+
+ // We start with Photo of product line
+ if (isset($imglinesize['width']) && isset($imglinesize['height']) && ($curY + $imglinesize['height']) > ($this->page_hauteur - ($heightforfooter + $heightforfreetext + $heightforinfotot))) // If photo too high, we moved completely on new page
+ {
+ $pdf->AddPage('', '', true);
+ if (!empty($tplidx)) $pdf->useTemplate($tplidx);
+ if (empty($conf->global->MAIN_PDF_DONOTREPEAT_HEAD)) $this->_pagehead($pdf, $object, 0, $outputlangs);
+ $pdf->setPage($pageposbefore + 1);
+
+ $curY = $tab_top_newpage;
+
+ // Allows data in the first page if description is long enough to break in multiples pages
+ if (!empty($conf->global->MAIN_PDF_DATA_ON_FIRST_PAGE))
+ $showpricebeforepagebreak = 1;
+ else
+ $showpricebeforepagebreak = 0;
+ }
+
+ if (isset($imglinesize['width']) && isset($imglinesize['height']))
+ {
+ $curX = $this->posxpicture - 1;
+ $pdf->Image($realpatharray[$i], $curX + (($this->posxqty - $this->posxpicture - $imglinesize['width']) / 2), $curY, $imglinesize['width'], $imglinesize['height'], '', '', '', 2, 300); // Use 300 dpi
+ // $pdf->Image does not increase value return by getY, so we save it manually
+ $posYAfterImage = $curY + $imglinesize['height'];
+ }
+
+ // Description of product line
+ $curX = $this->posxdesc - 1;
+
+ $pdf->startTransaction();
+ if(method_exists($object->lines[$i], 'fetch_product')) {
+ $object->lines[$i]->fetch_product();
+ $object->lines[$i]->label = $object->lines[$i]->product->label;
+ $object->lines[$i]->description = $object->lines[$i]->product->description;
+ }
+ //var_dump($object->lines[$i]->product);exit;
+ pdf_writelinedesc($pdf, $object, $i, $outputlangs, $this->posxpicture - $curX, 3, $curX, $curY, $hideref, $hidedesc);
+
+ $pageposafter = $pdf->getPage();
+ if ($pageposafter > $pageposbefore) // There is a pagebreak
+ {
+ $pdf->rollbackTransaction(true);
+ $pageposafter = $pageposbefore;
+ //print $pageposafter.'-'.$pageposbefore;exit;
+ $pdf->setPageOrientation('', 1, $heightforfooter); // The only function to edit the bottom margin of current page to set it.
+ pdf_writelinedesc($pdf, $object, $i, $outputlangs, $this->posxpicture - $curX, 3, $curX, $curY, $hideref, $hidedesc);
+
+ $pageposafter = $pdf->getPage();
+ $posyafter = $pdf->GetY();
+ //var_dump($posyafter); var_dump(($this->page_hauteur - ($heightforfooter+$heightforfreetext+$heightforinfotot))); exit;
+ if ($posyafter > ($this->page_hauteur - ($heightforfooter + $heightforfreetext + $heightforinfotot))) // There is no space left for total+free text
+ {
+ if ($i == ($nblines - 1)) // No more lines, and no space left to show total, so we create a new page
+ {
+ $pdf->AddPage('', '', true);
+ if (!empty($tplidx)) $pdf->useTemplate($tplidx);
+ if (empty($conf->global->MAIN_PDF_DONOTREPEAT_HEAD)) $this->_pagehead($pdf, $object, 0, $outputlangs);
+ $pdf->setPage($pageposafter + 1);
+ }
+ }
+ else
+ {
+ // We found a page break
+
+ // Allows data in the first page if description is long enough to break in multiples pages
+ if (!empty($conf->global->MAIN_PDF_DATA_ON_FIRST_PAGE))
+ $showpricebeforepagebreak = 1;
+ else
+ $showpricebeforepagebreak = 0;
+ }
+ }
+ else // No pagebreak
+ {
+ $pdf->commitTransaction();
+ }
+ $posYAfterDescription = $pdf->GetY();
+
+ $nexY = $pdf->GetY();
+ $pageposafter = $pdf->getPage();
+
+ $pdf->setPage($pageposbefore);
+ $pdf->setTopMargin($this->marge_haute);
+ $pdf->setPageOrientation('', 1, 0); // The only function to edit the bottom margin of current page to set it.
+
+ // We suppose that a too long description or photo were moved completely on next page
+ if ($pageposafter > $pageposbefore && empty($showpricebeforepagebreak)) {
+ $pdf->setPage($pageposafter); $curY = $tab_top_newpage;
+ }
+
+ // We suppose that a too long description is moved completely on next page
+ if ($pageposafter > $pageposbefore) {
+ $pdf->setPage($pageposafter); $curY = $tab_top_newpage;
+ }
+
+ $pdf->SetFont('', '', $default_font_size - 1); // On repositionne la police par defaut
+
+
+ $pdf->SetXY($this->posxqty, $curY);
+ // Lot / série
+ if(!empty($conf->productbatch->enabled)) {
+ $pdf->SetXY($this->posxlot, $curY);
+ $pdf->MultiCell(($this->posxqty - $this->posxlot), 3, $object->lines[$i]->batch, '', 'C');
+ }
+
+ // Qty
+ $pdf->SetXY($this->posxqty, $curY);
+ $pdf->writeHTMLCell($this->posxwarehousesource - $this->posxqty + 2, 3, $this->posxqty - 1, $curY, $object->lines[$i]->qty, 0, 0, false, true, 'C');
+ //$pdf->MultiCell(($this->posxwarehousesource - $this->posxqty), 3, $weighttxt.(($weighttxt && $voltxt)?'
':'').$voltxt,'','C');
+
+ // Warehouse source
+ $wh_source = new Entrepot($db);
+ if(!empty($TCacheEntrepots[$object->lines[$i]->fk_warehouse_source])) $wh_source = $TCacheEntrepots[$object->lines[$i]->fk_warehouse_source];
+ else {
+ $wh_source->fetch($object->lines[$i]->fk_warehouse_source);
+ $TCacheEntrepots[$object->lines[$i]->fk_warehouse_source] = $wh_source;
+ }
+ $pdf->SetXY($this->posxwarehousesource, $curY);
+ $pdf->MultiCell(($this->posxwarehousedestination - $this->posxwarehousesource), 3, $wh_source->ref.(!empty($wh_source->lieu) ? ' - '.$wh_source->lieu : ''), '', 'C');
+
+ // Warehouse destination
+ $wh_destination = new Entrepot($db);
+ if(!empty($TCacheEntrepots[$object->lines[$i]->fk_warehouse_destination])) $wh_destination = $TCacheEntrepots[$object->lines[$i]->fk_warehouse_destination];
+ else {
+ $wh_destination->fetch($object->lines[$i]->fk_warehouse_destination);
+ $TCacheEntrepots[$object->lines[$i]->fk_warehouse_destination] = $wh_destination;
+ }
+ $pdf->SetXY($this->posxwarehousedestination, $curY);
+ $pdf->MultiCell(($this->posxpuht - $this->posxwarehousedestination), 3, $wh_destination->ref.(!empty($wh_destination->lieu) ? ' - '.$wh_destination->lieu : ''), '', 'C');
+
+ if (!empty($conf->global->STOCKTRANSFER_PDF_DISPLAY_AMOUNT_HT))
+ {
+ $pdf->SetXY($this->posxpuht, $curY);
+ $pdf->MultiCell(($this->posxtotalht - $this->posxpuht - 1), 3, price($object->lines[$i]->subprice, 0, $outputlangs), '', 'R');
+
+ $pdf->SetXY($this->posxtotalht, $curY);
+ $pdf->MultiCell(($this->page_largeur - $this->marge_droite - $this->posxtotalht), 3, price($object->lines[$i]->total_ht, 0, $outputlangs), '', 'R');
+ }
+
+ $nexY += 3;
+ if ($weighttxt && $voltxt) $nexY += 2;
+
+ // Add line
+ if (!empty($conf->global->MAIN_PDF_DASH_BETWEEN_LINES) && $i < ($nblines - 1))
+ {
+ $pdf->setPage($pageposafter);
+ $pdf->SetLineStyle(array('dash'=>'1,1', 'color'=>array(80, 80, 80)));
+ //$pdf->SetDrawColor(190,190,200);
+ $pdf->line($this->marge_gauche, $nexY - 1, $this->page_largeur - $this->marge_droite, $nexY - 1);
+ $pdf->SetLineStyle(array('dash'=>0));
+ }
+
+ // Detect if some page were added automatically and output _tableau for past pages
+ while ($pagenb < $pageposafter)
+ {
+ $pdf->setPage($pagenb);
+ if ($pagenb == 1)
+ {
+ $this->_tableau($pdf, $tab_top, $this->page_hauteur - $tab_top - $heightforfooter, 0, $outputlangs, 0, 1);
+ }
+ else
+ {
+ $this->_tableau($pdf, $tab_top_newpage, $this->page_hauteur - $tab_top_newpage - $heightforfooter, 0, $outputlangs, 1, 1);
+ }
+ $this->_pagefoot($pdf, $object, $outputlangs, 1);
+ $pagenb++;
+ $pdf->setPage($pagenb);
+ $pdf->setPageOrientation('', 1, 0); // The only function to edit the bottom margin of current page to set it.
+ if (empty($conf->global->MAIN_PDF_DONOTREPEAT_HEAD)) $this->_pagehead($pdf, $object, 0, $outputlangs);
+ }
+ if (isset($object->lines[$i + 1]->pagebreak) && $object->lines[$i + 1]->pagebreak)
+ {
+ if ($pagenb == 1)
+ {
+ $this->_tableau($pdf, $tab_top, $this->page_hauteur - $tab_top - $heightforfooter, 0, $outputlangs, 0, 1);
+ }
+ else
+ {
+ $this->_tableau($pdf, $tab_top_newpage, $this->page_hauteur - $tab_top_newpage - $heightforfooter, 0, $outputlangs, 1, 1);
+ }
+ $this->_pagefoot($pdf, $object, $outputlangs, 1);
+ // New page
+ $pdf->AddPage();
+ if (!empty($tplidx)) $pdf->useTemplate($tplidx);
+ $pagenb++;
+ if (empty($conf->global->MAIN_PDF_DONOTREPEAT_HEAD)) $this->_pagehead($pdf, $object, 0, $outputlangs);
+ }
+ }
+
+ // Show square
+ if ($pagenb == 1)
+ {
+ $this->_tableau($pdf, $tab_top, $this->page_hauteur - $tab_top - $heightforinfotot - $heightforfreetext - $heightforfooter, 0, $outputlangs, 0, 0);
+ $bottomlasttab = $this->page_hauteur - $heightforinfotot - $heightforfreetext - $heightforfooter + 1;
+ }
+ else
+ {
+ $this->_tableau($pdf, $tab_top_newpage, $this->page_hauteur - $tab_top_newpage - $heightforinfotot - $heightforfreetext - $heightforfooter, 0, $outputlangs, 1, 0);
+ $bottomlasttab = $this->page_hauteur - $heightforinfotot - $heightforfreetext - $heightforfooter + 1;
+ }
+
+ // Affiche zone totaux
+ //$posy = $this->_tableau_tot($pdf, $object, 0, $bottomlasttab, $outputlangs);
+
+ // Pied de page
+ $this->_pagefoot($pdf, $object, $outputlangs);
+ if (method_exists($pdf, 'AliasNbPages')) $pdf->AliasNbPages();
+
+ $pdf->Close();
+
+ $pdf->Output($file, 'F');
+
+ // Add pdfgeneration hook
+ $hookmanager->initHooks(array('pdfgeneration'));
+ $parameters = array('file'=>$file, 'object'=>$object, 'outputlangs'=>$outputlangs);
+ global $action;
+ $reshook = $hookmanager->executeHooks('afterPDFCreation', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
+ if ($reshook < 0)
+ {
+ $this->error = $hookmanager->error;
+ $this->errors = $hookmanager->errors;
+ }
+
+ if (!empty($conf->global->MAIN_UMASK))
+ @chmod($file, octdec($conf->global->MAIN_UMASK));
+
+ $this->result = array('fullpath'=>$file);
+
+ return 1; // No error
+ }
+ else
+ {
+ $this->error = $langs->transnoentities("ErrorCanNotCreateDir", $dir);
+ return 0;
+ }
+ }
+ else
+ {
+ $this->error = $langs->transnoentities("ErrorConstantNotDefined", "EXP_OUTPUTDIR");
+ return 0;
+ }
+ }
+
+ // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
+ // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
+ /**
+ * Show total to pay
+ *
+ * @param PDF $pdf Object PDF
+ * @param Facture $object Object invoice
+ * @param int $deja_regle Montant deja regle
+ * @param int $posy Position depart
+ * @param Translate $outputlangs Objet langs
+ * @return int Position pour suite
+ */
+ protected function _tableau_tot(&$pdf, $object, $deja_regle, $posy, $outputlangs)
+ {
+ // phpcs:enable
+ global $conf, $mysoc;
+
+ $sign = 1;
+
+ $default_font_size = pdf_getPDFFontSize($outputlangs);
+
+ $tab2_top = $posy;
+ $tab2_hl = 4;
+ $pdf->SetFont('', 'B', $default_font_size - 1);
+
+ // Tableau total
+ $col1x = $this->posxqty - 50; $col2x = $this->posxqty;
+ /*if ($this->page_largeur < 210) // To work with US executive format
+ {
+ $col2x-=20;
+ }*/
+ if (empty($conf->global->STOCKTRANSFER_PDF_HIDE_ORDERED)) $largcol2 = ($this->posxwarehousesource - $this->posxqty);
+ else $largcol2 = ($this->posxwarehousedestination - $this->posxqty);
+
+ $useborder = 0;
+ $index = 0;
+
+ $totalWeighttoshow = '';
+ $totalVolumetoshow = '';
+
+ // Load dim data
+ $tmparray = $object->getTotalWeightVolume();
+ $totalWeight = $tmparray['weight'];
+ $totalVolume = $tmparray['volume'];
+ $totalOrdered = $tmparray['ordered'];
+ $totalToShip = $tmparray['toship'];
+ // Set trueVolume and volume_units not currently stored into database
+ if ($object->trueWidth && $object->trueHeight && $object->trueDepth)
+ {
+ $object->trueVolume = price(($object->trueWidth * $object->trueHeight * $object->trueDepth), 0, $outputlangs, 0, 0);
+ $object->volume_units = $object->size_units * 3;
+ }
+
+ if ($totalWeight != '') $totalWeighttoshow = showDimensionInBestUnit($totalWeight, 0, "weight", $outputlangs);
+ if ($totalVolume != '') $totalVolumetoshow = showDimensionInBestUnit($totalVolume, 0, "volume", $outputlangs);
+ if ($object->trueWeight) $totalWeighttoshow = showDimensionInBestUnit($object->trueWeight, $object->weight_units, "weight", $outputlangs);
+ if ($object->trueVolume) $totalVolumetoshow = showDimensionInBestUnit($object->trueVolume, $object->volume_units, "volume", $outputlangs);
+
+ $pdf->SetFillColor(255, 255, 255);
+ $pdf->SetXY($col1x, $tab2_top + $tab2_hl * $index);
+ $pdf->MultiCell($col2x - $col1x, $tab2_hl, $outputlangs->transnoentities("Total"), 0, 'L', 1);
+
+ if (empty($conf->global->STOCKTRANSFER_PDF_HIDE_ORDERED))
+ {
+ $pdf->SetXY($this->posxwarehousesource, $tab2_top + $tab2_hl * $index);
+ $pdf->MultiCell($this->posxwarehousedestination - $this->posxwarehousesource, $tab2_hl, $totalOrdered, 0, 'C', 1);
+ }
+
+ if (empty($conf->global->STOCKTRANSFER_PDF_HIDE_QTYTOSHIP))
+ {
+ $pdf->SetXY($this->posxwarehousedestination, $tab2_top + $tab2_hl * $index);
+ $pdf->MultiCell($this->posxpuht - $this->posxwarehousedestination, $tab2_hl, $totalToShip, 0, 'C', 1);
+ }
+
+ if (!empty($conf->global->STOCKTRANSFER_PDF_DISPLAY_AMOUNT_HT))
+ {
+ $pdf->SetXY($this->posxpuht, $tab2_top + $tab2_hl * $index);
+ $pdf->MultiCell($this->posxtotalht - $this->posxpuht, $tab2_hl, '', 0, 'C', 1);
+
+ $pdf->SetXY($this->posxtotalht, $tab2_top + $tab2_hl * $index);
+ $pdf->MultiCell($this->page_largeur - $this->marge_droite - $this->posxtotalht, $tab2_hl, price($object->total_ht, 0, $outputlangs), 0, 'C', 1);
+ }
+
+ if (empty($conf->global->STOCKTRANSFER_PDF_HIDE_WEIGHT_AND_VOLUME))
+ {
+ // Total Weight
+ if ($totalWeighttoshow)
+ {
+ $pdf->SetXY($this->posxqty, $tab2_top + $tab2_hl * $index);
+ $pdf->MultiCell(($this->posxwarehousesource - $this->posxqty), $tab2_hl, $totalWeighttoshow, 0, 'C', 1);
+
+ $index++;
+ }
+ if ($totalVolumetoshow)
+ {
+ $pdf->SetXY($this->posxqty, $tab2_top + $tab2_hl * $index);
+ $pdf->MultiCell(($this->posxwarehousesource - $this->posxqty), $tab2_hl, $totalVolumetoshow, 0, 'C', 1);
+
+ $index++;
+ }
+ if (!$totalWeighttoshow && !$totalVolumetoshow) $index++;
+ }
+
+ $pdf->SetTextColor(0, 0, 0);
+
+ return ($tab2_top + ($tab2_hl * $index));
+ }
+
+ // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
+ /**
+ * Show table for lines
+ *
+ * @param PDF $pdf Object PDF
+ * @param string $tab_top Top position of table
+ * @param string $tab_height Height of table (rectangle)
+ * @param int $nexY Y
+ * @param Translate $outputlangs Langs object
+ * @param int $hidetop Hide top bar of array
+ * @param int $hidebottom Hide bottom bar of array
+ * @return void
+ */
+ protected function _tableau(&$pdf, $tab_top, $tab_height, $nexY, $outputlangs, $hidetop = 0, $hidebottom = 0)
+ {
+ global $conf;
+
+ // Force to disable hidetop and hidebottom
+ $hidebottom = 0;
+ if ($hidetop) $hidetop = -1;
+
+ $default_font_size = pdf_getPDFFontSize($outputlangs);
+
+ // Amount in (at tab_top - 1)
+ $pdf->SetTextColor(0, 0, 0);
+ $pdf->SetFont('', '', $default_font_size - 2);
+
+ // Output Rect
+ $this->printRect($pdf, $this->marge_gauche, $tab_top, $this->page_largeur - $this->marge_gauche - $this->marge_droite, $tab_height, $hidetop, $hidebottom); // Rect takes a length in 3rd parameter and 4th parameter
+
+ $pdf->SetDrawColor(128, 128, 128);
+ $pdf->SetFont('', '', $default_font_size - 1);
+
+ if (empty($hidetop))
+ {
+ $pdf->line($this->marge_gauche, $tab_top + 5, $this->page_largeur - $this->marge_droite, $tab_top + 5);
+
+ $pdf->SetXY($this->posxdesc - 1, $tab_top + 1);
+ $pdf->MultiCell($this->posxwarehousesource - $this->posxdesc, 2, $outputlangs->transnoentities("Description"), '', 'L');
+ }
+
+ if(!empty($conf->productbatch->enabled) && $this->atLeastOneBatch) {
+ $pdf->line($this->posxlot - 1, $tab_top, $this->posxlot - 1, $tab_top + $tab_height);
+ if (empty($hidetop)) {
+ $pdf->SetXY($this->posxlot - 8, $tab_top + 1);
+ $pdf->MultiCell(($this->posxwarehousesource - $this->posxlot), 2, $outputlangs->transnoentities("Batch"), '', 'C');
+ }
+ }
+
+ $pdf->line($this->posxqty - 1, $tab_top, $this->posxqty - 1, $tab_top + $tab_height);
+ if (empty($hidetop))
+ {
+ $pdf->SetXY($this->posxqty - 1, $tab_top + 1);
+ $pdf->MultiCell(($this->posxwarehousesource - $this->posxqty), 2, $outputlangs->transnoentities("Qty"), '', 'C');
+ }
+
+ $pdf->line($this->posxwarehousesource - 1, $tab_top, $this->posxwarehousesource - 1, $tab_top + $tab_height);
+ if (empty($hidetop))
+ {
+ $pdf->SetXY($this->posxwarehousesource - 1, $tab_top + 1);
+ $pdf->MultiCell(($this->posxwarehousedestination - $this->posxwarehousesource), 2, $outputlangs->transnoentities("WarehouseSource"), '', 'C');
+ }
+
+
+ $pdf->line($this->posxwarehousedestination - 1, $tab_top, $this->posxwarehousedestination - 1, $tab_top + $tab_height);
+ if (empty($hidetop))
+ {
+ $pdf->SetXY($this->posxwarehousedestination-2.5, $tab_top + 1);
+ $pdf->MultiCell(($this->posxpuht - $this->posxwarehousedestination+4), 2, $outputlangs->transnoentities("WarehouseTarget"), '', 'C');
+ }
+
+ /*if (!empty($conf->global->STOCKTRANSFER_PDF_DISPLAY_AMOUNT_HT)) {
+ $pdf->line($this->posxpuht - 1, $tab_top, $this->posxpuht - 1, $tab_top + $tab_height);
+ if (empty($hidetop))
+ {
+ $pdf->SetXY($this->posxpuht - 1, $tab_top + 1);
+ $pdf->MultiCell(($this->posxtotalht - $this->posxpuht), 2, $outputlangs->transnoentities("PriceUHT"), '', 'C');
+ }
+
+ $pdf->line($this->posxtotalht - 1, $tab_top, $this->posxtotalht - 1, $tab_top + $tab_height);
+ if (empty($hidetop))
+ {
+ $pdf->SetXY($this->posxtotalht - 1, $tab_top + 1);
+ $pdf->MultiCell(($this->page_largeur - $this->marge_droite - $this->posxtotalht), 2, $outputlangs->transnoentities("TotalHT"), '', 'C');
+ }
+ }*/
+ }
+
+ function atLeastOneBatch($object) {
+
+ $atLeastOneBatch = false;
+
+ foreach ($object->lines as $line) {
+ if(!empty($line->batch)) {
+ return true;
+ }
+ }
+
+ return false;
+
+ }
+
+ // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
+ /**
+ * Show top header of page.
+ *
+ * @param PDF $pdf Object PDF
+ * @param Object $object Object to show
+ * @param int $showaddress 0=no, 1=yes
+ * @param Translate $outputlangs Object lang for output
+ * @return void
+ */
+ protected function _pagehead(&$pdf, $object, $showaddress, $outputlangs)
+ {
+ global $conf, $langs, $mysoc;
+
+ $langs->load("orders");
+
+ $default_font_size = pdf_getPDFFontSize($outputlangs);
+
+ pdf_pagehead($pdf, $outputlangs, $this->page_hauteur);
+
+ // Show Draft Watermark
+ if ($object->statut == 0 && (!empty($conf->global->SHIPPING_DRAFT_WATERMARK)))
+ {
+ pdf_watermark($pdf, $outputlangs, $this->page_hauteur, $this->page_largeur, 'mm', $conf->global->SHIPPING_DRAFT_WATERMARK);
+ }
+
+ //Prepare la suite
+ $pdf->SetTextColor(0, 0, 60);
+ $pdf->SetFont('', 'B', $default_font_size + 3);
+
+ $w = 110;
+
+ $posy = $this->marge_haute;
+ $posx = $this->page_largeur - $this->marge_droite - $w;
+
+ $pdf->SetXY($this->marge_gauche, $posy);
+
+ // Logo
+ $logo = $conf->mycompany->dir_output.'/logos/'.$this->emetteur->logo;
+ if ($this->emetteur->logo)
+ {
+ if (is_readable($logo))
+ {
+ $height = pdf_getHeightForLogo($logo);
+ $pdf->Image($logo, $this->marge_gauche, $posy, 0, $height); // width=0 (auto)
+ }
+ else
+ {
+ $pdf->SetTextColor(200, 0, 0);
+ $pdf->SetFont('', 'B', $default_font_size - 2);
+ $pdf->MultiCell($w, 3, $outputlangs->transnoentities("ErrorLogoFileNotFound", $logo), 0, 'L');
+ $pdf->MultiCell($w, 3, $outputlangs->transnoentities("ErrorGoToGlobalSetup"), 0, 'L');
+ }
+ }
+ else
+ {
+ $text = $this->emetteur->name;
+ $pdf->MultiCell($w, 4, $outputlangs->convToOutputCharset($text), 0, 'L');
+ }
+
+ // Show barcode
+ if (!empty($conf->barcode->enabled))
+ {
+ $posx = 105;
+ }
+ else
+ {
+ $posx = $this->marge_gauche + 3;
+ }
+ //$pdf->Rect($this->marge_gauche, $this->marge_haute, $this->page_largeur-$this->marge_gauche-$this->marge_droite, 30);
+ if (!empty($conf->barcode->enabled))
+ {
+ // TODO Build code bar with function writeBarCode of barcode module for sending ref $object->ref
+ //$pdf->SetXY($this->marge_gauche+3, $this->marge_haute+3);
+ //$pdf->Image($logo,10, 5, 0, 24);
+ }
+
+ $pdf->SetDrawColor(128, 128, 128);
+ if (!empty($conf->barcode->enabled))
+ {
+ // TODO Build code bar with function writeBarCode of barcode module for sending ref $object->ref
+ //$pdf->SetXY($this->marge_gauche+3, $this->marge_haute+3);
+ //$pdf->Image($logo,10, 5, 0, 24);
+ }
+
+
+ $posx = $this->page_largeur - $w - $this->marge_droite;
+ $posy = $this->marge_haute;
+
+ $pdf->SetFont('', 'B', $default_font_size + 2);
+ $pdf->SetXY($posx, $posy);
+ $pdf->SetTextColor(0, 0, 60);
+ $title = $outputlangs->transnoentities("StockTransferSheet");
+ $pdf->MultiCell($w, 4, $title, '', 'R');
+
+ $pdf->SetFont('', '', $default_font_size + 1);
+
+ $posy += 5;
+
+ $pdf->SetXY($posx, $posy);
+ $pdf->SetTextColor(0, 0, 60);
+ $pdf->MultiCell($w, 4, $outputlangs->transnoentities("Ref")." : ".$object->ref, '', 'R');
+
+ // Date reelle depart
+ if (!empty($object->date_prevue_depart))
+ {
+ $posy += 4;
+ $pdf->SetXY($posx, $posy);
+ $pdf->SetTextColor(0, 0, 60);
+ $pdf->MultiCell($w, 4, $outputlangs->transnoentities("DatePrevueDepart")." : ".dol_print_date($object->date_prevue_depart, "day", false, $outputlangs, true), '', 'R');
+ }
+
+ // Date reelle arrivée
+ if (!empty($object->date_prevue_arrivee))
+ {
+ $posy += 4;
+ $pdf->SetXY($posx, $posy);
+ $pdf->SetTextColor(0, 0, 60);
+ $pdf->MultiCell($w, 4, $outputlangs->transnoentities("DatePrevueArrivee")." : ".dol_print_date($object->date_prevue_arrivee, "day", false, $outputlangs, true), '', 'R');
+ }
+
+ // Date reelle depart
+ if (!empty($object->date_reelle_depart))
+ {
+ $posy += 4;
+ $pdf->SetXY($posx, $posy);
+ $pdf->SetTextColor(0, 0, 60);
+ $pdf->MultiCell($w, 4, $outputlangs->transnoentities("DateReelleDepart")." : ".dol_print_date($object->date_reelle_depart, "day", false, $outputlangs, true), '', 'R');
+ }
+
+ // Date reelle arrivée
+ if (!empty($object->date_reelle_arrivee))
+ {
+ $posy += 4;
+ $pdf->SetXY($posx, $posy);
+ $pdf->SetTextColor(0, 0, 60);
+ $pdf->MultiCell($w, 4, $outputlangs->transnoentities("DateReelleArrivee")." : ".dol_print_date($object->date_reelle_arrivee, "day", false, $outputlangs, true), '', 'R');
+ }
+
+ if (!empty($object->thirdparty->code_client))
+ {
+ $posy += 4;
+ $pdf->SetXY($posx, $posy);
+ $pdf->SetTextColor(0, 0, 60);
+ $pdf->MultiCell($w, 3, $outputlangs->transnoentities("CustomerCode")." : ".$outputlangs->transnoentities($object->thirdparty->code_client), '', 'R');
+ }
+
+
+ $pdf->SetFont('', '', $default_font_size + 3);
+ $Yoff = 25;
+
+ // Add list of linked orders
+ $origin = $object->origin;
+ $origin_id = $object->origin_id;
+
+ // TODO move to external function
+ if (!empty($conf->$origin->enabled)) // commonly $origin='commande'
+ {
+ $outputlangs->load('orders');
+
+ $classname = ucfirst($origin);
+ $linkedobject = new $classname($this->db);
+ $result = $linkedobject->fetch($origin_id);
+ if ($result >= 0)
+ {
+ //$linkedobject->fetchObjectLinked() Get all linked object to the $linkedobject (commonly order) into $linkedobject->linkedObjects
+
+ $pdf->SetFont('', '', $default_font_size - 2);
+ $text = $linkedobject->ref;
+ if ($linkedobject->ref_client) $text .= ' ('.$linkedobject->ref_client.')';
+ $Yoff = $Yoff + 8;
+ $pdf->SetXY($this->page_largeur - $this->marge_droite - $w, $Yoff);
+ $pdf->MultiCell($w, 2, $outputlangs->transnoentities("RefOrder")." : ".$outputlangs->transnoentities($text), 0, 'R');
+ $Yoff = $Yoff + 3;
+ $pdf->SetXY($this->page_largeur - $this->marge_droite - $w, $Yoff);
+ $pdf->MultiCell($w, 2, $outputlangs->transnoentities("OrderDate")." : ".dol_print_date($linkedobject->date, "day", false, $outputlangs, true), 0, 'R');
+ }
+ }
+
+ if ($showaddress)
+ {
+ // Sender properties
+ $carac_emetteur = '';
+ // Add internal contact of origin element if defined
+ $arrayidcontact = array();
+ $arrayidcontact = $object->getIdContact('external', 'STFROM');
+
+ $usecontact = false;
+ if (count($arrayidcontact) > 0)
+ {
+ /*$object->fetch_user(reset($arrayidcontact));
+ $carac_emetteur .= ($carac_emetteur ? "\n" : '').$outputlangs->transnoentities("Name").": ".$outputlangs->convToOutputCharset($object->user->getFullName($outputlangs))."\n";*/
+ $usecontact = true;
+ $result = $object->fetch_contact($arrayidcontact[0]);
+ }
+
+ if ($usecontact) $thirdparty = $object->contact;
+ else $thirdparty = $this->emetteur;
+
+ if(!empty($thirdparty)) $carac_emetteur_name = pdfBuildThirdpartyName($thirdparty, $outputlangs);
+
+ if($usecontact) $carac_emetteur .= pdf_build_address($outputlangs, $this->emetteur, $object->thirdparty, $object->contact, 1, 'targetwithdetails', $object);
+ else $carac_emetteur .= pdf_build_address($outputlangs, $this->emetteur, $object->thirdparty, '', 0, 'source', $object);
+
+ // Show sender
+ $posy = !empty($conf->global->MAIN_PDF_USE_ISO_LOCATION) ? 40 : 42;
+ $posx = $this->marge_gauche;
+ if (!empty($conf->global->MAIN_INVERT_SENDER_RECIPIENT)) $posx = $this->page_largeur - $this->marge_droite - 80;
+
+ $hautcadre = !empty($conf->global->MAIN_PDF_USE_ISO_LOCATION) ? 38 : 40;
+ $widthrecbox = !empty($conf->global->MAIN_PDF_USE_ISO_LOCATION) ? 92 : 82;
+
+ // Show sender frame
+ $pdf->SetTextColor(0, 0, 0);
+ $pdf->SetFont('', '', $default_font_size - 2);
+ $pdf->SetXY($posx, $posy - 5);
+ $pdf->MultiCell(66, 5, $outputlangs->transnoentities("Sender").":", 0, 'L');
+ $pdf->SetXY($posx, $posy);
+ $pdf->SetFillColor(230, 230, 230);
+ $pdf->MultiCell($widthrecbox, $hautcadre, "", 0, 'R', 1);
+ $pdf->SetTextColor(0, 0, 60);
+ $pdf->SetFillColor(255, 255, 255);
+
+ // Show sender name
+ $pdf->SetXY($posx + 2, $posy + 3);
+ $pdf->SetFont('', 'B', $default_font_size);
+ $pdf->MultiCell($widthrecbox - 2, 4, $outputlangs->convToOutputCharset($carac_emetteur_name), 0, 'L');
+ $posy = $pdf->getY();
+
+ // Show sender information
+ $pdf->SetXY($posx + 2, $posy);
+ $pdf->SetFont('', '', $default_font_size - 1);
+ $pdf->MultiCell($widthrecbox - 2, 4, $carac_emetteur, 0, 'L');
+
+
+ // If SHIPPING contact defined, we use it
+ $usecontact = false;
+ $arrayidcontact = $object->getIdContact('external', 'STDEST');
+ if (count($arrayidcontact) > 0)
+ {
+ $usecontact = true;
+ $result = $object->fetch_contact($arrayidcontact[0]);
+ }
+
+ //Recipient name
+ // On peut utiliser le nom de la societe du contact
+ if ($usecontact/* && !empty($conf->global->MAIN_USE_COMPANY_NAME_OF_CONTACT)*/) {
+ $thirdparty = $object->contact;
+ } else {
+ $thirdparty = $object->thirdparty;
+ }
+
+ if(!empty($thirdparty)) $carac_client_name = pdfBuildThirdpartyName($thirdparty, $outputlangs);
+
+ $carac_client = pdf_build_address($outputlangs, $this->emetteur, $object->thirdparty, (!empty($object->contact) ? $object->contact : null), $usecontact, 'targetwithdetails', $object);
+
+ // Show recipient
+ $widthrecbox = !empty($conf->global->MAIN_PDF_USE_ISO_LOCATION) ? 92 : 100;
+ if ($this->page_largeur < 210) $widthrecbox = 84; // To work with US executive format
+ $posy = !empty($conf->global->MAIN_PDF_USE_ISO_LOCATION) ? 40 : 42;
+ $posx = $this->page_largeur - $this->marge_droite - $widthrecbox;
+ if (!empty($conf->global->MAIN_INVERT_SENDER_RECIPIENT)) $posx = $this->marge_gauche;
+
+ // Show recipient frame
+ $pdf->SetTextColor(0, 0, 0);
+ $pdf->SetFont('', '', $default_font_size - 2);
+ $pdf->SetXY($posx + 2, $posy - 5);
+ $pdf->MultiCell($widthrecbox, 5, $outputlangs->transnoentities("Recipient").":", 0, 'L');
+ $pdf->Rect($posx, $posy, $widthrecbox, $hautcadre);
+
+ // Show recipient name
+ $pdf->SetXY($posx + 2, $posy + 3);
+ $pdf->SetFont('', 'B', $default_font_size);
+ $pdf->MultiCell($widthrecbox, 2, $carac_client_name, 0, 'L');
+
+ $posy = $pdf->getY();
+
+ // Show recipient information
+ $pdf->SetFont('', '', $default_font_size - 1);
+ $pdf->SetXY($posx + 2, $posy);
+ $pdf->MultiCell($widthrecbox, 4, $carac_client, 0, 'L');
+ }
+
+ $pdf->SetTextColor(0, 0, 0);
+ }
+
+ // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
+ /**
+ * Show footer of page. Need this->emetteur object
+ *
+ * @param PDF $pdf PDF
+ * @param Object $object Object to show
+ * @param Translate $outputlangs Object lang for output
+ * @param int $hidefreetext 1=Hide free text
+ * @return int Return height of bottom margin including footer text
+ */
+ protected function _pagefoot(&$pdf, $object, $outputlangs, $hidefreetext = 0)
+ {
+ global $conf;
+ $showdetails = $conf->global->MAIN_GENERATE_DOCUMENTS_SHOW_FOOT_DETAILS;
+ return pdf_pagefoot($pdf, $outputlangs, 'SHIPPING_FREE_TEXT', $this->emetteur, $this->marge_basse, $this->marge_gauche, $this->page_hauteur, $object, $showdetails, $hidefreetext);
+ }
+}
diff --git a/htdocs/core/modules/stocktransfer/mod_stocktransfer_advanced.php b/htdocs/core/modules/stocktransfer/mod_stocktransfer_advanced.php
new file mode 100644
index 00000000000..60207efb650
--- /dev/null
+++ b/htdocs/core/modules/stocktransfer/mod_stocktransfer_advanced.php
@@ -0,0 +1,150 @@
+
+ * Copyright (C) 2004-2007 Laurent Destailleur
+ * Copyright (C) 2005-2009 Regis Houssin
+ * Copyright (C) 2008 Raphael Bertrand (Resultic)
+ * Copyright (C) 2019 Frédéric France
+ *
+ * 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 .
+ * or see https://www.gnu.org/
+ */
+
+/**
+ * \file htdocs/core/modules/stocktransfer/mod_stocktransfer_advanced.php
+ * \ingroup stocktransfer
+ * \brief File containing class for advanced numbering model of StockTransfer
+ */
+
+require_once DOL_DOCUMENT_ROOT . '/core/modules/stocktransfer/modules_stocktransfer.php';
+
+
+/**
+ * Class to manage customer Bom numbering rules advanced
+ */
+class mod_stocktransfer_advanced extends ModeleNumRefStockTransfer
+{
+ /**
+ * Dolibarr version of the loaded document
+ * @var string
+ */
+ public $version = 'dolibarr'; // 'development', 'experimental', 'dolibarr'
+
+ /**
+ * @var string Error message
+ */
+ public $error = '';
+
+ /**
+ * @var string name
+ */
+ public $name = 'got2be';
+
+
+ /**
+ * Returns the description of the numbering model
+ *
+ * @return string Texte descripif
+ */
+ public function info()
+ {
+ global $conf, $langs, $db;
+
+ $langs->load("bills");
+
+ $form = new Form($db);
+
+ $texte = $langs->trans('GenericNumRefModelDesc')."
\n";
+ $texte .= '';
+
+ return $texte;
+ }
+
+ /**
+ * Return an example of numbering
+ *
+ * @return string Example
+ */
+ public function getExample()
+ {
+ global $conf, $db, $langs, $mysoc;
+
+ $object = new StockTransfer($db);
+ $object->initAsSpecimen();
+
+ /*$old_code_client = $mysoc->code_client;
+ $old_code_type = $mysoc->typent_code;
+ $mysoc->code_client = 'CCCCCCCCCC';
+ $mysoc->typent_code = 'TTTTTTTTTT';*/
+
+ $numExample = $this->getNextValue($object);
+
+ /*$mysoc->code_client = $old_code_client;
+ $mysoc->typent_code = $old_code_type;*/
+
+ if (!$numExample)
+ {
+ $numExample = $langs->trans('NotConfigured');
+ }
+ return $numExample;
+ }
+
+ /**
+ * Return next free value
+ *
+ * @param Object $object Object we need next value for
+ * @return string Value if KO, <0 if KO
+ */
+ public function getNextValue($object)
+ {
+ global $db, $conf;
+
+ require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
+
+ // We get cursor rule
+ $mask = $conf->global->STOCKTRANSFER_STOCKTRANSFER_ADVANCED_MASK;
+
+ if (!$mask)
+ {
+ $this->error = 'NotConfigured';
+ return 0;
+ }
+
+ $date = $object->date;
+
+ $numFinal = get_next_value($db, $mask, 'stocktransfer_stocktransfer', 'ref', '', null, $date);
+
+ return $numFinal;
+ }
+}
diff --git a/htdocs/core/modules/stocktransfer/mod_stocktransfer_standard.php b/htdocs/core/modules/stocktransfer/mod_stocktransfer_standard.php
new file mode 100644
index 00000000000..d4a06024136
--- /dev/null
+++ b/htdocs/core/modules/stocktransfer/mod_stocktransfer_standard.php
@@ -0,0 +1,160 @@
+
+ * Copyright (C) 2005-2009 Regis Houssin
+ *
+ * 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 .
+ * or see https://www.gnu.org/
+ */
+
+/**
+ * \file htdocs/core/modules/stocktransfer/mod_stocktransfer_standard.php
+ * \ingroup stocktransfer
+ * \brief File of class to manage StockTransfer numbering rules standard
+ */
+require_once DOL_DOCUMENT_ROOT . '/core/modules/stocktransfer/modules_stocktransfer.php';
+
+
+/**
+ * Class to manage customer order numbering rules standard
+ */
+class mod_stocktransfer_standard extends ModeleNumRefStockTransfer
+{
+ /**
+ * Dolibarr version of the loaded document
+ * @var string
+ */
+ public $version = 'dolibarr'; // 'development', 'experimental', 'dolibarr'
+
+ public $prefix = 'ST';
+
+ /**
+ * @var string Error code (or message)
+ */
+ public $error = '';
+
+ /**
+ * @var string name
+ */
+ public $name = 'standard';
+
+
+ /**
+ * Return description of numbering module
+ *
+ * @return string Text with description
+ */
+ public function info()
+ {
+ global $langs;
+ return $langs->trans("SimpleNumRefModelDesc", $this->prefix);
+ }
+
+
+ /**
+ * Return an example of numbering
+ *
+ * @return string Example
+ */
+ public function getExample()
+ {
+ return $this->prefix."0501-0001";
+ }
+
+
+ /**
+ * Checks if the numbers already in the database do not
+ * cause conflicts that would prevent this numbering working.
+ *
+ * @param Object $object Object we need next value for
+ * @return boolean false if conflict, true if ok
+ */
+ public function canBeActivated($object)
+ {
+ global $conf, $langs, $db;
+
+ $coyymm = ''; $max = '';
+
+ $posindice = strlen($this->prefix) + 6;
+ $sql = "SELECT MAX(CAST(SUBSTRING(ref FROM ".$posindice.") AS SIGNED)) as max";
+ $sql .= " FROM ".MAIN_DB_PREFIX."stocktransfer_stocktransfer";
+ $sql .= " WHERE ref LIKE '".$db->escape($this->prefix)."____-%'";
+ if ($object->ismultientitymanaged == 1) {
+ $sql .= " AND entity = ".$conf->entity;
+ }
+ elseif ($object->ismultientitymanaged == 2) {
+ // TODO
+ }
+
+ $resql = $db->query($sql);
+ if ($resql)
+ {
+ $row = $db->fetch_row($resql);
+ if ($row) { $coyymm = substr($row[0], 0, 6); $max = $row[0]; }
+ }
+ if ($coyymm && !preg_match('/'.$this->prefix.'[0-9][0-9][0-9][0-9]/i', $coyymm))
+ {
+ $langs->load("errors");
+ $this->error = $langs->trans('ErrorNumRefModel', $max);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Return next free value
+ *
+ * @param Object $object Object we need next value for
+ * @return string Value if KO, <0 if KO
+ */
+ public function getNextValue($object)
+ {
+ global $db, $conf;
+
+ // first we get the max value
+ $posindice = strlen($this->prefix) + 6;
+ $sql = "SELECT MAX(CAST(SUBSTRING(ref FROM ".$posindice.") AS SIGNED)) as max";
+ $sql .= " FROM ".MAIN_DB_PREFIX."stocktransfer_stocktransfer";
+ $sql .= " WHERE ref LIKE '".$db->escape($this->prefix)."____-%'";
+ if ($object->ismultientitymanaged == 1) {
+ $sql .= " AND entity = ".$conf->entity;
+ }
+ elseif ($object->ismultientitymanaged == 2) {
+ // TODO
+ }
+
+ $resql = $db->query($sql);
+ if ($resql)
+ {
+ $obj = $db->fetch_object($resql);
+ if ($obj) $max = intval($obj->max);
+ else $max = 0;
+ }
+ else
+ {
+ dol_syslog("mod_stocktransfer_standard::getNextValue", LOG_DEBUG);
+ return -1;
+ }
+
+ //$date=time();
+ $date = $object->date_creation;
+ $yymm = strftime("%y%m", $date);
+
+ if ($max >= (pow(10, 4) - 1)) $num = $max + 1; // If counter > 9999, we do not format on 4 chars, we take number as it is
+ else $num = sprintf("%04s", $max + 1);
+
+ dol_syslog("mod_stocktransfer_standard::getNextValue return ".$this->prefix.$yymm."-".$num);
+ return $this->prefix.$yymm."-".$num;
+ }
+}
diff --git a/htdocs/core/modules/stocktransfer/modules_stocktransfer.php b/htdocs/core/modules/stocktransfer/modules_stocktransfer.php
new file mode 100644
index 00000000000..0394f8ac1f0
--- /dev/null
+++ b/htdocs/core/modules/stocktransfer/modules_stocktransfer.php
@@ -0,0 +1,150 @@
+
+ * Copyright (C) 2004-2011 Laurent Destailleur
+ * Copyright (C) 2004 Eric Seigne
+ * Copyright (C) 2005-2012 Regis Houssin
+ * Copyright (C) 2006 Andre Cianfarani
+ * Copyright (C) 2012 Juanjo Menent
+ * Copyright (C) 2014 Marcos García
+ *
+ * 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 .
+ * or see https://www.gnu.org/
+ */
+
+/**
+ * \file htdocs/core/modules/stocktransfer/modules_stocktransfer.php
+ * \ingroup stocktransfer
+ * \brief File that contains parent class for stocktransfers document models and parent class for stocktransfers numbering models
+ */
+
+require_once DOL_DOCUMENT_ROOT.'/core/class/commondocgenerator.class.php';
+require_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php'; // required for use by classes that inherit
+
+
+/**
+ * Parent class for documents models
+ */
+abstract class ModelePDFStockTransfer extends CommonDocGenerator
+{
+
+ // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
+ /**
+ * Return list of active generation modules
+ *
+ * @param DoliDB $db Database handler
+ * @param integer $maxfilenamelength Max length of value to show
+ * @return array List of templates
+ */
+ public static function liste_modeles($db, $maxfilenamelength = 0)
+ {
+ // phpcs:enable
+ global $conf;
+
+ $type = 'stocktransfer';
+ $list = array();
+
+ include_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
+ $list = getListOfModels($db, $type, $maxfilenamelength);
+
+ return $list;
+ }
+}
+
+
+
+/**
+ * Parent class to manage numbering of StockTransfer
+ */
+abstract class ModeleNumRefStockTransfer
+{
+ /**
+ * @var string Error code (or message)
+ */
+ public $error = '';
+
+ /**
+ * Return if a module can be used or not
+ *
+ * @return boolean true if module can be used
+ */
+ public function isEnabled()
+ {
+ return true;
+ }
+
+ /**
+ * Returns the default description of the numbering template
+ *
+ * @return string Texte descripif
+ */
+ public function info()
+ {
+ global $langs;
+ $langs->load("stocktransfer@stocktransfer");
+ return $langs->trans("NoDescription");
+ }
+
+ /**
+ * Returns an example of numbering
+ *
+ * @return string Example
+ */
+ public function getExample()
+ {
+ global $langs;
+ $langs->load("stocktransfer@stocktransfer");
+ return $langs->trans("NoExample");
+ }
+
+ /**
+ * Checks if the numbers already in the database do not
+ * cause conflicts that would prevent this numbering working.
+ *
+ * @param Object $object Object we need next value for
+ * @return boolean false if conflict, true if ok
+ */
+ public function canBeActivated($object)
+ {
+ return true;
+ }
+
+ /**
+ * Returns next assigned value
+ *
+ * @param Object $object Object we need next value for
+ * @return string Valeur
+ */
+ public function getNextValue($object)
+ {
+ global $langs;
+ return $langs->trans("NotAvailable");
+ }
+
+ /**
+ * Returns version of numbering module
+ *
+ * @return string Valeur
+ */
+ public function getVersion()
+ {
+ global $langs;
+ $langs->load("admin");
+
+ if ($this->version == 'development') return $langs->trans("VersionDevelopment");
+ if ($this->version == 'experimental') return $langs->trans("VersionExperimental");
+ if ($this->version == 'dolibarr') return DOL_VERSION;
+ if ($this->version) return $this->version;
+ return $langs->trans("NotAvailable");
+ }
+}
diff --git a/htdocs/install/mysql/migration/14.0.0-15.0.0.sql b/htdocs/install/mysql/migration/14.0.0-15.0.0.sql
index 8eaf1767b8a..b9243f11823 100644
--- a/htdocs/install/mysql/migration/14.0.0-15.0.0.sql
+++ b/htdocs/install/mysql/migration/14.0.0-15.0.0.sql
@@ -39,3 +39,75 @@ ALTER TABLE llx_product_fournisseur_price MODIFY COLUMN ref_fourn varchar(128);
ALTER TABLE llx_product_customer_price MODIFY COLUMN ref_customer varchar(128);
+CREATE TABLE llx_stocktransfer_stocktransfer(
+ -- BEGIN MODULEBUILDER FIELDS
+ rowid integer AUTO_INCREMENT PRIMARY KEY NOT NULL,
+ entity integer DEFAULT 1 NOT NULL,
+ ref varchar(128) DEFAULT '(PROV)' NOT NULL,
+ label varchar(255),
+ fk_soc integer,
+ fk_project integer,
+ fk_warehouse_source integer,
+ fk_warehouse_destination integer,
+ description text,
+ note_public text,
+ note_private text,
+ tms timestamp,
+ date_creation datetime NOT NULL,
+ date_prevue_depart date DEFAULT NULL,
+ date_reelle_depart date DEFAULT NULL,
+ date_prevue_arrivee date DEFAULT NULL,
+ date_reelle_arrivee date DEFAULT NULL,
+ lead_time_for_warning integer DEFAULT NULL,
+ fk_user_creat integer NOT NULL,
+ fk_user_modif integer,
+ import_key varchar(14),
+ model_pdf varchar(255),
+ status smallint NOT NULL
+ -- END MODULEBUILDER FIELDS
+) ENGINE=innodb;
+
+ALTER TABLE llx_stocktransfer_stocktransfer ADD INDEX idx_stocktransfer_stocktransfer_rowid (rowid);
+ALTER TABLE llx_stocktransfer_stocktransfer ADD INDEX idx_stocktransfer_stocktransfer_ref (ref);
+ALTER TABLE llx_stocktransfer_stocktransfer ADD INDEX idx_stocktransfer_stocktransfer_fk_soc (fk_soc);
+ALTER TABLE llx_stocktransfer_stocktransfer ADD INDEX idx_stocktransfer_stocktransfer_fk_project (fk_project);
+ALTER TABLE llx_stocktransfer_stocktransfer ADD CONSTRAINT llx_stocktransfer_stocktransfer_fk_user_creat FOREIGN KEY (fk_user_creat) REFERENCES llx_user(rowid);
+ALTER TABLE llx_stocktransfer_stocktransfer ADD INDEX idx_stocktransfer_stocktransfer_status (status);
+
+CREATE TABLE llx_stocktransfer_stocktransferline(
+ -- BEGIN MODULEBUILDER FIELDS
+ rowid integer AUTO_INCREMENT PRIMARY KEY NOT NULL,
+ amount double DEFAULT NULL,
+ qty real,
+ fk_warehouse_source integer NOT NULL,
+ fk_warehouse_destination integer NOT NULL,
+ fk_stocktransfer integer NOT NULL,
+ fk_product integer NOT NULL,
+ batch varchar(128) DEFAULT NULL, -- Lot or serial number
+ pmp double,
+ rang integer DEFAULT 0,
+ fk_parent_line integer NULL
+ -- END MODULEBUILDER FIELDS
+) ENGINE=innodb;
+
+ALTER TABLE llx_stocktransfer_stocktransferline ADD INDEX idx_stocktransfer_stocktransferline_rowid (rowid);
+
+create table llx_stocktransfer_stocktransfer_extrafields
+(
+ rowid integer AUTO_INCREMENT PRIMARY KEY,
+ tms timestamp,
+ fk_object integer NOT NULL,
+ import_key varchar(14) -- import key
+) ENGINE=innodb;
+
+ALTER TABLE llx_stocktransfer_stocktransfer_extrafields ADD INDEX idx_fk_object(fk_object);
+
+create table llx_stocktransfer_stocktransferline_extrafields
+(
+ rowid integer AUTO_INCREMENT PRIMARY KEY,
+ tms timestamp,
+ fk_object integer NOT NULL,
+ import_key varchar(14) -- import key
+) ENGINE=innodb;
+
+ALTER TABLE llx_stocktransfer_stocktransferline_extrafields ADD INDEX idx_fk_object(fk_object);
diff --git a/htdocs/install/mysql/tables/llx_stocktransfer_stocktransfer.key.sql b/htdocs/install/mysql/tables/llx_stocktransfer_stocktransfer.key.sql
new file mode 100644
index 00000000000..74108a5face
--- /dev/null
+++ b/htdocs/install/mysql/tables/llx_stocktransfer_stocktransfer.key.sql
@@ -0,0 +1,29 @@
+-- Copyright (C) ---Put here your own copyright and developer email---
+-- Copyright (C) 2021 Gauthier VERDOL
+-- 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/.
+
+
+-- BEGIN MODULEBUILDER INDEXES
+ALTER TABLE llx_stocktransfer_stocktransfer ADD INDEX idx_stocktransfer_stocktransfer_rowid (rowid);
+ALTER TABLE llx_stocktransfer_stocktransfer ADD INDEX idx_stocktransfer_stocktransfer_ref (ref);
+ALTER TABLE llx_stocktransfer_stocktransfer ADD INDEX idx_stocktransfer_stocktransfer_fk_soc (fk_soc);
+ALTER TABLE llx_stocktransfer_stocktransfer ADD INDEX idx_stocktransfer_stocktransfer_fk_project (fk_project);
+ALTER TABLE llx_stocktransfer_stocktransfer ADD CONSTRAINT llx_stocktransfer_stocktransfer_fk_user_creat FOREIGN KEY (fk_user_creat) REFERENCES llx_user(rowid);
+ALTER TABLE llx_stocktransfer_stocktransfer ADD INDEX idx_stocktransfer_stocktransfer_status (status);
+-- END MODULEBUILDER INDEXES
+
+--ALTER TABLE llx_stocktransfer_stocktransfer ADD UNIQUE INDEX uk_stocktransfer_stocktransfer_fieldxy(fieldx, fieldy);
+
+--ALTER TABLE llx_stocktransfer_stocktransfer ADD CONSTRAINT llx_stocktransfer_stocktransfer_fk_field FOREIGN KEY (fk_field) REFERENCES llx_stocktransfer_myotherobject(rowid);
+
diff --git a/htdocs/install/mysql/tables/llx_stocktransfer_stocktransfer.sql b/htdocs/install/mysql/tables/llx_stocktransfer_stocktransfer.sql
new file mode 100644
index 00000000000..31bd144ef5e
--- /dev/null
+++ b/htdocs/install/mysql/tables/llx_stocktransfer_stocktransfer.sql
@@ -0,0 +1,43 @@
+-- Copyright (C) ---Put here your own copyright and developer email---
+-- Copyright (C) 2021 Gauthier VERDOL
+-- 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/.
+
+
+CREATE TABLE llx_stocktransfer_stocktransfer(
+ -- BEGIN MODULEBUILDER FIELDS
+ rowid integer AUTO_INCREMENT PRIMARY KEY NOT NULL,
+ entity integer DEFAULT 1 NOT NULL,
+ ref varchar(128) DEFAULT '(PROV)' NOT NULL,
+ label varchar(255),
+ fk_soc integer,
+ fk_project integer,
+ fk_warehouse_source integer,
+ fk_warehouse_destination integer,
+ description text,
+ note_public text,
+ note_private text,
+ tms timestamp,
+ date_creation datetime NOT NULL,
+ date_prevue_depart date DEFAULT NULL,
+ date_reelle_depart date DEFAULT NULL,
+ date_prevue_arrivee date DEFAULT NULL,
+ date_reelle_arrivee date DEFAULT NULL,
+ lead_time_for_warning integer DEFAULT NULL,
+ fk_user_creat integer NOT NULL,
+ fk_user_modif integer,
+ import_key varchar(14),
+ model_pdf varchar(255),
+ status smallint NOT NULL
+ -- END MODULEBUILDER FIELDS
+) ENGINE=innodb;
diff --git a/htdocs/install/mysql/tables/llx_stocktransfer_stocktransfer_extrafields.key.sql b/htdocs/install/mysql/tables/llx_stocktransfer_stocktransfer_extrafields.key.sql
new file mode 100644
index 00000000000..36850341a6c
--- /dev/null
+++ b/htdocs/install/mysql/tables/llx_stocktransfer_stocktransfer_extrafields.key.sql
@@ -0,0 +1,19 @@
+-- Copyright (C) ---Put here your own copyright and developer email---
+-- Copyright (C) 2021 Gauthier VERDOL
+-- 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/.
+
+
+-- BEGIN MODULEBUILDER INDEXES
+ALTER TABLE llx_stocktransfer_stocktransfer_extrafields ADD INDEX idx_fk_object(fk_object);
+-- END MODULEBUILDER INDEXES
diff --git a/htdocs/install/mysql/tables/llx_stocktransfer_stocktransfer_extrafields.sql b/htdocs/install/mysql/tables/llx_stocktransfer_stocktransfer_extrafields.sql
new file mode 100644
index 00000000000..33a91fc1070
--- /dev/null
+++ b/htdocs/install/mysql/tables/llx_stocktransfer_stocktransfer_extrafields.sql
@@ -0,0 +1,23 @@
+-- Copyright (C) ---Put here your own copyright and developer email---
+-- Copyright (C) 2021 Gauthier VERDOL
+-- 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/.
+
+create table llx_stocktransfer_stocktransfer_extrafields
+(
+ rowid integer AUTO_INCREMENT PRIMARY KEY,
+ tms timestamp,
+ fk_object integer NOT NULL,
+ import_key varchar(14) -- import key
+) ENGINE=innodb;
+
diff --git a/htdocs/install/mysql/tables/llx_stocktransfer_stocktransferline.key.sql b/htdocs/install/mysql/tables/llx_stocktransfer_stocktransferline.key.sql
new file mode 100644
index 00000000000..32dfbd8bcd2
--- /dev/null
+++ b/htdocs/install/mysql/tables/llx_stocktransfer_stocktransferline.key.sql
@@ -0,0 +1,24 @@
+-- Copyright (C) ---Put here your own copyright and developer email---
+-- Copyright (C) 2021 Gauthier VERDOL
+-- 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/.
+
+
+-- BEGIN MODULEBUILDER INDEXES
+ALTER TABLE llx_stocktransfer_stocktransferline ADD INDEX idx_stocktransfer_stocktransferline_rowid (rowid);
+-- END MODULEBUILDER INDEXES
+
+--ALTER TABLE llx_stocktransfer_stocktransferline ADD UNIQUE INDEX uk_stocktransfer_stocktransferline_fieldxy(fieldx, fieldy);
+
+--ALTER TABLE llx_stocktransfer_stocktransferline ADD CONSTRAINT llx_stocktransfer_stocktransferline_fk_field FOREIGN KEY (fk_field) REFERENCES llx_stocktransfer_myotherobject(rowid);
+
diff --git a/htdocs/install/mysql/tables/llx_stocktransfer_stocktransferline.sql b/htdocs/install/mysql/tables/llx_stocktransfer_stocktransferline.sql
new file mode 100644
index 00000000000..10c22fed32c
--- /dev/null
+++ b/htdocs/install/mysql/tables/llx_stocktransfer_stocktransferline.sql
@@ -0,0 +1,31 @@
+-- Copyright (C) ---Put here your own copyright and developer email---
+-- Copyright (C) 2021 Gauthier VERDOL
+-- 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/.
+
+
+CREATE TABLE llx_stocktransfer_stocktransferline(
+ -- BEGIN MODULEBUILDER FIELDS
+ rowid integer AUTO_INCREMENT PRIMARY KEY NOT NULL,
+ amount double DEFAULT NULL,
+ qty real,
+ fk_warehouse_source integer NOT NULL,
+ fk_warehouse_destination integer NOT NULL,
+ fk_stocktransfer integer NOT NULL,
+ fk_product integer NOT NULL,
+ batch varchar(128) DEFAULT NULL, -- Lot or serial number
+ pmp double,
+ rang integer DEFAULT 0,
+ fk_parent_line integer NULL
+ -- END MODULEBUILDER FIELDS
+) ENGINE=innodb;
diff --git a/htdocs/install/mysql/tables/llx_stocktransfer_stocktransferline_extrafields.key.sql b/htdocs/install/mysql/tables/llx_stocktransfer_stocktransferline_extrafields.key.sql
new file mode 100644
index 00000000000..a35079cbf0c
--- /dev/null
+++ b/htdocs/install/mysql/tables/llx_stocktransfer_stocktransferline_extrafields.key.sql
@@ -0,0 +1,19 @@
+-- Copyright (C) ---Put here your own copyright and developer email---
+-- Copyright (C) 2021 Gauthier VERDOL
+-- 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/.
+
+
+-- BEGIN MODULEBUILDER INDEXES
+ALTER TABLE llx_stocktransfer_stocktransferline_extrafields ADD INDEX idx_fk_object(fk_object);
+-- END MODULEBUILDER INDEXES
diff --git a/htdocs/install/mysql/tables/llx_stocktransfer_stocktransferline_extrafields.sql b/htdocs/install/mysql/tables/llx_stocktransfer_stocktransferline_extrafields.sql
new file mode 100644
index 00000000000..5b5080441bd
--- /dev/null
+++ b/htdocs/install/mysql/tables/llx_stocktransfer_stocktransferline_extrafields.sql
@@ -0,0 +1,23 @@
+-- Copyright (C) ---Put here your own copyright and developer email---
+-- Copyright (C) 2021 Gauthier VERDOL
+-- 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/.
+
+create table llx_stocktransfer_stocktransferline_extrafields
+(
+ rowid integer AUTO_INCREMENT PRIMARY KEY,
+ tms timestamp,
+ fk_object integer NOT NULL,
+ import_key varchar(14) -- import key
+) ENGINE=innodb;
+
diff --git a/htdocs/langs/en_US/stocks.lang b/htdocs/langs/en_US/stocks.lang
index efa5257498c..4143eaacdc9 100644
--- a/htdocs/langs/en_US/stocks.lang
+++ b/htdocs/langs/en_US/stocks.lang
@@ -255,4 +255,45 @@ MakeMovementsAndClose=Generate movements and close
AutofillWithExpected=Fill real quantity with expected quantity
ShowAllBatchByDefault=By default, show batch details on product "stock" tab
CollapseBatchDetailHelp=You can set batch detail default display in stocks module configuration
-FieldCannotBeNegative=Field "%s" cannot be negative
\ No newline at end of file
+FieldCannotBeNegative=Field "%s" cannot be negative
+
+ModuleStockTransferName=Stocks Transfer
+ModuleStockTransferDesc=Creation of stocks transfer objects, with generation of transfer sheet
+StockTransferNew=New stocks transfer
+StockTransferList=Stocks transfers list
+ConfirmValidateStockTransfer=Are you sure you want to validate this stocks transfer with reference %s ?
+ConfirmDestock=Decrease of stocks with transfer %s
+ConfirmDestockCancel=Cancel decrease of stocks with transfer %s
+DestockAllProduct=Decrease of stocks
+DestockAllProductCancel=Cancel decrease of stocks
+ConfirmAddStock=Increase stocks with transfer %s
+ConfirmAddStockCancel=Cancel increase of stocks with transfer %s
+AddStockAllProduct=Increase of stocks
+AddStockAllProductCancel=Cancel increase of stocks
+DatePrevueDepart=Intended date of departure
+DateReelleDepart=Real date of departure
+DatePrevueArrivee=Intended date of arrival
+DateReelleArrivee=Real date of arrival
+HelpWarehouseStockTransferSource=If this warehouse is set, only itself and its children will be available as source warehouse
+HelpWarehouseStockTransferDestination=If this warehouse is set, only itself and its children will be available as destination warehouse
+LeadTimeForWarning=Lead time before alert (in days)
+TypeContact_stocktransfer_internal_STFROM=Sender of stocks transfer
+TypeContact_stocktransfer_internal_STDEST=Recipient of stocks transfer
+TypeContact_stocktransfer_internal_STRESP=Responsible of stocks transfer
+StockTransferSheet=Stocks transfer sheet
+StockTransferDecrementation=Decrease source warehouses
+StockTransferIncrementation=Increase destination warehouses
+StockTransferDecrementationCancel=Cancel decrease of source warehouses
+StockTransferIncrementationCancel=Cancel increase of destination warehouses
+StockStransferDecremented=Source warehouses decreased
+StockStransferDecrementedCancel=Decrease of source warehouses canceled
+StockStransferIncremented=Closed - Stocks transfered
+StockStransferIncrementedShort=Stocks transfered
+StockStransferIncrementedShortCancel=Increase of destination warehouses canceled
+StockTransferNoBatchForProduct=Product %s doesn't use batch, clear batch on line and retry
+StockTransferSetup = Stocks Transfer module configuration
+Settings=Settings
+StockTransferSetupPage = Configuration page for stocks transfer module
+StockTransferRightRead=Read stocks transfers
+StockTransferRightCreateUpdate=Create/Update stocks transfers
+StockTransferRightDelete=Delete stocks transfers
diff --git a/htdocs/langs/fr_FR/stocks.lang b/htdocs/langs/fr_FR/stocks.lang
index f0fe7235a77..d832b160d02 100644
--- a/htdocs/langs/fr_FR/stocks.lang
+++ b/htdocs/langs/fr_FR/stocks.lang
@@ -256,3 +256,44 @@ AutofillWithExpected=Remplir la quantité réelle avec la quantité prévue
ShowAllBatchByDefault=Par défaut, afficher les détails des lots sur l'onglet "stock" du produit
CollapseBatchDetailHelp=You can set batch detail default display in stocks module configuration
FieldCannotBeNegative=Le champ "%s" ne peut pas être négatif
+
+ModuleStockTransferName = Transferts de stocks
+ModuleStockTransferDesc = Création d'objets transfert de stocks, avec génération de bons de transfert
+StockTransferNew=Nouveau transfert de stocks
+StockTransferList=Liste des transferts de stocks
+ConfirmValidateStockTransfer=Êtes-vous sûr de vouloir valider ce transfert de stocks sous la référence %s ?
+ConfirmDestock=Décrémentation des stocks via transfert %s
+ConfirmDestockCancel=Annulation décrémentation des stocks via transfert %s
+DestockAllProduct=Décrémentation des stocks
+DestockAllProductCancel=Annulation décrémentation des stocks
+ConfirmAddStock=Incrémentation des stocks via transfert %s
+ConfirmAddStockCancel=Annulation incrémentation des stocks via transfert %s
+AddStockAllProduct=Incrémentation des stocks
+AddStockAllProductCancel=Annulation incrémentation des stocks
+DatePrevueDepart=Date prévue de départ
+DateReelleDepart=Date réelle de départ
+DatePrevueArrivee=Date prévue d'arrivée
+DateReelleArrivee=Date réelle d'arrivée
+HelpWarehouseStockTransferSource=Si renseigné, seul cet entrepôt source et ses enfants seront sélectionnables pour l'ajout des lignes
+HelpWarehouseStockTransferDestination=Si renseigné, seul cet entrepôt de destination et ses enfants seront sélectionnables pour l'ajout des lignes
+LeadTimeForWarning=Délai pour alerte (en jours)
+TypeContact_stocktransfer_internal_STFROM=Contact expéditeur transfert de stocks
+TypeContact_stocktransfer_internal_STDEST=Contact destinataire transfert de stocks
+TypeContact_stocktransfer_internal_STRESP=Responsable du transfert de stocks
+StockTransferSheet=Bon de transfert
+StockTransferDecrementation=Décrémenter les entrepôts sources
+StockTransferIncrementation=Incrémenter les entrepôts de destination
+StockTransferDecrementationCancel=Annuler décrémentation des entrepôts sources
+StockTransferIncrementationCancel=Annuler incrémentation des entrepôts de destination
+StockStransferDecremented=Entrepôts sources décrémentés
+StockStransferDecrementedCancel=Décrémentation entrepôts source annulée
+StockStransferIncremented=Clôturé - Stocks transférés
+StockStransferIncrementedShort=Stocks transférés
+StockStransferIncrementedShortCancel=Incrémentation entrepôts de destination annulée
+StockTransferNoBatchForProduct=Le produit %s ne gère pas les numéros de lot, retirez le lot sur la ligne avant d'exécuter à nouveau cette action
+StockTransferSetup = Configuration du module transferts de stocks
+Settings=Réglages
+StockTransferSetupPage = Page de configuration du module transferts de stocks
+StockTransferRightRead=Lire les transferts de stocks
+StockTransferRightCreateUpdate=Créer/Mettre à jour les transferts de stocks
+StockTransferRightDelete=Supprimer les transferts de stocks
diff --git a/htdocs/product/stock/stocktransfer/class/stocktransfer.class.php b/htdocs/product/stock/stocktransfer/class/stocktransfer.class.php
new file mode 100644
index 00000000000..7faf8d7c530
--- /dev/null
+++ b/htdocs/product/stock/stocktransfer/class/stocktransfer.class.php
@@ -0,0 +1,1107 @@
+
+ * Copyright (C) 2021 Gauthier VERDOL
+ * Copyright (C) ---Put here your own copyright and developer email---
+ *
+ * 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 .
+ */
+
+/**
+ * \file class/stocktransfer.class.php
+ * \ingroup stocktransfer
+ * \brief This file is a CRUD class file for StockTransfer (Create/Read/Update/Delete)
+ */
+
+// Put here all includes required by your class file
+require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
+//require_once DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php';
+//require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
+
+/**
+ * Class for StockTransfer
+ */
+class StockTransfer extends CommonObject
+{
+ /**
+ * @var string ID to identify managed object.
+ */
+ public $element = 'stocktransfer';
+
+ /**
+ * @var string Name of table without prefix where object is stored. This is also the key used for extrafields management.
+ */
+ public $table_element = 'stocktransfer_stocktransfer';
+
+ /**
+ * @var int Does this object support multicompany module ?
+ * 0=No test on entity, 1=Test with field entity, 'field@table'=Test with link by field@table
+ */
+ public $ismultientitymanaged = 0;
+
+ /**
+ * @var int Does object support extrafields ? 0=No, 1=Yes
+ */
+ public $isextrafieldmanaged = 1;
+
+ /**
+ * @var string String with name of icon for stocktransfer. Must be the part after the 'object_' into object_stocktransfer.png
+ */
+ public $picto = 'stock';
+
+
+ const STATUS_DRAFT = 0;
+ const STATUS_VALIDATED = 1;
+ const STATUS_TRANSFERED = 2;
+ const STATUS_CLOSED = 3;
+
+
+ /**
+ * 'type' if the field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter]]', 'varchar(x)', 'double(24,8)', 'real', 'price', 'text', 'html', 'date', 'datetime', 'timestamp', 'duration', 'mail', 'phone', 'url', 'password')
+ * Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)"
+ * 'label' the translation key.
+ * 'enabled' is a condition when the field must be managed (Example: 1 or '$conf->global->MY_SETUP_PARAM)
+ * 'position' is the sort order of field.
+ * 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
+ * 'visible' says if field is visible in list (Examples: 0=Not visible, 1=Visible on list and create/update/view forms, 2=Visible on list only, 3=Visible on create/update/view form only (not list), 4=Visible on list and update/view form only (not create). 5=Visible on list and view only (not create/not update). Using a negative value means field is not shown by default on list but can be selected for viewing)
+ * 'noteditable' says if field is not editable (1 or 0)
+ * 'default' is a default value for creation (can still be overwrote by the Setup of Default Values if field is editable in creation form). Note: If default is set to '(PROV)' and field is 'ref', the default value will be set to '(PROVid)' where id is rowid when a new record is created.
+ * 'index' if we want an index in database.
+ * 'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommanded to name the field fk_...).
+ * 'searchall' is 1 if we want to search in this field when making a search from the quick search button.
+ * 'isameasure' must be set to 1 if you want to have a total on list for this field. Field type must be summable like integer or double(24,8).
+ * 'css' is the CSS style to use on field. For example: 'maxwidth200'
+ * 'help' is a string visible as a tooltip on field
+ * 'showoncombobox' if value of the field must be visible into the label of the combobox that list record
+ * 'disabled' is 1 if we want to have the field locked by a 'disabled' attribute. In most cases, this is never set into the definition of $fields into class, but is set dynamically by some part of code.
+ * 'arraykeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel")
+ * 'autofocusoncreate' to have field having the focus on a create form. Only 1 field should have this property set to 1.
+ * 'comment' is not used. You can store here any text of your choice. It is not used by application.
+ *
+ * Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
+ */
+
+ // BEGIN MODULEBUILDER PROPERTIES
+ /**
+ * @var array Array with all fields and their property. Do not use it as a static var. It may be modified by constructor.
+ */
+ public $fields=array(
+ 'rowid' => array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>'1', 'position'=>1, 'notnull'=>1, 'visible'=>0, 'noteditable'=>'1', 'index'=>1, 'comment'=>"Id"),
+ 'entity' => array('type'=>'integer', 'label'=>'Entity', 'enabled'=>'1', 'position'=>1, 'default'=>1, 'notnull'=>1, 'visible'=>0, 'noteditable'=>'1', 'index'=>1, 'comment'=>"Id"),
+ 'ref' => array('type'=>'varchar(128)', 'label'=>'Ref', 'enabled'=>'1', 'position'=>10, 'notnull'=>1, 'visible'=>4, 'noteditable'=>'1', 'default'=>'(PROV)', 'index'=>1, 'searchall'=>1, 'showoncombobox'=>'1', 'comment'=>"Reference of object"),
+ 'label' => array('type'=>'varchar(255)', 'label'=>'Label', 'enabled'=>'1', 'position'=>30, 'notnull'=>0, 'visible'=>1, 'searchall'=>1, 'css'=>'minwidth200'/*, 'help'=>"Help text"*/, 'showoncombobox'=>'1',),
+ 'description' => array('type'=>'text', 'label'=>'Description', 'enabled'=>'1', 'position'=>31, 'notnull'=>0, 'visible'=>3,),
+ 'fk_project' => array('type'=>'integer:Project:projet/class/project.class.php:1', 'label'=>'Project', 'enabled'=>'1', 'position'=>32, 'notnull'=>-1, 'visible'=>-1, 'index'=>1,),
+ 'fk_soc' => array('type'=>'integer:Societe:societe/class/societe.class.php:1:status=1 AND entity IN (__SHARED_ENTITIES__)', 'label'=>'ThirdParty', 'enabled'=>'1', 'position'=>50, 'notnull'=>-1, 'visible'=>1, 'index'=>1/*, 'help'=>"LinkToThirparty"*/,),
+ 'fk_warehouse_source' => array('type'=>'integer:Entrepot:product/stock/class/entrepot.class.php', 'label'=>'Entrepôt source', 'enabled'=>'1', 'position'=>50, 'notnull'=>0, 'visible'=>1, 'help'=>'HelpWarehouseStockTransferSource',),
+ 'fk_warehouse_destination' => array('type'=>'integer:Entrepot:product/stock/class/entrepot.class.php', 'label'=>'Entrepôt de destination', 'enabled'=>'1', 'position'=>51, 'notnull'=>0, 'visible'=>1, 'help'=>'HelpWarehouseStockTransferDestination'),
+ 'note_public' => array('type'=>'html', 'label'=>'NotePublic', 'enabled'=>'1', 'position'=>61, 'notnull'=>0, 'visible'=>0,),
+ 'note_private' => array('type'=>'html', 'label'=>'NotePrivate', 'enabled'=>'1', 'position'=>62, 'notnull'=>0, 'visible'=>0,),
+ 'date_creation' => array('type'=>'datetime', 'label'=>'DateCreation', 'enabled'=>'1', 'position'=>500, 'notnull'=>1, 'visible'=>-2,),
+ 'date_prevue_depart' => array('type'=>'date', 'label'=>'DatePrevueDepart', 'enabled'=>'1', 'position'=>100, 'notnull'=>0, 'visible'=>1,),
+ 'date_reelle_depart' => array('type'=>'date', 'label'=>'DateReelleDepart', 'enabled'=>'1', 'position'=>101, 'notnull'=>0, 'visible'=>5,),
+ 'date_prevue_arrivee' => array('type'=>'date', 'label'=>'DatePrevueArrivee', 'enabled'=>'1', 'position'=>102, 'notnull'=>0, 'visible'=>1,),
+ 'date_reelle_arrivee' => array('type'=>'date', 'label'=>'DateReelleArrivee', 'enabled'=>'1', 'position'=>103, 'notnull'=>0, 'visible'=>5,),
+ 'lead_time_for_warning' => array('type'=>'integer', 'label'=>'LeadTimeForWarning', 'enabled'=>'1', 'position'=>200, 'default'=>0, 'notnull'=>0, 'visible'=>1),
+ 'tms' => array('type'=>'timestamp', 'label'=>'DateModification', 'enabled'=>'1', 'position'=>501, 'notnull'=>0, 'visible'=>-2,),
+ 'fk_user_creat' => array('type'=>'integer:User:user/class/user.class.php', 'label'=>'UserAuthor', 'enabled'=>'1', 'position'=>510, 'notnull'=>1, 'visible'=>-2, 'foreignkey'=>'user.rowid',),
+ 'fk_user_modif' => array('type'=>'integer:User:user/class/user.class.php', 'label'=>'ChangedBy', 'enabled'=>'1', 'position'=>511, 'notnull'=>-1, 'visible'=>-2,),
+ 'import_key' => array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>'1', 'position'=>1000, 'notnull'=>-1, 'visible'=>-2,),
+ 'model_pdf' => array('type'=>'varchar(255)', 'label'=>'Model pdf', 'enabled'=>'1', 'position'=>1010, 'notnull'=>-1, 'visible'=>0,),
+ 'status' => array('type'=>'smallint', 'label'=>'Status', 'enabled'=>'1', 'position'=>1000, 'notnull'=>1, 'visible'=>5, 'index'=>1, 'arrayofkeyval'=>array('0'=>'Draft', '1'=>'Validated', '2'=>'StockStransferDecremented', '3'=>'StockStransferIncremented'),),
+ );
+ public $rowid;
+ public $ref;
+ public $label;
+ public $fk_soc;
+ public $fk_project;
+ public $description;
+ public $note_public;
+ public $note_private;
+ public $date_creation;
+ public $tms;
+ public $fk_user_creat;
+ public $fk_user_modif;
+ public $import_key;
+ public $model_pdf;
+ public $status;
+ // END MODULEBUILDER PROPERTIES
+
+
+ // If this object has a subtable with lines
+
+ /**
+ * @var int Name of subtable line
+ */
+ public $table_element_line = 'stocktransfer_stocktransferline';
+
+ /**
+ * @var int Field with ID of parent key if this object has a parent
+ */
+ public $fk_element = 'fk_stocktransfer';
+
+ /**
+ * @var int Name of subtable class that manage subtable lines
+ */
+ //public $class_element_line = 'StockTransferline';
+
+ /**
+ * @var array List of child tables. To test if we can delete object.
+ */
+ //protected $childtables = array();
+
+ /**
+ * @var array List of child tables. To know object to delete on cascade.
+ * If name matches '@ClassNAme:FilePathClass;ParentFkFieldName' it will
+ * call method deleteByParentField(parentId, ParentFkFieldName) to fetch and delete child object
+ */
+ //protected $childtablesoncascade = array('stocktransfer_stocktransferdet');
+
+ /**
+ * @var StockTransferLine[] Array of subtable lines
+ */
+ //public $lines = array();
+
+
+
+ /**
+ * Constructor
+ *
+ * @param DoliDb $db Database handler
+ */
+ public function __construct(DoliDB $db)
+ {
+ global $conf, $langs;
+
+ $this->db = $db;
+ $this->origin_type = 'StockTransfer@stocktransfer';
+
+ if (empty($conf->global->MAIN_SHOW_TECHNICAL_ID) && isset($this->fields['rowid'])) $this->fields['rowid']['visible'] = 0;
+ if (empty($conf->multicompany->enabled) && isset($this->fields['entity'])) $this->fields['entity']['enabled'] = 0;
+
+ // Example to show how to set values of fields definition dynamically
+ /*if ($user->rights->stocktransfer->stocktransfer->read) {
+ $this->fields['myfield']['visible'] = 1;
+ $this->fields['myfield']['noteditable'] = 0;
+ }*/
+
+ // Unset fields that are disabled
+ foreach ($this->fields as $key => $val)
+ {
+ if (isset($val['enabled']) && empty($val['enabled']))
+ {
+ unset($this->fields[$key]);
+ }
+ }
+
+ // Translate some data of arrayofkeyval
+ if (is_object($langs))
+ {
+ foreach ($this->fields as $key => $val)
+ {
+ if (is_array($val['arrayofkeyval']))
+ {
+ foreach ($val['arrayofkeyval'] as $key2 => $val2)
+ {
+ $this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Create object into database
+ *
+ * @param User $user User that creates
+ * @param bool $notrigger false=launch triggers after, true=disable triggers
+ * @return int <0 if KO, Id of created object if OK
+ */
+ public function create(User $user, $notrigger = false)
+ {
+ $this->status = (int)$this->status;
+ if($this->fk_warehouse_source <= 0) $this->fk_warehouse_source = 0;
+ if($this->fk_warehouse_destination <= 0) $this->fk_warehouse_destination = 0;
+ return $this->createCommon($user, $notrigger);
+ }
+
+ /**
+ * Clone an object into another one
+ *
+ * @param User $user User that creates
+ * @param int $fromid Id of object to clone
+ * @return mixed New object created, <0 if KO
+ */
+ public function createFromClone(User $user, $fromid)
+ {
+ global $langs, $extrafields;
+ $error = 0;
+
+ dol_syslog(__METHOD__, LOG_DEBUG);
+
+ $object = new self($this->db);
+
+ $this->db->begin();
+
+ // Load source object
+ $result = $object->fetchCommon($fromid);
+ if ($result > 0 && !empty($object->table_element_line)) $object->fetchLines();
+
+ // get lines so they will be clone
+ //foreach($this->lines as $line)
+ // $line->fetch_optionals();
+
+ // Reset some properties
+ unset($object->id);
+ unset($object->fk_user_creat);
+ unset($object->import_key);
+ unset($object->date_prevue_depart);
+ unset($object->date_prevue_arrivee);
+ unset($object->date_reelle_depart);
+ unset($object->date_reelle_arrivee);
+
+
+ // Clear fields
+ $object->ref = empty($this->fields['ref']['default']) ? "copy_of_".$object->ref : $this->fields['ref']['default'];
+ $object->label = empty($this->fields['label']['default']) ? $langs->trans("CopyOf")." ".$object->label : $this->fields['label']['default'];
+ $object->status = self::STATUS_DRAFT;
+ // ...
+ // Clear extrafields that are unique
+ if (is_array($object->array_options) && count($object->array_options) > 0)
+ {
+ $extrafields->fetch_name_optionals_label($this->table_element);
+ foreach ($object->array_options as $key => $option)
+ {
+ $shortkey = preg_replace('/options_/', '', $key);
+ if (!empty($extrafields->attributes[$this->table_element]['unique'][$shortkey]))
+ {
+ //var_dump($key); var_dump($clonedObj->array_options[$key]); exit;
+ unset($object->array_options[$key]);
+ }
+ }
+ }
+
+ // Create clone
+ $object->context['createfromclone'] = 'createfromclone';
+ $result = $object->createCommon($user);
+ if ($result < 0) {
+ $error++;
+ $this->error = $object->error;
+ $this->errors = $object->errors;
+ }
+
+ if (!$error)
+ {
+ // copy internal contacts
+ if ($this->copy_linked_contact($object, 'internal') < 0)
+ {
+ $error++;
+ }
+ }
+
+ if (!$error)
+ {
+ // copy external contacts if same company
+ if (property_exists($this, 'socid') && $this->socid == $object->socid)
+ {
+ if ($this->copy_linked_contact($object, 'external') < 0)
+ $error++;
+ }
+ }
+
+ unset($object->context['createfromclone']);
+
+ // End
+ if (!$error) {
+ $this->db->commit();
+ return $object;
+ } else {
+ $this->db->rollback();
+ return -1;
+ }
+ }
+
+ /**
+ * Load object in memory from the database
+ *
+ * @param int $id Id object
+ * @param string $ref Ref
+ * @return int <0 if KO, 0 if not found, >0 if OK
+ */
+ public function fetch($id, $ref = null)
+ {
+ $result = $this->fetchCommon($id, $ref);
+ if ($result > 0 && !empty($this->table_element_line)) $this->fetchLines();
+ return $result;
+ }
+
+ /**
+ * Load object lines in memory from the database
+ *
+ * @return int <0 if KO, 0 if not found, >0 if OK
+ */
+ public function fetchLines()
+ {
+ require_once DOL_DOCUMENT_ROOT . '/product/stock/stocktransfer/class/stocktransferline.class.php';
+ $this->lines = array();
+
+ $result = $this->fetchLinesCommon();
+ usort($this->lines, array('stocktransfer', 'cmp'));
+ return $result;
+ }
+
+ function cmp($a, $b)
+ {
+ if ($a->rang == $b->rang) {
+ return 0;
+ }
+ return ($a->rang < $b->rang) ? -1 : 1;
+ }
+
+ function getValorisationTotale() {
+
+ $total_pmp = 0;
+
+ if(empty($this->lines)) $this->fetchLines();
+ if(!empty($this->lines)) {
+ foreach ($this->lines as $l) $total_pmp+= ($l->pmp * $l->qty);
+ }
+
+ return $total_pmp;
+
+ }
+
+ /**
+ * Load list of objects in memory from the database.
+ *
+ * @param string $sortorder Sort Order
+ * @param string $sortfield Sort field
+ * @param int $limit limit
+ * @param int $offset Offset
+ * @param array $filter Filter array. Example array('field'=>'valueforlike', 'customurl'=>...)
+ * @param string $filtermode Filter mode (AND or OR)
+ * @return array|int int <0 if KO, array of pages if OK
+ */
+ public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, array $filter = array(), $filtermode = 'AND')
+ {
+ global $conf;
+
+ dol_syslog(__METHOD__, LOG_DEBUG);
+
+ $records = array();
+
+ $sql = 'SELECT ';
+ $sql .= $this->getFieldList();
+ $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
+ if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) $sql .= ' WHERE t.entity IN ('.getEntity($this->table_element).')';
+ else $sql .= ' WHERE 1 = 1';
+ // Manage filter
+ $sqlwhere = array();
+ if (count($filter) > 0) {
+ foreach ($filter as $key => $value) {
+ if ($key == 't.rowid') {
+ $sqlwhere[] = $key.'='.$value;
+ }
+ elseif (strpos($key, 'date') !== false) {
+ $sqlwhere[] = $key.' = \''.$this->db->idate($value).'\'';
+ }
+ elseif ($key == 'customsql') {
+ $sqlwhere[] = $value;
+ }
+ else {
+ $sqlwhere[] = $key.' LIKE \'%'.$this->db->escape($value).'%\'';
+ }
+ }
+ }
+ if (count($sqlwhere) > 0) {
+ $sql .= ' AND ('.implode(' '.$filtermode.' ', $sqlwhere).')';
+ }
+
+ if (!empty($sortfield)) {
+ $sql .= $this->db->order($sortfield, $sortorder);
+ }
+ if (!empty($limit)) {
+ $sql .= ' '.$this->db->plimit($limit, $offset);
+ }
+
+ $resql = $this->db->query($sql);
+ if ($resql) {
+ $num = $this->db->num_rows($resql);
+ $i = 0;
+ while ($i < ($limit ? min($limit, $num) : $num))
+ {
+ $obj = $this->db->fetch_object($resql);
+
+ $record = new self($this->db);
+ $record->setVarsFromFetchObj($obj);
+
+ $records[$record->id] = $record;
+
+ $i++;
+ }
+ $this->db->free($resql);
+
+ return $records;
+ } else {
+ $this->errors[] = 'Error '.$this->db->lasterror();
+ dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
+
+ return -1;
+ }
+ }
+
+ /**
+ * Update object into database
+ *
+ * @param User $user User that modifies
+ * @param bool $notrigger false=launch triggers after, true=disable triggers
+ * @return int <0 if KO, >0 if OK
+ */
+ public function update(User $user, $notrigger = false)
+ {
+ return $this->updateCommon($user, $notrigger);
+ }
+
+ /**
+ * Delete object in database
+ *
+ * @param User $user User that deletes
+ * @param bool $notrigger false=launch triggers after, true=disable triggers
+ * @return int <0 if KO, >0 if OK
+ */
+ public function delete(User $user, $notrigger = false)
+ {
+ if(empty($this->lines)) $this->fetchLines();
+ if(!empty($this->lines)) {
+ foreach ($this->lines as $l) $res = $this->deleteLine($user, $l->id);
+ }
+ return $this->deleteCommon($user, $notrigger);
+ //return $this->deleteCommon($user, $notrigger, 1);
+ }
+
+ /**
+ * Delete a line of object in database
+ *
+ * @param User $user User that delete
+ * @param int $idline Id of line to delete
+ * @param bool $notrigger false=launch triggers after, true=disable triggers
+ * @return int >0 if OK, <0 if KO
+ */
+ public function deleteLine(User $user, $idline, $notrigger = false)
+ {
+ global $db;
+ if ($this->status < 0)
+ {
+ $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
+ return -2;
+ }
+
+ $res = $this->deleteLineCommon($user, $idline, $notrigger);
+ $this->line_order(true);
+ return $res;
+ }
+
+
+ /**
+ * Validate object
+ *
+ * @param User $user User making status change
+ * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
+ * @return int <=0 if OK, 0=Nothing done, >0 if KO
+ */
+ public function validate($user, $notrigger = 0)
+ {
+ global $conf, $langs;
+
+ require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
+
+ $error = 0;
+
+ // Protection
+ if ($this->status == self::STATUS_VALIDATED)
+ {
+ dol_syslog(get_class($this)."::validate action abandonned: already validated", LOG_WARNING);
+ return 0;
+ }
+
+ /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->stocktransfer->stocktransfer->write))
+ || (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->stocktransfer->stocktransfer->stocktransfer_advance->validate))))
+ {
+ $this->error='NotEnoughPermissions';
+ dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
+ return -1;
+ }*/
+
+ $now = dol_now();
+
+ $this->db->begin();
+
+ // Define new ref
+ if (!$error && (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref))) // empty should not happened, but when it occurs, the test save life
+ {
+ $num = $this->getNextNumRef();
+ }
+ else
+ {
+ $num = $this->ref;
+ }
+ $this->newref = $num;
+
+ if (!empty($num)) {
+ // Validate
+ $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
+ $sql .= " SET ref = '".$this->db->escape($num)."',";
+ $sql .= " status = ".self::STATUS_VALIDATED;
+ if (!empty($this->fields['date_validation'])) $sql .= ", date_validation = '".$this->db->idate($now)."',";
+ if (!empty($this->fields['fk_user_valid'])) $sql .= ", fk_user_valid = ".$user->id;
+ $sql .= " WHERE rowid = ".$this->id;
+
+ dol_syslog(get_class($this)."::validate()", LOG_DEBUG);
+ $resql = $this->db->query($sql);
+ if (!$resql)
+ {
+ dol_print_error($this->db);
+ $this->error = $this->db->lasterror();
+ $error++;
+ }
+
+ if (!$error && !$notrigger)
+ {
+ // Call trigger
+ $result = $this->call_trigger('STOCKTRANSFER_VALIDATE', $user);
+ if ($result < 0) $error++;
+ // End call triggers
+ }
+ }
+
+ if (!$error)
+ {
+ $this->oldref = $this->ref;
+
+ // Rename directory if dir was a temporary ref
+ if (preg_match('/^[\(]?PROV/i', $this->ref))
+ {
+ // Now we rename also files into index
+ $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'stocktransfer/".$this->db->escape($this->newref)."'";
+ $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'stocktransfer/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
+ $resql = $this->db->query($sql);
+ if (!$resql) { $error++; $this->error = $this->db->lasterror(); }
+
+ // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
+ $oldref = dol_sanitizeFileName($this->ref);
+ $newref = dol_sanitizeFileName($num);
+ $dirsource = $conf->stocktransfer->dir_output.'/stocktransfer/'.$oldref;
+ $dirdest = $conf->stocktransfer->dir_output.'/stocktransfer/'.$newref;
+ if (!$error && file_exists($dirsource))
+ {
+ dol_syslog(get_class($this)."::validate() rename dir ".$dirsource." into ".$dirdest);
+
+ if (@rename($dirsource, $dirdest))
+ {
+ dol_syslog("Rename ok");
+ // Rename docs starting with $oldref with $newref
+ $listoffiles = dol_dir_list($conf->stocktransfer->dir_output.'/stocktransfer/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
+ foreach ($listoffiles as $fileentry)
+ {
+ $dirsource = $fileentry['name'];
+ $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
+ $dirsource = $fileentry['path'].'/'.$dirsource;
+ $dirdest = $fileentry['path'].'/'.$dirdest;
+ @rename($dirsource, $dirdest);
+ }
+ }
+ }
+ }
+ }
+
+ // Set new ref and current status
+ if (!$error)
+ {
+ $this->ref = $num;
+ $this->status = self::STATUS_VALIDATED;
+ }
+
+ if (!$error)
+ {
+ $this->db->commit();
+ return 1;
+ }
+ else
+ {
+ $this->db->rollback();
+ return -1;
+ }
+ }
+
+
+ /**
+ * Set draft status
+ *
+ * @param User $user Object user that modify
+ * @param int $notrigger 1=Does not execute triggers, 0=Execute triggers
+ * @return int <0 if KO, >0 if OK
+ */
+ public function setDraft($user, $notrigger = 0)
+ {
+ // Protection
+ if ($this->status <= self::STATUS_DRAFT)
+ {
+ return 0;
+ }
+
+ /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->stocktransfer->write))
+ || (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->stocktransfer->stocktransfer_advance->validate))))
+ {
+ $this->error='Permission denied';
+ return -1;
+ }*/
+
+ return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'STOCKTRANSFER_UNVALIDATE');
+ }
+
+ /**
+ * Set cancel status
+ *
+ * @param User $user Object user that modify
+ * @param int $notrigger 1=Does not execute triggers, 0=Execute triggers
+ * @return int <0 if KO, 0=Nothing done, >0 if OK
+ */
+ public function cancel($user, $notrigger = 0)
+ {
+ // Protection
+ if ($this->status != self::STATUS_VALIDATED)
+ {
+ return 0;
+ }
+
+ /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->stocktransfer->write))
+ || (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->stocktransfer->stocktransfer_advance->validate))))
+ {
+ $this->error='Permission denied';
+ return -1;
+ }*/
+
+ return $this->setStatusCommon($user, self::STATUS_CLOSED, $notrigger, 'STOCKTRANSFER_CLOSE');
+ }
+
+ /**
+ * Set back to validated status
+ *
+ * @param User $user Object user that modify
+ * @param int $notrigger 1=Does not execute triggers, 0=Execute triggers
+ * @return int <0 if KO, 0=Nothing done, >0 if OK
+ */
+ public function reopen($user, $notrigger = 0)
+ {
+ // Protection
+ if ($this->status != self::STATUS_CLOSED)
+ {
+ return 0;
+ }
+
+ /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->stocktransfer->write))
+ || (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->stocktransfer->stocktransfer_advance->validate))))
+ {
+ $this->error='Permission denied';
+ return -1;
+ }*/
+
+ return $this->setStatusCommon($user, self::STATUS_VALIDATED, $notrigger, 'STOCKTRANSFER_REOPEN');
+ }
+
+ /**
+ * Return a link to the object card (with optionaly the picto)
+ *
+ * @param int $withpicto Include picto in link (0=No picto, 1=Include picto into link, 2=Only picto)
+ * @param string $option On what the link point to ('nolink', ...)
+ * @param int $notooltip 1=Disable tooltip
+ * @param string $morecss Add more css on link
+ * @param int $save_lastsearch_value -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
+ * @return string String with URL
+ */
+ public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
+ {
+ global $conf, $langs, $hookmanager;
+
+ if (!empty($conf->dol_no_mouse_hover)) $notooltip = 1; // Force disable tooltips
+
+ $result = '';
+
+ $label = ''.$langs->trans("StockTransfer").'';
+ $label .= '
';
+ $label .= ''.$langs->trans('Ref').': '.$this->ref;
+ if (isset($this->status)) {
+ $label .= '
'.$langs->trans("Status").": ".$this->getLibStatut(5);
+ }
+
+ $url = dol_buildpath('/product/stock/stocktransfer/stocktransfer_card.php', 1).'?id='.$this->id;
+
+ if ($option != 'nolink')
+ {
+ // Add param to save lastsearch_values or not
+ $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
+ if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) $add_save_lastsearch_values = 1;
+ if ($add_save_lastsearch_values) $url .= '&save_lastsearch_values=1';
+ }
+
+ $linkclose = '';
+ if (empty($notooltip))
+ {
+ if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER))
+ {
+ $label = $langs->trans("ShowStockTransfer");
+ $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
+ }
+ $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
+ $linkclose .= ' class="classfortooltip'.($morecss ? ' '.$morecss : '').'"';
+ }
+ else $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
+
+ $linkstart = '';
+ $linkend = '';
+
+ $result .= $linkstart;
+
+ if (empty($this->showphoto_on_popup)) {
+ if ($withpicto) $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
+ } else {
+ if ($withpicto) {
+ require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
+
+ list($class, $module) = explode('@', $this->picto);
+ $upload_dir = $conf->$module->multidir_output[$conf->entity]."/$class/".dol_sanitizeFileName($this->ref);
+ $filearray = dol_dir_list($upload_dir, "files");
+ $filename = $filearray[0]['name'];
+ if (!empty($filename)) {
+ $pospoint = strpos($filearray[0]['name'], '.');
+
+ $pathtophoto = $class.'/'.$this->ref.'/thumbs/'.substr($filename, 0, $pospoint).'_mini'.substr($filename, $pospoint);
+ if (empty($conf->global->{strtoupper($module.'_'.$class).'_FORMATLISTPHOTOSASUSERS'})) {
+ $result .= '';
+ }
+ else {
+ $result .= '.')
';
+ }
+
+ $result .= '';
+ }
+ else {
+ $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
+ }
+ }
+ }
+
+ if ($withpicto != 2) $result .= $this->ref;
+
+ $result .= $linkend;
+ //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
+
+ global $action, $hookmanager;
+ $hookmanager->initHooks(array('stocktransferdao'));
+ $parameters = array('id'=>$this->id, 'getnomurl'=>$result);
+ $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
+ if ($reshook > 0) $result = $hookmanager->resPrint;
+ else $result .= $hookmanager->resPrint;
+
+ return $result;
+ }
+
+ /**
+ * Return label of the status
+ *
+ * @param int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto
+ * @return string Label of status
+ */
+ public function getLibStatut($mode = 0)
+ {
+ return $this->LibStatut($this->status, $mode);
+ }
+
+ // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
+ /**
+ * Return the status
+ *
+ * @param int $status Id status
+ * @param int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto
+ * @return string Label of status
+ */
+ public function LibStatut($status, $mode = 0)
+ {
+ // phpcs:enable
+ if (empty($this->labelStatus) || empty($this->labelStatusShort))
+ {
+ global $langs;
+ //$langs->load("stocktransfer@stocktransfer");
+ $this->labelStatus[self::STATUS_DRAFT] = $langs->trans('Draft');
+ $this->labelStatus[self::STATUS_VALIDATED] = $langs->trans('Validated');
+ $this->labelStatus[self::STATUS_TRANSFERED] = $langs->trans('StockStransferDecremented');
+ $this->labelStatus[self::STATUS_CLOSED] = $langs->trans('StockStransferIncremented');
+ $this->labelStatusShort[self::STATUS_DRAFT] = $langs->trans('Draft');
+ $this->labelStatusShort[self::STATUS_VALIDATED] = $langs->trans('Validated');
+ $this->labelStatusShort[self::STATUS_TRANSFERED] = $langs->trans('StockStransferDecremented');
+ $this->labelStatusShort[self::STATUS_CLOSED] = $langs->trans('StockStransferIncremented');
+ }
+
+ $statusType = 'status'.$status;
+ //if ($status == self::STATUS_VALIDATED) $statusType = 'status1';
+ if ($status == self::STATUS_CLOSED) $statusType = 'status6';
+
+ return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
+ }
+
+ /**
+ * Load the info information in the object
+ *
+ * @param int $id Id of object
+ * @return void
+ */
+ public function info($id)
+ {
+ $sql = 'SELECT rowid, date_creation as datec, tms as datem,';
+ $sql .= ' fk_user_creat, fk_user_modif';
+ $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
+ $sql .= ' WHERE t.rowid = '.$id;
+ $result = $this->db->query($sql);
+ if ($result)
+ {
+ if ($this->db->num_rows($result))
+ {
+ $obj = $this->db->fetch_object($result);
+ $this->id = $obj->rowid;
+ if ($obj->fk_user_author)
+ {
+ $cuser = new User($this->db);
+ $cuser->fetch($obj->fk_user_author);
+ $this->user_creation = $cuser;
+ }
+
+ if ($obj->fk_user_valid)
+ {
+ $vuser = new User($this->db);
+ $vuser->fetch($obj->fk_user_valid);
+ $this->user_validation = $vuser;
+ }
+
+ if ($obj->fk_user_cloture)
+ {
+ $cluser = new User($this->db);
+ $cluser->fetch($obj->fk_user_cloture);
+ $this->user_cloture = $cluser;
+ }
+
+ $this->date_creation = $this->db->jdate($obj->datec);
+ $this->date_modification = $this->db->jdate($obj->datem);
+ $this->date_validation = $this->db->jdate($obj->datev);
+ }
+
+ $this->db->free($result);
+ }
+ else
+ {
+ dol_print_error($this->db);
+ }
+ }
+
+ /**
+ * Initialise object with example values
+ * Id must be 0 if object instance is a specimen
+ *
+ * @return void
+ */
+ public function initAsSpecimen()
+ {
+ $this->initAsSpecimenCommon();
+ }
+
+ /**
+ * Create an array of lines
+ *
+ * @return array|int array of lines if OK, <0 if KO
+ */
+ public function getLinesArray()
+ {
+ $this->lines = array();
+
+ $objectline = new StockTransferLine($this->db);
+ $result = $objectline->fetchAll('ASC', 'rang', 0, 0, array('customsql'=>'fk_stocktransfer = '.$this->id));
+
+ if (is_numeric($result))
+ {
+ $this->error = $this->error;
+ $this->errors = $this->errors;
+ return $result;
+ }
+ else
+ {
+ $this->lines = $result;
+ return $this->lines;
+ }
+ }
+
+ /**
+ * Returns the reference to the following non used object depending on the active numbering module.
+ *
+ * @return string Object free reference
+ */
+ public function getNextNumRef()
+ {
+ global $langs, $conf;
+ $langs->load("stocks");
+
+ if (empty($conf->global->STOCKTRANSFER_STOCKTRANSFER_ADDON)) {
+ $conf->global->STOCKTRANSFER_STOCKTRANSFER_ADDON = 'mod_stocktransfer_standard';
+ }
+
+ if (!empty($conf->global->STOCKTRANSFER_STOCKTRANSFER_ADDON))
+ {
+ $mybool = false;
+
+ $file = $conf->global->STOCKTRANSFER_STOCKTRANSFER_ADDON.".php";
+ $classname = $conf->global->STOCKTRANSFER_STOCKTRANSFER_ADDON;
+
+ // Include file with class
+ $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
+ foreach ($dirmodels as $reldir)
+ {
+ $dir = dol_buildpath($reldir."core/modules/stocktransfer/");
+
+ // Load file with numbering class (if found)
+ $mybool |= @include_once $dir.$file;
+ }
+
+ if ($mybool === false)
+ {
+ dol_print_error('', "Failed to include file ".$file);
+ return '';
+ }
+
+ if (class_exists($classname)) {
+ $obj = new $classname();
+ $numref = $obj->getNextValue($this);
+
+ if ($numref != '' && $numref != '-1')
+ {
+ return $numref;
+ }
+ else
+ {
+ $this->error = $obj->error;
+ //dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error);
+ return "";
+ }
+ } else {
+ print $langs->trans("Error")." ".$langs->trans("ClassNotFound").' '.$classname;
+ return "";
+ }
+ }
+ else
+ {
+ print $langs->trans("ErrorNumberingModuleNotSetup", $this->element);
+ return "";
+ }
+ }
+
+ /**
+ * Create a document onto disk according to template module.
+ *
+ * @param string $modele Force template to use ('' to not force)
+ * @param Translate $outputlangs objet lang a utiliser pour traduction
+ * @param int $hidedetails Hide details of lines
+ * @param int $hidedesc Hide description
+ * @param int $hideref Hide ref
+ * @param null|array $moreparams Array to provide more information
+ * @return int 0 if KO, 1 if OK
+ */
+ public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
+ {
+ global $conf, $langs;
+
+ $result = 0;
+ $includedocgeneration = 1;
+
+ $langs->load("stocks");
+
+ if (!dol_strlen($modele)) {
+ $modele = 'eagle';
+
+ if ($this->modelpdf) {
+ $modele = $this->modelpdf;
+ } elseif (!empty($conf->global->STOCKTRANSFER_ADDON_PDF)) {
+ $modele = $conf->global->STOCKTRANSFER_ADDON_PDF;
+ }
+ }
+
+ $modelpath = "core/modules/stocktransfer/doc/";
+
+ if ($includedocgeneration) {
+ $result = $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Action executed by scheduler
+ * CAN BE A CRON TASK. In such a case, parameters come from the schedule job setup field 'Parameters'
+ * Use public function doScheduledJob($param1, $param2, ...) to get parameters
+ *
+ * @return int 0 if OK, <>0 if KO (this function is used also by cron so only 0 is OK)
+ */
+ public function doScheduledJob()
+ {
+ global $conf, $langs;
+
+ //$conf->global->SYSLOG_FILE = 'DOL_DATA_ROOT/dolibarr_mydedicatedlofile.log';
+
+ $error = 0;
+ $this->output = '';
+ $this->error = '';
+
+ dol_syslog(__METHOD__, LOG_DEBUG);
+
+ $now = dol_now();
+
+ $this->db->begin();
+
+ // ...
+
+ $this->db->commit();
+
+ return $error;
+ }
+}
+
+/**
+ * Class StockTransferLine. You can also remove this and generate a CRUD class for lines objects.
+ */
+//class StockTransferLine
+//{
+// // To complete with content of an object StockTransferLine
+// // We should have a field rowid, fk_stocktransfer and position
+//
+// /**
+// * @var int Does object support extrafields ? 0=No, 1=Yes
+// */
+// public $isextrafieldmanaged = 0;
+//
+// /**
+// * Constructor
+// *
+// * @param DoliDb $db Database handler
+// */
+// public function __construct(DoliDB $db)
+// {
+// $this->db = $db;
+// }
+//}
diff --git a/htdocs/product/stock/stocktransfer/class/stocktransferline.class.php b/htdocs/product/stock/stocktransfer/class/stocktransferline.class.php
new file mode 100644
index 00000000000..35d79dda95d
--- /dev/null
+++ b/htdocs/product/stock/stocktransfer/class/stocktransferline.class.php
@@ -0,0 +1,1125 @@
+
+ * Copyright (C) 2021 Gauthier VERDOL
+ * Copyright (C) ---Put here your own copyright and developer email---
+ *
+ * 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 .
+ */
+
+/**
+ * \file class/stocktransferline.class.php
+ * \ingroup stocktransfer
+ * \brief This file is a CRUD class file for StockTransferLine (Create/Read/Update/Delete)
+ */
+
+// Put here all includes required by your class file
+require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
+//require_once DOL_DOCUMENT_ROOT . '/societe/class/societe.class.php';
+//require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
+
+/**
+ * Class for StockTransferLine
+ */
+class StockTransferLine extends CommonObject
+{
+ /**
+ * @var string ID to identify managed object.
+ */
+ public $element = 'stocktransferline';
+
+ /**
+ * @var string Name of table without prefix where object is stored. This is also the key used for extrafields management.
+ */
+ public $table_element = 'stocktransfer_stocktransferline';
+
+ /**
+ * @var int Does this object support multicompany module ?
+ * 0=No test on entity, 1=Test with field entity, 'field@table'=Test with link by field@table
+ */
+ public $ismultientitymanaged = 0;
+
+ /**
+ * @var int Does object support extrafields ? 0=No, 1=Yes
+ */
+ public $isextrafieldmanaged = 1;
+
+ /**
+ * @var string String with name of icon for stocktransferline. Must be the part after the 'object_' into object_stocktransferline.png
+ */
+ public $picto = 'stocktransferline@stocktransfer';
+
+
+ const STATUS_DRAFT = 0;
+ const STATUS_VALIDATED = 1;
+ const STATUS_CANCELED = 9;
+
+
+ /**
+ * 'type' if the field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter]]', 'varchar(x)', 'double(24,8)', 'real', 'price', 'text', 'html', 'date', 'datetime', 'timestamp', 'duration', 'mail', 'phone', 'url', 'password')
+ * Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)"
+ * 'label' the translation key.
+ * 'enabled' is a condition when the field must be managed (Example: 1 or '$conf->global->MY_SETUP_PARAM)
+ * 'position' is the sort order of field.
+ * 'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
+ * 'visible' says if field is visible in list (Examples: 0=Not visible, 1=Visible on list and create/update/view forms, 2=Visible on list only, 3=Visible on create/update/view form only (not list), 4=Visible on list and update/view form only (not create). 5=Visible on list and view only (not create/not update). Using a negative value means field is not shown by default on list but can be selected for viewing)
+ * 'noteditable' says if field is not editable (1 or 0)
+ * 'default' is a default value for creation (can still be overwrote by the Setup of Default Values if field is editable in creation form). Note: If default is set to '(PROV)' and field is 'ref', the default value will be set to '(PROVid)' where id is rowid when a new record is created.
+ * 'index' if we want an index in database.
+ * 'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommanded to name the field fk_...).
+ * 'searchall' is 1 if we want to search in this field when making a search from the quick search button.
+ * 'isameasure' must be set to 1 if you want to have a total on list for this field. Field type must be summable like integer or double(24,8).
+ * 'css' is the CSS style to use on field. For example: 'maxwidth200'
+ * 'help' is a string visible as a tooltip on field
+ * 'showoncombobox' if value of the field must be visible into the label of the combobox that list record
+ * 'disabled' is 1 if we want to have the field locked by a 'disabled' attribute. In most cases, this is never set into the definition of $fields into class, but is set dynamically by some part of code.
+ * 'arraykeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel")
+ * 'autofocusoncreate' to have field having the focus on a create form. Only 1 field should have this property set to 1.
+ * 'comment' is not used. You can store here any text of your choice. It is not used by application.
+ *
+ * Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
+ */
+
+ // BEGIN MODULEBUILDER PROPERTIES
+ /**
+ * @var array Array with all fields and their property. Do not use it as a static var. It may be modified by constructor.
+ */
+ public $fields=array(
+ 'rowid' => array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>'1', 'position'=>1, 'notnull'=>1, 'visible'=>0, 'noteditable'=>'1', 'index'=>1, 'comment'=>"Id"),
+ 'amount' => array('type'=>'price', 'label'=>'Amount', 'enabled'=>'1', 'position'=>40, 'notnull'=>0, 'visible'=>1, 'default'=>'null', 'isameasure'=>'1', 'help'=>"Help text for amount",),
+ 'qty' => array('type'=>'real', 'label'=>'Qty', 'enabled'=>'1', 'position'=>45, 'notnull'=>0, 'visible'=>1, 'default'=>'0', 'isameasure'=>'1', 'css'=>'maxwidth75imp', 'help'=>"Help text for quantity",),
+ 'fk_warehouse_destination' => array('type'=>'integer:Entrepot:product/stock/class/entrepot.class.php', 'label'=>'Entrepôt de destination', 'enabled'=>'1', 'position'=>50, 'notnull'=>1, 'visible'=>1,),
+ 'fk_warehouse_source' => array('type'=>'integer:Entrepot:product/stock/class/entrepot.class.php', 'label'=>'Entrepôt source', 'enabled'=>'1', 'position'=>50, 'notnull'=>1, 'visible'=>1,),
+ 'fk_stocktransfer' => array('type'=>'integer:StockTransfer:stocktransfer/stock/class/stocktransfer.class.php', 'label'=>'StockTransfer', 'enabled'=>'1', 'position'=>50, 'notnull'=>1, 'visible'=>0,),
+ 'fk_product' => array('type'=>'integer:Product:product/class/product.class.php', 'label'=>'Product', 'enabled'=>'1', 'position'=>50, 'notnull'=>1, 'visible'=>1,),
+ 'batch' => array('type'=>'varchar(128)', 'label'=>'Batch', 'enabled'=>'1', 'position'=>1000, 'notnull'=>-1, 'visible'=>1,),
+ 'pmp' => array('type'=>'double'/*, 'help'=>'THMEstimatedHelp'*/, 'label'=>'PMP', 'enabled'=>'1', 'position'=>50, 'notnull'=>0, 'visible'=>1,),
+ 'rang' => array('type'=>'integer', 'label'=>'Qty', 'enabled'=>'1', 'position'=>45, 'notnull'=>0, 'visible'=>0, 'default'=>'0', 'isameasure'=>'1', 'css'=>'maxwidth75imp', 'help'=>"Help text for quantity",),
+ );
+ public $rowid;
+ public $amount;
+ public $qty;
+ public $fk_warehouse_destination;
+ public $fk_warehouse_source;
+ public $fk_stocktransfer;
+ public $fk_product;
+ public $batch;
+ // END MODULEBUILDER PROPERTIES
+
+
+ // If this object has a subtable with lines
+
+ /**
+ * @var int Name of subtable line
+ */
+ //public $table_element_line = 'stocktransfer_stocktransferlineline';
+
+ /**
+ * @var int Field with ID of parent key if this object has a parent
+ */
+ //public $fk_element = 'fk_stocktransferline';
+
+ /**
+ * @var int Name of subtable class that manage subtable lines
+ */
+ //public $class_element_line = 'StockTransferLineline';
+
+ /**
+ * @var array List of child tables. To test if we can delete object.
+ */
+ //protected $childtables = array();
+
+ /**
+ * @var array List of child tables. To know object to delete on cascade.
+ * If name matches '@ClassNAme:FilePathClass;ParentFkFieldName' it will
+ * call method deleteByParentField(parentId, ParentFkFieldName) to fetch and delete child object
+ */
+ //protected $childtablesoncascade = array('stocktransfer_stocktransferlinedet');
+
+ /**
+ * @var StockTransferLineLine[] Array of subtable lines
+ */
+ //public $lines = array();
+
+
+
+ /**
+ * Constructor
+ *
+ * @param DoliDb $db Database handler
+ */
+ public function __construct(DoliDB $db)
+ {
+ global $conf, $langs;
+
+ $this->db = $db;
+
+ if (empty($conf->global->MAIN_SHOW_TECHNICAL_ID) && isset($this->fields['rowid'])) $this->fields['rowid']['visible'] = 0;
+ if (empty($conf->multicompany->enabled) && isset($this->fields['entity'])) $this->fields['entity']['enabled'] = 0;
+
+ // Example to show how to set values of fields definition dynamically
+ /*if ($user->rights->stocktransfer->stocktransferline->read) {
+ $this->fields['myfield']['visible'] = 1;
+ $this->fields['myfield']['noteditable'] = 0;
+ }*/
+
+ // Unset fields that are disabled
+ foreach ($this->fields as $key => $val)
+ {
+ if (isset($val['enabled']) && empty($val['enabled']))
+ {
+ unset($this->fields[$key]);
+ }
+ }
+
+ // Translate some data of arrayofkeyval
+ if (is_object($langs))
+ {
+ foreach ($this->fields as $key => $val)
+ {
+ if (is_array($val['arrayofkeyval']))
+ {
+ foreach ($val['arrayofkeyval'] as $key2 => $val2)
+ {
+ $this->fields[$key]['arrayofkeyval'][$key2] = $langs->trans($val2);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Create object into database
+ *
+ * @param User $user User that creates
+ * @param bool $notrigger false=launch triggers after, true=disable triggers
+ * @return int <0 if KO, Id of created object if OK
+ */
+ public function create(User $user, $notrigger = false)
+ {
+ return $this->createCommon($user, $notrigger);
+ }
+
+ /**
+ * Clone an object into another one
+ *
+ * @param User $user User that creates
+ * @param int $fromid Id of object to clone
+ * @return mixed New object created, <0 if KO
+ */
+ public function createFromClone(User $user, $fromid)
+ {
+ global $langs, $extrafields;
+ $error = 0;
+
+ dol_syslog(__METHOD__, LOG_DEBUG);
+
+ $object = new self($this->db);
+
+ $this->db->begin();
+
+ // Load source object
+ $result = $object->fetchCommon($fromid);
+ if ($result > 0 && !empty($object->table_element_line)) $object->fetchLines();
+
+ // get lines so they will be clone
+ //foreach($this->lines as $line)
+ // $line->fetch_optionals();
+
+ // Reset some properties
+ unset($object->id);
+ unset($object->fk_user_creat);
+ unset($object->import_key);
+
+
+ // Clear fields
+ $object->ref = empty($this->fields['ref']['default']) ? "copy_of_".$object->ref : $this->fields['ref']['default'];
+ $object->label = empty($this->fields['label']['default']) ? $langs->trans("CopyOf")." ".$object->label : $this->fields['label']['default'];
+ $object->status = self::STATUS_DRAFT;
+ // ...
+ // Clear extrafields that are unique
+ if (is_array($object->array_options) && count($object->array_options) > 0)
+ {
+ $extrafields->fetch_name_optionals_label($this->table_element);
+ foreach ($object->array_options as $key => $option)
+ {
+ $shortkey = preg_replace('/options_/', '', $key);
+ if (!empty($extrafields->attributes[$this->table_element]['unique'][$shortkey]))
+ {
+ //var_dump($key); var_dump($clonedObj->array_options[$key]); exit;
+ unset($object->array_options[$key]);
+ }
+ }
+ }
+
+ // Create clone
+ $object->context['createfromclone'] = 'createfromclone';
+ $result = $object->createCommon($user);
+ if ($result < 0) {
+ $error++;
+ $this->error = $object->error;
+ $this->errors = $object->errors;
+ }
+
+ if (!$error)
+ {
+ // copy internal contacts
+ if ($this->copy_linked_contact($object, 'internal') < 0)
+ {
+ $error++;
+ }
+ }
+
+ if (!$error)
+ {
+ // copy external contacts if same company
+ if (property_exists($this, 'socid') && $this->socid == $object->socid)
+ {
+ if ($this->copy_linked_contact($object, 'external') < 0)
+ $error++;
+ }
+ }
+
+ unset($object->context['createfromclone']);
+
+ // End
+ if (!$error) {
+ $this->db->commit();
+ return $object;
+ } else {
+ $this->db->rollback();
+ return -1;
+ }
+ }
+
+ /**
+ * Load object in memory from the database
+ *
+ * @param int $id Id object
+ * @param string $ref Ref
+ * @return int <0 if KO, 0 if not found, >0 if OK
+ */
+ public function fetch($id, $ref = null)
+ {
+ $result = $this->fetchCommon($id, $ref);
+ if ($result > 0 && !empty($this->table_element_line)) $this->fetchLines();
+ return $result;
+ }
+
+ /**
+ * Load object lines in memory from the database
+ *
+ * @return int <0 if KO, 0 if not found, >0 if OK
+ */
+ public function fetchLines()
+ {
+ $this->lines = array();
+
+ $result = $this->fetchLinesCommon();
+ return $result;
+ }
+
+
+ /**
+ * Load list of objects in memory from the database.
+ *
+ * @param string $sortorder Sort Order
+ * @param string $sortfield Sort field
+ * @param int $limit limit
+ * @param int $offset Offset
+ * @param array $filter Filter array. Example array('field'=>'valueforlike', 'customurl'=>...)
+ * @param string $filtermode Filter mode (AND or OR)
+ * @return array|int int <0 if KO, array of pages if OK
+ */
+ public function fetchAll($sortorder = '', $sortfield = '', $limit = 0, $offset = 0, array $filter = array(), $filtermode = 'AND')
+ {
+ global $conf;
+
+ dol_syslog(__METHOD__, LOG_DEBUG);
+
+ $records = array();
+
+ $sql = 'SELECT ';
+ $sql .= $this->getFieldList();
+ $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
+ if (isset($this->ismultientitymanaged) && $this->ismultientitymanaged == 1) $sql .= ' WHERE t.entity IN ('.getEntity($this->table_element).')';
+ else $sql .= ' WHERE 1 = 1';
+ // Manage filter
+ $sqlwhere = array();
+ if (count($filter) > 0) {
+ foreach ($filter as $key => $value) {
+ if ($key == 't.rowid') {
+ $sqlwhere[] = $key.'='.$value;
+ }
+ elseif (strpos($key, 'date') !== false) {
+ $sqlwhere[] = $key.' = \''.$this->db->idate($value).'\'';
+ }
+ elseif ($key == 'customsql') {
+ $sqlwhere[] = $value;
+ }
+ else {
+ $sqlwhere[] = $key.' LIKE \'%'.$this->db->escape($value).'%\'';
+ }
+ }
+ }
+ if (count($sqlwhere) > 0) {
+ $sql .= ' AND ('.implode(' '.$filtermode.' ', $sqlwhere).')';
+ }
+
+ if (!empty($sortfield)) {
+ $sql .= $this->db->order($sortfield, $sortorder);
+ }
+ if (!empty($limit)) {
+ $sql .= ' '.$this->db->plimit($limit, $offset);
+ }
+
+ $resql = $this->db->query($sql);
+ if ($resql) {
+ $num = $this->db->num_rows($resql);
+ $i = 0;
+ while ($i < ($limit ? min($limit, $num) : $num))
+ {
+ $obj = $this->db->fetch_object($resql);
+
+ $record = new self($this->db);
+ $record->setVarsFromFetchObj($obj);
+
+ $records[$record->id] = $record;
+
+ $i++;
+ }
+ $this->db->free($resql);
+
+ return $records;
+ } else {
+ $this->errors[] = 'Error '.$this->db->lasterror();
+ dol_syslog(__METHOD__.' '.join(',', $this->errors), LOG_ERR);
+
+ return -1;
+ }
+ }
+
+ /**
+ * Update object into database
+ *
+ * @param User $user User that modifies
+ * @param bool $notrigger false=launch triggers after, true=disable triggers
+ * @return int <0 if KO, >0 if OK
+ */
+ public function update(User $user, $notrigger = false)
+ {
+ return $this->updateCommon($user, $notrigger);
+ }
+
+ /**
+ * Delete object in database
+ *
+ * @param User $user User that deletes
+ * @param bool $notrigger false=launch triggers after, true=disable triggers
+ * @return int <0 if KO, >0 if OK
+ */
+ public function delete(User $user, $notrigger = false)
+ {
+ return $this->deleteCommon($user, $notrigger);
+ //return $this->deleteCommon($user, $notrigger, 1);
+ }
+
+ /**
+ * Delete a line of object in database
+ *
+ * @param User $user User that delete
+ * @param int $idline Id of line to delete
+ * @param bool $notrigger false=launch triggers after, true=disable triggers
+ * @return int >0 if OK, <0 if KO
+ */
+ public function deleteLine(User $user, $idline, $notrigger = false)
+ {
+ if ($this->status < 0)
+ {
+ $this->error = 'ErrorDeleteLineNotAllowedByObjectStatus';
+ return -2;
+ }
+
+ return $this->deleteLineCommon($user, $idline, $notrigger);
+ }
+
+ function doStockMovement($label, $fk_entrepot, $direction=1) {
+
+ global $db, $conf, $user, $langs;
+
+ require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
+ include_once DOL_DOCUMENT_ROOT . '/product/stock/class/mouvementstock.class.php';
+
+ $p = new Product($db);
+ $p->fetch($this->fk_product);
+
+ $op[0] = "+".trim($this->qty);
+ $op[1] = "-".trim($this->qty);
+ $movementstock = new MouvementStock($db);
+ $movementstock->origin = new StockTransfer($db);
+ $movementstock->origin->id = $this->fk_stocktransfer;
+
+ if (empty($this->batch)) // no batch for line
+ {
+ /*$result = $p->correct_stock(
+ $user,
+ $fk_entrepot,
+ $this->qty,
+ $direction, // 1=décrémentation
+ $label,
+ empty($direction) ? $this->pmp : 0,
+ GETPOST('inventorycode', 'alphanohtml'),
+ 'stocktransfer',
+ $this->fk_stocktransfer
+ );*/
+
+ $result = $movementstock->_create($user, $p->id, $fk_entrepot, $op[$direction], $direction, empty($direction) ? $this->pmp : 0, $label);
+
+ if ($result < 0) {
+ setEventMessages($p->errors, $p->errorss, 'errors');
+ return 0;
+ }
+ }
+ else {
+ if ($p->hasbatch()) {
+ $arraybatchinfo = $p->loadBatchInfo($this->batch);
+ if (count($arraybatchinfo) > 0) {
+ $firstrecord = array_shift($arraybatchinfo);
+ $dlc = $firstrecord['eatby'];
+ $dluo = $firstrecord['sellby'];
+ //var_dump($batch); var_dump($arraybatchinfo); var_dump($firstrecord); var_dump($dlc); var_dump($dluo); exit;
+ } else {
+ $dlc = '';
+ $dluo = '';
+ }
+
+ /*$result = $p->correct_stock_batch(
+ $user,
+ $fk_entrepot,
+ $this->qty,
+ $direction,
+ $label,
+ empty($direction) ? $this->pmp : 0,
+ $dlc,
+ $dluo,
+ $this->batch,
+ GETPOST("codemove")
+ );*/
+
+ $result = $movementstock->_create($user, $p->id, $fk_entrepot, $op[$direction], $direction, empty($direction) ? $this->pmp : 0, $label, '', '', $dlc, $dluo, $this->batch);
+
+ if ($result < 0) {
+ setEventMessages($p->errors, $p->errorss, 'errors');
+ return 0;
+ }
+ } else {
+ setEventMessages($langs->trans('StockTransferNoBatchForProduct', $p->getNomUrl()), '', 'errors');
+ return -1;
+ }
+ }
+
+ return 1;
+
+ }
+
+ /**
+ * Validate object
+ *
+ * @param User $user User making status change
+ * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
+ * @return int <=0 if OK, 0=Nothing done, >0 if KO
+ */
+ public function validate($user, $notrigger = 0)
+ {
+ global $conf, $langs;
+
+ require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
+
+ $error = 0;
+
+ // Protection
+ if ($this->status == self::STATUS_VALIDATED)
+ {
+ dol_syslog(get_class($this)."::validate action abandonned: already validated", LOG_WARNING);
+ return 0;
+ }
+
+ /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->stocktransfer->stocktransferline->write))
+ || (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->stocktransfer->stocktransferline->stocktransferline_advance->validate))))
+ {
+ $this->error='NotEnoughPermissions';
+ dol_syslog(get_class($this)."::valid ".$this->error, LOG_ERR);
+ return -1;
+ }*/
+
+ $now = dol_now();
+
+ $this->db->begin();
+
+ // Define new ref
+ if (!$error && (preg_match('/^[\(]?PROV/i', $this->ref) || empty($this->ref))) // empty should not happened, but when it occurs, the test save life
+ {
+ $num = $this->getNextNumRef();
+ }
+ else
+ {
+ $num = $this->ref;
+ }
+ $this->newref = $num;
+
+ if (!empty($num)) {
+ // Validate
+ $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element;
+ $sql .= " SET ref = '".$this->db->escape($num)."',";
+ $sql .= " status = ".self::STATUS_VALIDATED;
+ if (!empty($this->fields['date_validation'])) $sql .= ", date_validation = '".$this->db->idate($now)."',";
+ if (!empty($this->fields['fk_user_valid'])) $sql .= ", fk_user_valid = ".$user->id;
+ $sql .= " WHERE rowid = ".$this->id;
+
+ dol_syslog(get_class($this)."::validate()", LOG_DEBUG);
+ $resql = $this->db->query($sql);
+ if (!$resql)
+ {
+ dol_print_error($this->db);
+ $this->error = $this->db->lasterror();
+ $error++;
+ }
+
+ if (!$error && !$notrigger)
+ {
+ // Call trigger
+ $result = $this->call_trigger('STOCKTRANSFERLINE_VALIDATE', $user);
+ if ($result < 0) $error++;
+ // End call triggers
+ }
+ }
+
+ if (!$error)
+ {
+ $this->oldref = $this->ref;
+
+ // Rename directory if dir was a temporary ref
+ if (preg_match('/^[\(]?PROV/i', $this->ref))
+ {
+ // Now we rename also files into index
+ $sql = 'UPDATE '.MAIN_DB_PREFIX."ecm_files set filename = CONCAT('".$this->db->escape($this->newref)."', SUBSTR(filename, ".(strlen($this->ref) + 1).")), filepath = 'stocktransferline/".$this->db->escape($this->newref)."'";
+ $sql .= " WHERE filename LIKE '".$this->db->escape($this->ref)."%' AND filepath = 'stocktransferline/".$this->db->escape($this->ref)."' and entity = ".$conf->entity;
+ $resql = $this->db->query($sql);
+ if (!$resql) { $error++; $this->error = $this->db->lasterror(); }
+
+ // We rename directory ($this->ref = old ref, $num = new ref) in order not to lose the attachments
+ $oldref = dol_sanitizeFileName($this->ref);
+ $newref = dol_sanitizeFileName($num);
+ $dirsource = $conf->stocktransfer->dir_output.'/stocktransferline/'.$oldref;
+ $dirdest = $conf->stocktransfer->dir_output.'/stocktransferline/'.$newref;
+ if (!$error && file_exists($dirsource))
+ {
+ dol_syslog(get_class($this)."::validate() rename dir ".$dirsource." into ".$dirdest);
+
+ if (@rename($dirsource, $dirdest))
+ {
+ dol_syslog("Rename ok");
+ // Rename docs starting with $oldref with $newref
+ $listoffiles = dol_dir_list($conf->stocktransfer->dir_output.'/stocktransferline/'.$newref, 'files', 1, '^'.preg_quote($oldref, '/'));
+ foreach ($listoffiles as $fileentry)
+ {
+ $dirsource = $fileentry['name'];
+ $dirdest = preg_replace('/^'.preg_quote($oldref, '/').'/', $newref, $dirsource);
+ $dirsource = $fileentry['path'].'/'.$dirsource;
+ $dirdest = $fileentry['path'].'/'.$dirdest;
+ @rename($dirsource, $dirdest);
+ }
+ }
+ }
+ }
+ }
+
+ // Set new ref and current status
+ if (!$error)
+ {
+ $this->ref = $num;
+ $this->status = self::STATUS_VALIDATED;
+ }
+
+ if (!$error)
+ {
+ $this->db->commit();
+ return 1;
+ }
+ else
+ {
+ $this->db->rollback();
+ return -1;
+ }
+ }
+
+
+ /**
+ * Set draft status
+ *
+ * @param User $user Object user that modify
+ * @param int $notrigger 1=Does not execute triggers, 0=Execute triggers
+ * @return int <0 if KO, >0 if OK
+ */
+ public function setDraft($user, $notrigger = 0)
+ {
+ // Protection
+ if ($this->status <= self::STATUS_DRAFT)
+ {
+ return 0;
+ }
+
+ /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->stocktransfer->write))
+ || (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->stocktransfer->stocktransfer_advance->validate))))
+ {
+ $this->error='Permission denied';
+ return -1;
+ }*/
+
+ return $this->setStatusCommon($user, self::STATUS_DRAFT, $notrigger, 'STOCKTRANSFERLINE_UNVALIDATE');
+ }
+
+ /**
+ * Set cancel status
+ *
+ * @param User $user Object user that modify
+ * @param int $notrigger 1=Does not execute triggers, 0=Execute triggers
+ * @return int <0 if KO, 0=Nothing done, >0 if OK
+ */
+ public function cancel($user, $notrigger = 0)
+ {
+ // Protection
+ if ($this->status != self::STATUS_VALIDATED)
+ {
+ return 0;
+ }
+
+ /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->stocktransfer->write))
+ || (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->stocktransfer->stocktransfer_advance->validate))))
+ {
+ $this->error='Permission denied';
+ return -1;
+ }*/
+
+ return $this->setStatusCommon($user, self::STATUS_CANCELED, $notrigger, 'STOCKTRANSFERLINE_CLOSE');
+ }
+
+ /**
+ * Set back to validated status
+ *
+ * @param User $user Object user that modify
+ * @param int $notrigger 1=Does not execute triggers, 0=Execute triggers
+ * @return int <0 if KO, 0=Nothing done, >0 if OK
+ */
+ public function reopen($user, $notrigger = 0)
+ {
+ // Protection
+ if ($this->status != self::STATUS_CANCELED)
+ {
+ return 0;
+ }
+
+ /*if (! ((empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->stocktransfer->write))
+ || (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && ! empty($user->rights->stocktransfer->stocktransfer_advance->validate))))
+ {
+ $this->error='Permission denied';
+ return -1;
+ }*/
+
+ return $this->setStatusCommon($user, self::STATUS_VALIDATED, $notrigger, 'STOCKTRANSFERLINE_REOPEN');
+ }
+
+ /**
+ * Return a link to the object card (with optionaly the picto)
+ *
+ * @param int $withpicto Include picto in link (0=No picto, 1=Include picto into link, 2=Only picto)
+ * @param string $option On what the link point to ('nolink', ...)
+ * @param int $notooltip 1=Disable tooltip
+ * @param string $morecss Add more css on link
+ * @param int $save_lastsearch_value -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
+ * @return string String with URL
+ */
+ public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
+ {
+ global $conf, $langs, $hookmanager;
+
+ if (!empty($conf->dol_no_mouse_hover)) $notooltip = 1; // Force disable tooltips
+
+ $result = '';
+
+ $label = ''.$langs->trans("StockTransferLine").'';
+ $label .= '
';
+ $label .= ''.$langs->trans('Ref').': '.$this->ref;
+ if (isset($this->status)) {
+ $label .= '
'.$langs->trans("Status").": ".$this->getLibStatut(5);
+ }
+
+ $url = dol_buildpath('/stocktransfer/stocktransferline_card.php', 1).'?id='.$this->id;
+
+ if ($option != 'nolink')
+ {
+ // Add param to save lastsearch_values or not
+ $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
+ if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) $add_save_lastsearch_values = 1;
+ if ($add_save_lastsearch_values) $url .= '&save_lastsearch_values=1';
+ }
+
+ $linkclose = '';
+ if (empty($notooltip))
+ {
+ if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER))
+ {
+ $label = $langs->trans("ShowStockTransferLine");
+ $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
+ }
+ $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
+ $linkclose .= ' class="classfortooltip'.($morecss ? ' '.$morecss : '').'"';
+ }
+ else $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
+
+ $linkstart = '';
+ $linkend = '';
+
+ $result .= $linkstart;
+
+ if (empty($this->showphoto_on_popup)) {
+ if ($withpicto) $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
+ } else {
+ if ($withpicto) {
+ require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
+
+ list($class, $module) = explode('@', $this->picto);
+ $upload_dir = $conf->$module->multidir_output[$conf->entity]."/$class/".dol_sanitizeFileName($this->ref);
+ $filearray = dol_dir_list($upload_dir, "files");
+ $filename = $filearray[0]['name'];
+ if (!empty($filename)) {
+ $pospoint = strpos($filearray[0]['name'], '.');
+
+ $pathtophoto = $class.'/'.$this->ref.'/thumbs/'.substr($filename, 0, $pospoint).'_mini'.substr($filename, $pospoint);
+ if (empty($conf->global->{strtoupper($module.'_'.$class).'_FORMATLISTPHOTOSASUSERS'})) {
+ $result .= '';
+ }
+ else {
+ $result .= '';
+ }
+
+ $result .= '';
+ }
+ else {
+ $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
+ }
+ }
+ }
+
+ if ($withpicto != 2) $result .= $this->ref;
+
+ $result .= $linkend;
+ //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
+
+ global $action, $hookmanager;
+ $hookmanager->initHooks(array('stocktransferlinedao'));
+ $parameters = array('id'=>$this->id, 'getnomurl'=>$result);
+ $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
+ if ($reshook > 0) $result = $hookmanager->resPrint;
+ else $result .= $hookmanager->resPrint;
+
+ return $result;
+ }
+
+ /**
+ * Return label of the status
+ *
+ * @param int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto
+ * @return string Label of status
+ */
+ public function getLibStatut($mode = 0)
+ {
+ return $this->LibStatut($this->status, $mode);
+ }
+
+ // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
+ /**
+ * Return the status
+ *
+ * @param int $status Id status
+ * @param int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto
+ * @return string Label of status
+ */
+ public function LibStatut($status, $mode = 0)
+ {
+ // phpcs:enable
+ if (empty($this->labelStatus) || empty($this->labelStatusShort))
+ {
+ global $langs;
+ //$langs->load("stocktransfer@stocktransfer");
+ $this->labelStatus[self::STATUS_DRAFT] = $langs->trans('Draft');
+ $this->labelStatus[self::STATUS_VALIDATED] = $langs->trans('Enabled');
+ $this->labelStatus[self::STATUS_CANCELED] = $langs->trans('Disabled');
+ $this->labelStatusShort[self::STATUS_DRAFT] = $langs->trans('Draft');
+ $this->labelStatusShort[self::STATUS_VALIDATED] = $langs->trans('Enabled');
+ $this->labelStatusShort[self::STATUS_CANCELED] = $langs->trans('Disabled');
+ }
+
+ $statusType = 'status'.$status;
+ //if ($status == self::STATUS_VALIDATED) $statusType = 'status1';
+ if ($status == self::STATUS_CANCELED) $statusType = 'status6';
+
+ return dolGetStatus($this->labelStatus[$status], $this->labelStatusShort[$status], '', $statusType, $mode);
+ }
+
+ /**
+ * Load the info information in the object
+ *
+ * @param int $id Id of object
+ * @return void
+ */
+ public function info($id)
+ {
+ $sql = 'SELECT rowid, date_creation as datec, tms as datem,';
+ $sql .= ' fk_user_creat, fk_user_modif';
+ $sql .= ' FROM '.MAIN_DB_PREFIX.$this->table_element.' as t';
+ $sql .= ' WHERE t.rowid = '.$id;
+ $result = $this->db->query($sql);
+ if ($result)
+ {
+ if ($this->db->num_rows($result))
+ {
+ $obj = $this->db->fetch_object($result);
+ $this->id = $obj->rowid;
+ if ($obj->fk_user_author)
+ {
+ $cuser = new User($this->db);
+ $cuser->fetch($obj->fk_user_author);
+ $this->user_creation = $cuser;
+ }
+
+ if ($obj->fk_user_valid)
+ {
+ $vuser = new User($this->db);
+ $vuser->fetch($obj->fk_user_valid);
+ $this->user_validation = $vuser;
+ }
+
+ if ($obj->fk_user_cloture)
+ {
+ $cluser = new User($this->db);
+ $cluser->fetch($obj->fk_user_cloture);
+ $this->user_cloture = $cluser;
+ }
+
+ $this->date_creation = $this->db->jdate($obj->datec);
+ $this->date_modification = $this->db->jdate($obj->datem);
+ $this->date_validation = $this->db->jdate($obj->datev);
+ }
+
+ $this->db->free($result);
+ }
+ else
+ {
+ dol_print_error($this->db);
+ }
+ }
+
+ /**
+ * Initialise object with example values
+ * Id must be 0 if object instance is a specimen
+ *
+ * @return void
+ */
+ public function initAsSpecimen()
+ {
+ $this->initAsSpecimenCommon();
+ }
+
+ /**
+ * Create an array of lines
+ *
+ * @return array|int array of lines if OK, <0 if KO
+ */
+ public function getLinesArray()
+ {
+ $this->lines = array();
+
+ $objectline = new StockTransferLineLine($this->db);
+ $result = $objectline->fetchAll('ASC', 'position', 0, 0, array('customsql'=>'fk_stocktransferline = '.$this->id));
+
+ if (is_numeric($result))
+ {
+ $this->error = $this->error;
+ $this->errors = $this->errors;
+ return $result;
+ }
+ else
+ {
+ $this->lines = $result;
+ return $this->lines;
+ }
+ }
+
+ /**
+ * Returns the reference to the following non used object depending on the active numbering module.
+ *
+ * @return string Object free reference
+ */
+ public function getNextNumRef()
+ {
+ global $langs, $conf;
+ $langs->load("stocks");
+
+ if (empty($conf->global->STOCKTRANSFER_STOCKTRANSFERLINE_ADDON)) {
+ $conf->global->STOCKTRANSFER_STOCKTRANSFERLINE_ADDON = 'mod_stocktransferline_standard';
+ }
+
+ if (!empty($conf->global->STOCKTRANSFER_STOCKTRANSFERLINE_ADDON))
+ {
+ $mybool = false;
+
+ $file = $conf->global->STOCKTRANSFER_STOCKTRANSFERLINE_ADDON.".php";
+ $classname = $conf->global->STOCKTRANSFER_STOCKTRANSFERLINE_ADDON;
+
+ // Include file with class
+ $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
+ foreach ($dirmodels as $reldir)
+ {
+ $dir = dol_buildpath($reldir."core/modules/stocktransfer/");
+
+ // Load file with numbering class (if found)
+ $mybool |= @include_once $dir.$file;
+ }
+
+ if ($mybool === false)
+ {
+ dol_print_error('', "Failed to include file ".$file);
+ return '';
+ }
+
+ if (class_exists($classname)) {
+ $obj = new $classname();
+ $numref = $obj->getNextValue($this);
+
+ if ($numref != '' && $numref != '-1')
+ {
+ return $numref;
+ }
+ else
+ {
+ $this->error = $obj->error;
+ //dol_print_error($this->db,get_class($this)."::getNextNumRef ".$obj->error);
+ return "";
+ }
+ } else {
+ print $langs->trans("Error")." ".$langs->trans("ClassNotFound").' '.$classname;
+ return "";
+ }
+ }
+ else
+ {
+ print $langs->trans("ErrorNumberingModuleNotSetup", $this->element);
+ return "";
+ }
+ }
+
+ /**
+ * Create a document onto disk according to template module.
+ *
+ * @param string $modele Force template to use ('' to not force)
+ * @param Translate $outputlangs objet lang a utiliser pour traduction
+ * @param int $hidedetails Hide details of lines
+ * @param int $hidedesc Hide description
+ * @param int $hideref Hide ref
+ * @param null|array $moreparams Array to provide more information
+ * @return int 0 if KO, 1 if OK
+ */
+ public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
+ {
+ global $conf, $langs;
+
+ $result = 0;
+ $includedocgeneration = 0;
+
+ $langs->load("stocks");
+
+ if (!dol_strlen($modele)) {
+ $modele = 'standard_stocktransferline';
+
+ if ($this->modelpdf) {
+ $modele = $this->modelpdf;
+ } elseif (!empty($conf->global->STOCKTRANSFERLINE_ADDON_PDF)) {
+ $modele = $conf->global->STOCKTRANSFERLINE_ADDON_PDF;
+ }
+ }
+
+ $modelpath = "core/modules/stocktransfer/doc/";
+
+ if ($includedocgeneration) {
+ $result = $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Action executed by scheduler
+ * CAN BE A CRON TASK. In such a case, parameters come from the schedule job setup field 'Parameters'
+ * Use public function doScheduledJob($param1, $param2, ...) to get parameters
+ *
+ * @return int 0 if OK, <>0 if KO (this function is used also by cron so only 0 is OK)
+ */
+ public function doScheduledJob()
+ {
+ global $conf, $langs;
+
+ //$conf->global->SYSLOG_FILE = 'DOL_DATA_ROOT/dolibarr_mydedicatedlofile.log';
+
+ $error = 0;
+ $this->output = '';
+ $this->error = '';
+
+ dol_syslog(__METHOD__, LOG_DEBUG);
+
+ $now = dol_now();
+
+ $this->db->begin();
+
+ // ...
+
+ $this->db->commit();
+
+ return $error;
+ }
+}
+
+/**
+ * Class StockTransferLineLine. You can also remove this and generate a CRUD class for lines objects.
+ */
+class StockTransferLineLine
+{
+ // To complete with content of an object StockTransferLineLine
+ // We should have a field rowid, fk_stocktransferline and position
+
+ /**
+ * @var int Does object support extrafields ? 0=No, 1=Yes
+ */
+ public $isextrafieldmanaged = 0;
+
+ /**
+ * Constructor
+ *
+ * @param DoliDb $db Database handler
+ */
+ public function __construct(DoliDB $db)
+ {
+ $this->db = $db;
+ }
+}
diff --git a/htdocs/product/stock/stocktransfer/lib/stocktransfer.lib.php b/htdocs/product/stock/stocktransfer/lib/stocktransfer.lib.php
new file mode 100644
index 00000000000..8460a6405bc
--- /dev/null
+++ b/htdocs/product/stock/stocktransfer/lib/stocktransfer.lib.php
@@ -0,0 +1,67 @@
+
+ *
+ * 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 .
+ */
+
+/**
+ * \file stocktransfer/lib/stocktransfer.lib.php
+ * \ingroup stocktransfer
+ * \brief Library files with common functions for StockTransfer
+ */
+
+/**
+ * Prepare admin pages header
+ *
+ * @return array
+ */
+function stocktransferAdminPrepareHead()
+{
+ global $langs, $conf;
+
+ $langs->load("stocks");
+
+ $h = 0;
+ $head = array();
+
+ $head[$h][0] = dol_buildpath("/admin/stocktransfer.php", 1);
+ $head[$h][1] = $langs->trans("Settings");
+ $head[$h][2] = 'settings';
+ $h++;
+
+ /*
+ $head[$h][0] = dol_buildpath("/stocktransfer/admin/myobject_extrafields.php", 1);
+ $head[$h][1] = $langs->trans("ExtraFields");
+ $head[$h][2] = 'myobject_extrafields';
+ $h++;
+ */
+
+ /*$head[$h][0] = dol_buildpath("/product/stock/stocktransfer/admin/about.php", 1);
+ $head[$h][1] = $langs->trans("About");
+ $head[$h][2] = 'about';
+ $h++;*/
+
+ // Show more tabs from modules
+ // Entries must be declared in modules descriptor with line
+ //$this->tabs = array(
+ // 'entity:+tabname:Title:@stocktransfer:/stocktransfer/mypage.php?id=__ID__'
+ //); // to add new tab
+ //$this->tabs = array(
+ // 'entity:-tabname:Title:@stocktransfer:/stocktransfer/mypage.php?id=__ID__'
+ //); // to remove a tab
+ complete_head_from_modules($conf, $langs, null, $head, $h, 'stocktransfer');
+
+ return $head;
+}
diff --git a/htdocs/product/stock/stocktransfer/lib/stocktransfer_stocktransfer.lib.php b/htdocs/product/stock/stocktransfer/lib/stocktransfer_stocktransfer.lib.php
new file mode 100644
index 00000000000..03999b712e6
--- /dev/null
+++ b/htdocs/product/stock/stocktransfer/lib/stocktransfer_stocktransfer.lib.php
@@ -0,0 +1,96 @@
+
+ *
+ * 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 .
+ */
+
+/**
+ * \file lib/stocktransfer_stocktransfer.lib.php
+ * \ingroup stocktransfer
+ * \brief Library files with common functions for StockTransfer
+ */
+
+/**
+ * Prepare array of tabs for StockTransfer
+ *
+ * @param StockTransfer $object StockTransfer
+ * @return array Array of tabs
+ */
+function stocktransferPrepareHead($object)
+{
+ global $db, $langs, $conf;
+
+ $langs->load("stocks");
+
+ $h = 0;
+ $head = array();
+
+ $head[$h][0] = dol_buildpath("/product/stock/stocktransfer/stocktransfer_card.php", 1).'?id='.$object->id;
+ $head[$h][1] = $langs->trans("Card");
+ $head[$h][2] = 'card';
+ $h++;
+
+ if (empty($conf->global->MAIN_DISABLE_CONTACTS_TAB))
+ {
+ $nbContact = count($object->liste_contact(-1, 'internal')) + count($object->liste_contact(-1, 'external'));
+ $head[$h][0] = dol_buildpath('/product/stock/stocktransfer/stocktransfer_contact.php', 1).'?id='.$object->id;
+ $head[$h][1] = $langs->trans('ContactsAddresses');
+ if ($nbContact > 0) $head[$h][1] .= ''.$nbContact.'';
+ $head[$h][2] = 'contact';
+ $h++;
+ }
+
+ if (isset($object->fields['note_public']) || isset($object->fields['note_private']))
+ {
+ $nbNote = 0;
+ if (!empty($object->note_private)) $nbNote++;
+ if (!empty($object->note_public)) $nbNote++;
+ $head[$h][0] = dol_buildpath('/product/stock/stocktransfer/stocktransfer_note.php', 1).'?id='.$object->id;
+ $head[$h][1] = $langs->trans('Notes');
+ if ($nbNote > 0) $head[$h][1] .= (empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER) ? ''.$nbNote.'' : '');
+ $head[$h][2] = 'note';
+ $h++;
+ }
+
+ require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
+ require_once DOL_DOCUMENT_ROOT.'/core/class/link.class.php';
+ $upload_dir = $conf->stocktransfer->dir_output."/stocktransfer/".dol_sanitizeFileName($object->ref);
+ $nbFiles = count(dol_dir_list($upload_dir, 'files', 0, '', '(\.meta|_preview.*\.png)$'));
+ $nbLinks = Link::count($db, $object->element, $object->id);
+ $head[$h][0] = dol_buildpath("/product/stock/stocktransfer/stocktransfer_document.php", 1).'?id='.$object->id;
+ $head[$h][1] = $langs->trans('Documents');
+ if (($nbFiles + $nbLinks) > 0) $head[$h][1] .= ''.($nbFiles + $nbLinks).'';
+ $head[$h][2] = 'document';
+ $h++;
+
+ $head[$h][0] = dol_buildpath("/product/stock/stocktransfer/stocktransfer_agenda.php", 1).'?id='.$object->id;
+ $head[$h][1] = $langs->trans("Events");
+ $head[$h][2] = 'agenda';
+ $h++;
+
+ // Show more tabs from modules
+ // Entries must be declared in modules descriptor with line
+ //$this->tabs = array(
+ // 'entity:+tabname:Title:@stocktransfer:/stocktransfer/mypage.php?id=__ID__'
+ //); // to add new tab
+ //$this->tabs = array(
+ // 'entity:-tabname:Title:@stocktransfer:/stocktransfer/mypage.php?id=__ID__'
+ //); // to remove a tab
+ complete_head_from_modules($conf, $langs, $object, $head, $h, 'stocktransfer@stocktransfer');
+
+ complete_head_from_modules($conf, $langs, $object, $head, $h, 'stocktransfer@stocktransfer', 'remove');
+
+ return $head;
+}
diff --git a/htdocs/product/stock/stocktransfer/stocktransfer_agenda.php b/htdocs/product/stock/stocktransfer/stocktransfer_agenda.php
new file mode 100644
index 00000000000..cc580d2ab4a
--- /dev/null
+++ b/htdocs/product/stock/stocktransfer/stocktransfer_agenda.php
@@ -0,0 +1,260 @@
+
+ * Copyright (C) 2021 Gauthier VERDOL
+ * Copyright (C) ---Put here your own copyright and developer email---
+ *
+ * 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 .
+ */
+
+/**
+ * \file stocktransfer_agenda.php
+ * \ingroup stocktransfer
+ * \brief Page of StockTransfer events
+ */
+
+// Load Dolibarr environment
+$res = 0;
+// Try main.inc.php into web root known defined into CONTEXT_DOCUMENT_ROOT (not always defined)
+if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) $res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
+// Try main.inc.php into web root detected using web root calculated from SCRIPT_FILENAME
+$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME']; $tmp2 = realpath(__FILE__); $i = strlen($tmp) - 1; $j = strlen($tmp2) - 1;
+while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) { $i--; $j--; }
+if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) $res = @include substr($tmp, 0, ($i + 1))."/main.inc.php";
+if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) $res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php";
+// Try main.inc.php using relative path
+if (!$res && file_exists("../main.inc.php")) $res = @include "../main.inc.php";
+if (!$res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php";
+if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php";
+if (!$res) die("Include of main fails");
+
+require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
+require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
+require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
+require_once DOL_DOCUMENT_ROOT.'/product/stock/stocktransfer/class/stocktransfer.class.php';
+require_once DOL_DOCUMENT_ROOT.'/product/stock/stocktransfer/lib/stocktransfer_stocktransfer.lib.php';
+
+
+// Load translation files required by the page
+$langs->loadLangs(array("stocks", "other"));
+
+// Get parameters
+$id = GETPOST('id', 'int');
+$ref = GETPOST('ref', 'alpha');
+$action = GETPOST('action', 'alpha');
+$cancel = GETPOST('cancel', 'aZ09');
+$backtopage = GETPOST('backtopage', 'alpha');
+
+if (GETPOST('actioncode', 'array'))
+{
+ $actioncode = GETPOST('actioncode', 'array', 3);
+ if (!count($actioncode)) $actioncode = '0';
+}
+else
+{
+ $actioncode = GETPOST("actioncode", "alpha", 3) ?GETPOST("actioncode", "alpha", 3) : (GETPOST("actioncode") == '0' ? '0' : (empty($conf->global->AGENDA_DEFAULT_FILTER_TYPE_FOR_OBJECT) ? '' : $conf->global->AGENDA_DEFAULT_FILTER_TYPE_FOR_OBJECT));
+}
+$search_agenda_label = GETPOST('search_agenda_label');
+
+$limit = GETPOST('limit', 'int') ?GETPOST('limit', 'int') : $conf->liste_limit;
+$sortfield = GETPOST("sortfield", 'alpha');
+$sortorder = GETPOST("sortorder", 'alpha');
+$page = GETPOSTISSET('pageplusone') ? (GETPOST('pageplusone') - 1) : GETPOST("page", 'int');
+if (empty($page) || $page == -1) { $page = 0; } // If $page is not defined, or '' or -1
+$offset = $limit * $page;
+$pageprev = $page - 1;
+$pagenext = $page + 1;
+if (!$sortfield) $sortfield = 'a.datep,a.id';
+if (!$sortorder) $sortorder = 'DESC,DESC';
+
+// Initialize technical objects
+$object = new StockTransfer($db);
+$extrafields = new ExtraFields($db);
+$diroutputmassaction = $conf->stocktransfer->dir_output.'/temp/massgeneration/'.$user->id;
+$hookmanager->initHooks(array('stocktransferagenda', 'globalcard')); // Note that conf->hooks_modules contains array
+// Fetch optionals attributes and labels
+$extrafields->fetch_name_optionals_label($object->table_element);
+
+// Load object
+include DOL_DOCUMENT_ROOT.'/core/actions_fetchobject.inc.php'; // Must be include, not include_once // Must be include, not include_once. Include fetch and fetch_thirdparty but not fetch_optionals
+if ($id > 0 || !empty($ref)) $upload_dir = $conf->stocktransfer->multidir_output[$object->entity]."/".$object->id;
+
+// Security check - Protection if external user
+//if ($user->socid > 0) accessforbidden();
+//if ($user->socid > 0) $socid = $user->socid;
+//$result = restrictedArea($user, 'stocktransfer', $object->id);
+
+$permissiontoadd = $user->rights->stocktransfer->stocktransfer->write; // Used by the include of actions_addupdatedelete.inc.php
+
+
+/*
+ * Actions
+ */
+
+$parameters = array('id'=>$id);
+$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))
+{
+ // Cancel
+ if (GETPOST('cancel', 'alpha') && !empty($backtopage))
+ {
+ header("Location: ".$backtopage);
+ exit;
+ }
+
+ // Purge search criteria
+ if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')) // All tests are required to be compatible with all browsers
+ {
+ $actioncode = '';
+ $search_agenda_label = '';
+ }
+}
+
+
+
+/*
+ * View
+ */
+
+$form = new Form($db);
+
+if ($object->id > 0)
+{
+ $title = $langs->trans("Agenda");
+ //if (! empty($conf->global->MAIN_HTML_TITLE) && preg_match('/thirdpartynameonly/',$conf->global->MAIN_HTML_TITLE) && $object->name) $title=$object->name." - ".$title;
+ $help_url = '';
+ llxHeader('', $title, $help_url);
+
+ if (!empty($conf->notification->enabled)) $langs->load("mails");
+ $head = stocktransferPrepareHead($object);
+
+
+ dol_fiche_head($head, 'agenda', $langs->trans("StockTransfer"), -1, $object->picto);
+
+ // Object card
+ // ------------------------------------------------------------
+ $linkback = ''.$langs->trans("BackToList").'';
+
+ $morehtmlref = '';
+ /*
+ // Ref customer
+ $morehtmlref.=$form->editfieldkey("RefCustomer", 'ref_client', $object->ref_client, $object, 0, 'string', '', 0, 1);
+ $morehtmlref.=$form->editfieldval("RefCustomer", 'ref_client', $object->ref_client, $object, 0, 'string', '', null, null, '', 1);
+ // Thirdparty
+ $morehtmlref.='
'.$langs->trans('ThirdParty') . ' : ' . (is_object($object->thirdparty) ? $object->thirdparty->getNomUrl(1) : '');
+ // Project
+ if (! empty($conf->projet->enabled))
+ {
+ $langs->load("projects");
+ $morehtmlref.='
'.$langs->trans('Project') . ' ';
+ if ($permissiontoadd)
+ {
+ if ($action != 'classify')
+ //$morehtmlref.='
' . img_edit($langs->transnoentitiesnoconv('SetProject')) . ' : ';
+ $morehtmlref.=' : ';
+ if ($action == 'classify') {
+ //$morehtmlref.=$form->form_project($_SERVER['PHP_SELF'] . '?id=' . $object->id, $object->socid, $object->fk_project, 'projectid', 0, 0, 1, 1);
+ $morehtmlref.='
';
+ } else {
+ $morehtmlref.=$form->form_project($_SERVER['PHP_SELF'] . '?id=' . $object->id, $object->socid, $object->fk_project, 'none', 0, 0, 0, 1);
+ }
+ } else {
+ if (! empty($object->fk_project)) {
+ $proj = new Project($db);
+ $proj->fetch($object->fk_project);
+ $morehtmlref .= ': '.$proj->getNomUrl();
+ } else {
+ $morehtmlref .= '';
+ }
+ }
+ }*/
+ $morehtmlref .= '
';
+
+
+ dol_banner_tab($object, 'ref', $linkback, 1, 'ref', 'ref', $morehtmlref);
+
+ print '';
+ print '
';
+
+ $object->info($object->id);
+ dol_print_object_info($object, 1);
+
+ print '
';
+
+ dol_fiche_end();
+
+
+
+ // Actions buttons
+
+ $objthirdparty = $object;
+ $objcon = new stdClass();
+
+ $out = '&origin='.$object->element.'&originid='.$object->id;
+ $permok = $user->rights->agenda->myactions->create;
+ if ((!empty($objthirdparty->id) || !empty($objcon->id)) && $permok)
+ {
+ //$out.='trans("AddAnAction"),'filenew');
+ //$out.="";
+ }
+
+
+ print '';
+
+ if (!empty($conf->agenda->enabled) && (!empty($user->rights->agenda->myactions->read) || !empty($user->rights->agenda->allactions->read)))
+ {
+ $param = '&id='.$object->id.'&socid='.$socid;
+ if (!empty($contextpage) && $contextpage != $_SERVER["PHP_SELF"]) $param .= '&contextpage='.urlencode($contextpage);
+ if ($limit > 0 && $limit != $conf->liste_limit) $param .= '&limit='.urlencode($limit);
+
+
+ //print load_fiche_titre($langs->trans("ActionsOnStockTransfer"), '', '');
+
+ // List of all actions
+ $filters = array();
+ $filters['search_agenda_label'] = $search_agenda_label;
+
+ // TODO Replace this with same code than into list.php
+
+ show_actions_done($conf, $langs, $db, $object, null, 0, $actioncode, '', $filters, $sortfield, $sortorder, '');
+ }
+}
+
+// End of page
+llxFooter();
+$db->close();
diff --git a/htdocs/product/stock/stocktransfer/stocktransfer_card.php b/htdocs/product/stock/stocktransfer/stocktransfer_card.php
new file mode 100644
index 00000000000..abc653d5e75
--- /dev/null
+++ b/htdocs/product/stock/stocktransfer/stocktransfer_card.php
@@ -0,0 +1,1064 @@
+
+ * Copyright (C) 2021 Gauthier VERDOL
+ * Copyright (C) ---Put here your own copyright and developer email---
+ *
+ * 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 .
+ */
+
+/**
+ * \file stocktransfer_card.php
+ * \ingroup stocktransfer
+ * \brief Page to create/edit/view stocktransfer
+ */
+
+//if (! defined('NOREQUIREDB')) define('NOREQUIREDB','1'); // Do not create database handler $db
+//if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER','1'); // Do not load object $user
+//if (! defined('NOREQUIRESOC')) define('NOREQUIRESOC','1'); // Do not load object $mysoc
+//if (! defined('NOREQUIRETRAN')) define('NOREQUIRETRAN','1'); // Do not load object $langs
+//if (! defined('NOSCANGETFORINJECTION')) define('NOSCANGETFORINJECTION','1'); // Do not check injection attack on GET parameters
+//if (! defined('NOSCANPOSTFORINJECTION')) define('NOSCANPOSTFORINJECTION','1'); // Do not check injection attack on POST parameters
+//if (! defined('NOCSRFCHECK')) define('NOCSRFCHECK','1'); // Do not check CSRF attack (test on referer + on token if option MAIN_SECURITY_CSRF_WITH_TOKEN is on).
+//if (! defined('NOTOKENRENEWAL')) define('NOTOKENRENEWAL','1'); // Do not roll the Anti CSRF token (used if MAIN_SECURITY_CSRF_WITH_TOKEN is on)
+//if (! defined('NOSTYLECHECK')) define('NOSTYLECHECK','1'); // Do not check style html tag into posted data
+//if (! defined('NOREQUIREMENU')) define('NOREQUIREMENU','1'); // If there is no need to load and show top and left menu
+//if (! defined('NOREQUIREHTML')) define('NOREQUIREHTML','1'); // If we don't need to load the html.form.class.php
+//if (! defined('NOREQUIREAJAX')) define('NOREQUIREAJAX','1'); // Do not load ajax.lib.php library
+//if (! defined("NOLOGIN")) define("NOLOGIN",'1'); // If this page is public (can be called outside logged session). This include the NOIPCHECK too.
+//if (! defined('NOIPCHECK')) define('NOIPCHECK','1'); // Do not check IP defined into conf $dolibarr_main_restrict_ip
+//if (! defined("MAIN_LANG_DEFAULT")) define('MAIN_LANG_DEFAULT','auto'); // Force lang to a particular value
+//if (! defined("MAIN_AUTHENTICATION_MODE")) define('MAIN_AUTHENTICATION_MODE','aloginmodule'); // Force authentication handler
+//if (! defined("NOREDIRECTBYMAINTOLOGIN")) define('NOREDIRECTBYMAINTOLOGIN',1); // The main.inc.php does not make a redirect if not logged, instead show simple error message
+//if (! defined("FORCECSP")) define('FORCECSP','none'); // Disable all Content Security Policies
+
+
+// Load Dolibarr environment
+$res = 0;
+// Try main.inc.php into web root known defined into CONTEXT_DOCUMENT_ROOT (not always defined)
+if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) $res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
+// Try main.inc.php into web root detected using web root calculated from SCRIPT_FILENAME
+$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME']; $tmp2 = realpath(__FILE__); $i = strlen($tmp) - 1; $j = strlen($tmp2) - 1;
+while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) { $i--; $j--; }
+if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) $res = @include substr($tmp, 0, ($i + 1))."/main.inc.php";
+if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) $res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php";
+// Try main.inc.php using relative path
+if (!$res && file_exists("../main.inc.php")) $res = @include "../main.inc.php";
+if (!$res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php";
+if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php";
+if (!$res) die("Include of main fails");
+
+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/class/html.formprojet.class.php';
+require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
+require_once DOL_DOCUMENT_ROOT.'/product/class/html.formproduct.class.php';
+require_once DOL_DOCUMENT_ROOT.'/product/stock/stocktransfer/class/stocktransfer.class.php';
+require_once DOL_DOCUMENT_ROOT.'/product/stock/stocktransfer/class/stocktransferline.class.php';
+require_once DOL_DOCUMENT_ROOT.'/product/stock/stocktransfer/lib/stocktransfer_stocktransfer.lib.php';
+
+// Load translation files required by the page
+$langs->loadLangs(array("stocks", "other", "productbatch", "companies"));
+
+// Get parameters
+$id = GETPOST('id', 'int');
+$ref = GETPOST('ref', 'alpha');
+$action = GETPOST('action', 'aZ09');
+$confirm = GETPOST('confirm', 'alpha');
+$cancel = GETPOST('cancel', 'aZ09');
+$contextpage = GETPOST('contextpage', 'aZ') ?GETPOST('contextpage', 'aZ') : 'stocktransfercard'; // To manage different context of search
+$backtopage = GETPOST('backtopage', 'alpha');
+$backtopageforcancel = GETPOST('backtopageforcancel', 'alpha');
+$qty = GETPOST('qty', 'int');
+$fk_product = GETPOST('fk_product', 'int');
+$fk_warehouse_source = GETPOST('fk_warehouse_source', 'int');
+$fk_warehouse_destination = GETPOST('fk_warehouse_destination', 'int');
+$lineid = GETPOST('lineid', 'int');
+$label = GETPOST('label', 'alpha');
+$batch = GETPOST('batch', 'alpha');
+
+// Initialize technical objects
+$object = new StockTransfer($db);
+$extrafields = new ExtraFields($db);
+$diroutputmassaction = $conf->stocktransfer->dir_output.'/temp/massgeneration/'.$user->id;
+$hookmanager->initHooks(array('stocktransfercard', 'globalcard')); // Note that conf->hooks_modules contains array
+
+// Fetch optionals attributes and labels
+$extrafields->fetch_name_optionals_label($object->table_element);
+
+$search_array_options = $extrafields->getOptionalsFromPost($object->table_element, '', 'search_');
+
+// Initialize array of search criterias
+$search_all = trim(GETPOST("search_all", 'alpha'));
+$search = array();
+foreach ($object->fields as $key => $val)
+{
+ if (GETPOST('search_'.$key, 'alpha')) $search[$key] = GETPOST('search_'.$key, 'alpha');
+}
+
+if (empty($action) && empty($id) && empty($ref)) $action = 'view';
+
+// Load object
+include DOL_DOCUMENT_ROOT.'/core/actions_fetchobject.inc.php'; // Must be include, not include_once.
+
+
+$permissiontoread = $user->rights->stocktransfer->stocktransfer->read;
+$permissiontoadd = $user->rights->stocktransfer->stocktransfer->write; // Used by the include of actions_addupdatedelete.inc.php and actions_lineupdown.inc.php
+$permissiontodelete = $user->rights->stocktransfer->stocktransfer->delete || ($permissiontoadd && isset($object->status) && $object->status == $object::STATUS_DRAFT);
+$permissionnote = $user->rights->stocktransfer->stocktransfer->write; // Used by the include of actions_setnotes.inc.php
+$permissiondellink = $user->rights->stocktransfer->stocktransfer->write; // Used by the include of actions_dellink.inc.php
+$upload_dir = $conf->stocktransfer->multidir_output[isset($object->entity) ? $object->entity : 1];
+
+// Security check - Protection if external user
+//if ($user->socid > 0) accessforbidden();
+//if ($user->socid > 0) $socid = $user->socid;
+//$isdraft = (($object->statut == $object::STATUS_DRAFT) ? 1 : 0);
+//$result = restrictedArea($user, 'stocktransfer', $object->id, '', '', 'fk_soc', 'rowid', $isdraft);
+
+//if (!$permissiontoread) accessforbidden();
+
+
+/*
+ * Actions
+ */
+
+$form = new Form($db);
+$formfile = new FormFile($db);
+$formproject = new FormProjets($db);
+
+$parameters = array();
+$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))
+{
+ $error = 0;
+
+ $backurlforlist = dol_buildpath('/product/stock/stocktransfer/stocktransfer_list.php', 1);
+
+ 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_buildpath('/product/stock/stocktransfer/stocktransfer_card.php', 1).'?id='.($id > 0 ? $id : '__ID__');
+ }
+ }
+ $triggermodname = 'STOCKTRANSFER_STOCKTRANSFER_MODIFY'; // Name of trigger action code to execute when we modify record
+
+ // Actions cancel, add, update, update_extras, confirm_validate, confirm_delete, confirm_deleteline, confirm_clone, confirm_close, confirm_setdraft, confirm_reopen
+ include DOL_DOCUMENT_ROOT.'/core/actions_addupdatedelete.inc.php';
+
+ // Actions when linking object each other
+ include DOL_DOCUMENT_ROOT.'/core/actions_dellink.inc.php';
+
+ // Actions when printing a doc from card
+ include DOL_DOCUMENT_ROOT.'/core/actions_printing.inc.php';
+
+ // Action to move up and down lines of object
+ include DOL_DOCUMENT_ROOT.'/core/actions_lineupdown.inc.php';
+
+ // Action to build doc
+ include DOL_DOCUMENT_ROOT.'/core/actions_builddoc.inc.php';
+
+ if ($action == 'set_thirdparty' && $permissiontoadd)
+ {
+ $object->setValueFrom('fk_soc', GETPOST('fk_soc', 'int'), '', '', 'date', '', $user, 'STOCKTRANSFER_MODIFY');
+ }
+ if ($action == 'classin' && $permissiontoadd)
+ {
+ $object->setProject(GETPOST('projectid', 'int'));
+ }
+
+ if($action == 'addline' && $permissiontoadd) {
+
+ if($qty <= 0) {
+ $error++;
+ setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Qty")), null, 'errors');
+ $action = 'view';
+ }
+
+ if($fk_warehouse_source <= 0) {
+ $error++;
+ setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("WarehouseSource")), null, 'errors');
+ $action = 'view';
+ }
+
+ if($fk_warehouse_destination <= 0) {
+ $error++;
+ setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("WarehouseTarget")), null, 'errors');
+ $action = 'view';
+ }
+
+ $prod = new Product($db);
+ $prod->fetch($fk_product);
+ if ($prod->hasbatch())
+ {
+ if (empty($batch))
+ {
+ $error++;
+ $langs->load("errors");
+ setEventMessages($langs->trans("ErrorTryToMakeMoveOnProductRequiringBatchData", $prod->ref), null, 'errors');
+ }
+ } else {
+ if(!empty($batch)) {
+ $error++;
+ setEventMessages($langs->trans('StockTransferNoBatchForProduct', $prod->getNomUrl()), '', 'errors');
+ }
+ }
+
+ if(empty($error)) {
+ $line = new StockTransferLine($db);
+ $records = $line->fetchAll('', '', 0, 0, array('customsql'=>' fk_stocktransfer = '.$id.' AND fk_product = '.$fk_product.' AND fk_warehouse_source = '.$fk_warehouse_source.' AND fk_warehouse_destination = '.$fk_warehouse_destination.' AND ('.(empty($batch) ? 'batch = "" or batch IS NULL' : 'batch = "'.$batch.'"' ).')'));
+ if(!empty($records[key($records)])) $line = $records[key($records)];
+ $line->fk_stocktransfer = $id;
+ $line->qty += $qty;
+ $line->fk_warehouse_source = $fk_warehouse_source;
+ $line->fk_warehouse_destination = $fk_warehouse_destination;
+ $line->fk_product = $fk_product;
+ $line->batch = $batch;
+
+ $line->pmp = $prod->pmp;
+ if($line->id > 0) $line->update($user);
+ else {
+ $line->rang = count($object->lines) + 1;
+ $line->create($user);
+ }
+ $object->fetchLines();
+ }
+ } elseif($action === 'updateline' && $permissiontoadd) {
+
+ if($qty <= 0) {
+ $error++;
+ setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Qty")), null, 'errors');
+ $action = 'editline';
+ }
+
+ if($fk_warehouse_source <= 0) {
+ $error++;
+ setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("WarehouseSource")), null, 'errors');
+ $action = 'editline';
+ }
+
+ if($fk_warehouse_destination <= 0) {
+ $error++;
+ setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("WarehouseTarget")), null, 'errors');
+ $action = 'editline';
+ }
+
+ $prod = new Product($db);
+ $prod->fetch($fk_product);
+ if ($prod->hasbatch())
+ {
+ if (empty($batch))
+ {
+ $error++;
+ $langs->load("errors");
+ setEventMessages($langs->trans("ErrorTryToMakeMoveOnProductRequiringBatchData", $prod->ref), null, 'errors');
+ $action = 'editline';
+ }
+ } else {
+ if(!empty($batch)) {
+ $error++;
+ setEventMessages($langs->trans('StockTransferNoBatchForProduct', $prod->getNomUrl()), '', 'errors');
+ $action = 'editline';
+ }
+ }
+
+ if(empty($error)) {
+ $line = new StockTransferLine($db);
+ $line->fetch($lineid);
+ $line->qty = $qty;
+ $line->fk_warehouse_source = $fk_warehouse_source;
+ $line->fk_warehouse_destination = $fk_warehouse_destination;
+ $line->fk_product = $fk_product;
+ $line->batch = $batch;
+ $line->pmp = $prod->pmp;
+ $line->update($user);
+ }
+ }
+
+ // Décrémentation
+ if($action == 'confirm_destock' && $confirm == 'yes' && $object->status == $object::STATUS_VALIDATED) {
+ $lines = $object->getLinesArray();
+ if(!empty($lines)) {
+ $db->begin();
+ foreach ($lines as $line) {
+ $res = $line->doStockMovement($label, $line->fk_warehouse_source);
+ if($res <= 0) $error++;
+ }
+ if(empty($error)) $db->commit();
+ else $db->rollback();
+ }
+ if(empty($error)) {
+ $object->setStatut($object::STATUS_TRANSFERED, $id);
+ $object->status = $object::STATUS_TRANSFERED;
+ $object->date_reelle_depart = date('Y-m-d');
+ $object->update($user);
+ setEventMessage('StockStransferDecremented');
+ }
+ }
+
+ // Annulation décrémentation
+ if($action == 'confirm_destockcancel' && $confirm == 'yes' && $object->status == $object::STATUS_TRANSFERED) {
+ $lines = $object->getLinesArray();
+ if(!empty($lines)) {
+ $db->begin();
+ foreach ($lines as $line) {
+ $res = $line->doStockMovement($label, $line->fk_warehouse_source, 0);
+ if($res <= 0) $error++;
+ }
+ if(empty($error)) $db->commit();
+ else $db->rollback();
+ }
+ if(empty($error)) {
+ $object->setStatut($object::STATUS_VALIDATED, $id);
+ $object->status = $object::STATUS_VALIDATED;
+ $object->date_reelle_depart = null;
+ $object->update($user);
+ setEventMessage('StockStransferDecrementedCancel', 'warnings');
+ }
+ }
+
+ // Incrémentation
+ if($action == 'confirm_addstock' && $confirm == 'yes' && $object->status == $object::STATUS_TRANSFERED) {
+ $lines = $object->getLinesArray();
+ if(!empty($lines)) {
+ $db->begin();
+ foreach ($lines as $line) {
+ $res = $line->doStockMovement($label, $line->fk_warehouse_destination, 0);
+ if($res <= 0) $error++;
+ }
+ if(empty($error)) $db->commit();
+ else $db->rollback();
+ }
+ if(empty($error)) {
+ $object->setStatut($object::STATUS_CLOSED, $id);
+ $object->status = $object::STATUS_CLOSED;
+ $object->date_reelle_arrivee = date('Y-m-d');
+ $object->update($user);
+ setEventMessage('StockStransferIncrementedShort');
+ }
+ }
+
+ // Annulation incrémentation
+ if($action == 'confirm_addstockcancel' && $confirm == 'yes' && $object->status == $object::STATUS_CLOSED) {
+ $lines = $object->getLinesArray();
+ if(!empty($lines)) {
+ $db->begin();
+ foreach ($lines as $line) {
+ $res = $line->doStockMovement($label, $line->fk_warehouse_destination);
+ if($res <= 0) $error++;
+ }
+ if(empty($error)) $db->commit();
+ else $db->rollback();
+ }
+ if(empty($error)) {
+ $object->setStatut($object::STATUS_TRANSFERED, $id);
+ $object->status = $object::STATUS_TRANSFERED;
+ $object->date_reelle_arrivee = null;
+ $object->update($user);
+ setEventMessage('StockStransferIncrementedShortCancel', 'warnings');
+ }
+ }
+
+ // Actions to send emails
+ $triggersendname = 'STOCKTRANSFER_SENTBYMAIL';
+ $autocopy = 'MAIN_MAIL_AUTOCOPY_STOCKTRANSFER_TO';
+ $trackid = 'stocktransfer'.$object->id;
+ include DOL_DOCUMENT_ROOT.'/core/actions_sendmails.inc.php';
+}
+
+
+
+
+/*
+ * View
+ *
+ * Put here all code to build page
+ */
+
+$title = $langs->trans("StockTransfer");
+$help_url = '';
+llxHeader('', $title, $help_url);
+
+
+
+// Example : Adding jquery code
+print '';
+
+
+// Part to create
+if ($action == 'create')
+{
+ print load_fiche_titre($langs->trans("NewObject", $langs->transnoentitiesnoconv("StockTransfer")), '', 'object_'.$object->picto);
+
+ print '';
+
+ //dol_set_focus('input[name="ref"]');
+}
+
+// Part to edit record
+if (($id || $ref) && $action == 'edit')
+{
+ //if($object->status < 3) {
+ print load_fiche_titre($langs->trans("StockTransfer"), '', 'object_' . $object->picto);
+
+ print '';
+ //} else $action = 'view';
+}
+
+// Part to show record
+if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'create')))
+{
+ $res = $object->fetch_optionals();
+
+ $head = stocktransferPrepareHead($object);
+ dol_fiche_head($head, 'card', $langs->trans("StockTransfer"), -1, $object->picto);
+
+ $formconfirm = '';
+
+ // Confirmation to delete
+ if ($action == 'delete')
+ {
+ $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('DeleteStockTransfer'), $langs->trans('ConfirmDeleteObject'), 'confirm_delete', '', 0, 1);
+ }
+ // Confirmation to delete line
+ if ($action == 'deleteline')
+ {
+ $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id.'&lineid='.$lineid, $langs->trans('DeleteLine'), $langs->trans('ConfirmDeleteLine'), 'confirm_deleteline', '', 0, 1);
+ }
+ // Clone confirmation
+ if ($action == 'clone') {
+ // Create an array for form
+ $formquestion = array();
+ $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('ToClone'), $langs->trans('ConfirmCloneAsk', $object->ref), 'confirm_clone', $formquestion, 'yes', 1);
+ }
+ // Destock confirmation
+ elseif ($action == 'destock') {
+ // Create an array for form
+ $formquestion = array( 'text' => '',
+ array('type' => 'text', 'name' => 'label', 'label' => $langs->trans("Label"), 'value' => $langs->trans('ConfirmDestock', $object->ref), 'size'=>40));
+ $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('DestockAllProduct'), '', 'confirm_destock', $formquestion, 'yes', 1);
+ }
+ // Destock confirmation cancel
+ elseif ($action == 'destockcancel') {
+ // Create an array for form
+ $formquestion = array( 'text' => '',
+ array('type' => 'text', 'name' => 'label', 'label' => $langs->trans("Label"), 'value' => $langs->trans('ConfirmDestockCancel', $object->ref), 'size'=>40));
+ $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('DestockAllProductCancel'), '', 'confirm_destockcancel', $formquestion, 'yes', 1);
+ }
+ // Addstock confirmation
+ elseif ($action == 'addstock') {
+ // Create an array for form
+ $formquestion = array( 'text' => '',
+ array('type' => 'text', 'name' => 'label', 'label' => $langs->trans("Label").' :', 'value' => $langs->trans('ConfirmAddStock', $object->ref), 'size'=>40));
+ $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('AddStockAllProduct'), '', 'confirm_addstock', $formquestion, 'yes', 1);
+ }
+ // Addstock confirmation cancel
+ elseif ($action == 'addstockcancel') {
+ // Create an array for form
+ $formquestion = array( 'text' => '',
+ array('type' => 'text', 'name' => 'label', 'label' => $langs->trans("Label").' :', 'value' => $langs->trans('ConfirmAddStockCancel', $object->ref), 'size'=>40));
+ $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('AddStockAllProductCancel'), '', 'confirm_addstockcancel', $formquestion, 'yes', 1);
+ }
+
+ // Confirmation of action xxxx
+ if ($action == 'xxx')
+ {
+ $formquestion = array();
+ /*
+ $forcecombo=0;
+ if ($conf->browser->name == 'ie') $forcecombo = 1; // There is a bug in IE10 that make combo inside popup crazy
+ $formquestion = array(
+ // 'text' => $langs->trans("ConfirmClone"),
+ // array('type' => 'checkbox', 'name' => 'clone_content', 'label' => $langs->trans("CloneMainAttributes"), 'value' => 1),
+ // array('type' => 'checkbox', 'name' => 'update_prices', 'label' => $langs->trans("PuttingPricesUpToDate"), 'value' => 1),
+ // array('type' => 'other', 'name' => 'idwarehouse', 'label' => $langs->trans("SelectWarehouseForStockDecrease"), 'value' => $formproduct->selectWarehouses(GETPOST('idwarehouse')?GETPOST('idwarehouse'):'ifone', 'idwarehouse', '', 1, 0, 0, '', 0, $forcecombo))
+ );
+ */
+ $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('XXX'), $text, 'confirm_xxx', $formquestion, 0, 1, 220);
+ }
+
+
+ if ($action == 'valid' && $permissiontoadd) {
+ $nextref=$object->getNextNumRef();
+ $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('Validate'), $langs->transnoentities('ConfirmValidateStockTransfer', $nextref), 'confirm_validate', $formquestion, 0, 2);
+ }
+
+ // Call Hook formConfirm
+ $parameters = array('formConfirm' => $formconfirm, 'lineid' => $lineid);
+ $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;
+
+
+ // Object card
+ // ------------------------------------------------------------
+ $linkback = ''.$langs->trans("BackToList").'';
+
+ $morehtmlref = '';
+ /*
+ // Ref customer
+ $morehtmlref.=$form->editfieldkey("RefCustomer", 'ref_client', $object->ref_client, $object, 0, 'string', '', 0, 1);
+ $morehtmlref.=$form->editfieldval("RefCustomer", 'ref_client', $object->ref_client, $object, 0, 'string', '', null, null, '', 1);
+ // Thirdparty
+ $morehtmlref.='
'.$langs->trans('ThirdParty') . ' : ' . (is_object($object->thirdparty) ? $object->thirdparty->getNomUrl(1) : '');
+ // Project
+ if (! empty($conf->projet->enabled))
+ {
+ $langs->load("projects");
+ $morehtmlref.='
'.$langs->trans('Project') . ' ';
+ if ($permissiontoadd)
+ {
+ //if ($action != 'classify') $morehtmlref.='
' . img_edit($langs->transnoentitiesnoconv('SetProject')) . ' ';
+ $morehtmlref.=' : ';
+ if ($action == 'classify') {
+ //$morehtmlref.=$form->form_project($_SERVER['PHP_SELF'] . '?id=' . $object->id, $object->socid, $object->fk_project, 'projectid', 0, 0, 1, 1);
+ $morehtmlref.='
';
+ } else {
+ $morehtmlref.=$form->form_project($_SERVER['PHP_SELF'] . '?id=' . $object->id, $object->socid, $object->fk_project, 'none', 0, 0, 0, 1);
+ }
+ } else {
+ if (! empty($object->fk_project)) {
+ $proj = new Project($db);
+ $proj->fetch($object->fk_project);
+ $morehtmlref .= ': '.$proj->getNomUrl();
+ } else {
+ $morehtmlref .= '';
+ }
+ }
+ }*/
+ $morehtmlref .= '
';
+
+
+ dol_banner_tab($object, 'ref', $linkback, 1, 'ref', 'ref', $morehtmlref);
+
+
+ print '';
+ print '
';
+ print '
';
+ print '
'."\n";
+
+ // Common attributes
+ //$keyforbreak='fieldkeytoswitchonsecondcolumn'; // We change column just before this field
+ //unset($object->fields['fk_project']); // Hide field already shown in banner
+ //unset($object->fields['fk_soc']); // Hide field already shown in banner
+
+ include DOL_DOCUMENT_ROOT.'/core/tpl/commonfields_view.tpl.php';
+
+ echo '';
+ echo '| '.$langs->trans('EnhancedValue').' '.strtolower($langs->trans('TotalWoman'));
+ echo ' | '.price($object->getValorisationTotale(), 0, '', 1, -1, -1, $conf->currency).' | ';
+ echo '
';
+ // Other attributes. Fields from hook formObjectOptions and Extrafields.
+ include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_view.tpl.php';
+
+ print '
';
+ print '
';
+ print '
';
+
+ print '';
+
+ dol_fiche_end();
+
+
+ /*
+ * Lines
+ */
+
+ if (!empty($object->table_element_line))
+ {
+ // Show object lines
+ /*$result = $object->getLinesArray();
+
+ print ' \n";*/
+ }
+
+
+ $formproduct = new FormProduct($db);
+ print '';
+ print '
';
+ print '
';
+
+ // Buttons for actions
+
+ if ($action != 'presend' && $action != 'editline') {
+ print ''."\n";
+ }
+
+
+ // Select mail models is same action as presend
+ if (GETPOST('modelselected')) {
+ $action = 'presend';
+ }
+
+ if ($action != 'presend')
+ {
+ print '';
+ print '
'; // ancre
+
+ $includedocgeneration = 1;
+
+ // Documents
+ if ($includedocgeneration) {
+ $objref = dol_sanitizeFileName($object->ref);
+ $relativepath = $objref . '/' . $objref . '.pdf';
+ $filedir = $conf->stocktransfer->dir_output.'/'.$object->element.'/'.$objref;
+ $urlsource = $_SERVER["PHP_SELF"] . "?id=" . $object->id;
+ $genallowed = $user->rights->stocktransfer->stocktransfer->read; // If you can read, you can build the PDF to read content
+ $delallowed = $user->rights->stocktransfer->stocktransfer->write; // If you can create/edit, you can remove a file on card
+ print $formfile->showdocuments('stocktransfer:StockTransfer', $object->element.'/'.$objref, $filedir, $urlsource, $genallowed, $delallowed, $object->model_pdf, 1, 0, 0, 28, 0, '', '', '', $langs->defaultlang);
+ }
+
+ // Show links to link elements
+ $linktoelem = $form->showLinkToObjectBlock($object, null, array('stocktransfer'));
+ $somethingshown = $form->showLinkedObjectBlock($object, $linktoelem);
+
+
+ print '
';
+
+ $MAXEVENT = 10;
+
+ $morehtmlright = '
';
+ $morehtmlright .= $langs->trans("SeeAll");
+ $morehtmlright .= '';
+
+ // List of actions on element
+ include_once DOL_DOCUMENT_ROOT.'/core/class/html.formactions.class.php';
+ $formactions = new FormActions($db);
+ $somethingshown = $formactions->showactions($object, 'stocktransfer', 0, 1, '', $MAXEVENT, '', $morehtmlright);
+
+ print '
';
+ }
+
+ //Select mail models is same action as presend
+ if (GETPOST('modelselected')) $action = 'presend';
+
+ // Presend form
+ $modelmail = 'stocktransfer';
+ $defaulttopic = 'InformationMessage';
+ $diroutput = $conf->stocktransfer->dir_output;
+ $trackid = 'stocktransfer'.$object->id;
+
+ include DOL_DOCUMENT_ROOT.'/core/tpl/card_presend.tpl.php';
+}
+
+// End of page
+llxFooter();
+$db->close();
diff --git a/htdocs/product/stock/stocktransfer/stocktransfer_contact.php b/htdocs/product/stock/stocktransfer/stocktransfer_contact.php
new file mode 100644
index 00000000000..6dc808f206d
--- /dev/null
+++ b/htdocs/product/stock/stocktransfer/stocktransfer_contact.php
@@ -0,0 +1,225 @@
+
+ * Copyright (C) 2005-2016 Destailleur Laurent
+ * Copyright (C) 2005-2012 Regis Houssin
+ * Copyright (C) 2011-2015 Philippe Grand
+ * Copyright (C) 2021 Gauthier VERDOL
+ *
+ * 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 .
+ */
+
+/**
+ * \file htdocs/comm/propal/contact.php
+ * \ingroup propal
+ * \brief Tab to manage contacts/adresses of proposal
+ */
+
+if (!$res && file_exists("../main.inc.php")) $res = @include "../main.inc.php";
+if (!$res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php";
+if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php";
+if (!$res) die("Include of main fails");
+require_once DOL_DOCUMENT_ROOT.'/product/stock/stocktransfer/class/stocktransfer.class.php';
+require_once DOL_DOCUMENT_ROOT.'/product/stock/stocktransfer/lib/stocktransfer_stocktransfer.lib.php';
+require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
+require_once DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php';
+require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php';
+require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
+
+// Load translation files required by the page
+$langs->loadLangs(array('facture', 'orders', 'sendings', 'companies', 'stocks'));
+
+$id = GETPOST('id', 'int');
+$ref = GETPOST('ref', 'alpha');
+$lineid = GETPOST('lineid', 'int');
+$action = GETPOST('action', 'alpha');
+
+// Security check
+if ($user->socid) $socid = $user->socid;
+
+$result = restrictedArea($user, 'stocktransfer', $id, '', 'stocktransfer');
+
+$object = new StockTransfer($db);
+
+// Load object
+if ($id > 0 || !empty($ref))
+{
+ $ret = $object->fetch($id, $ref);
+ if ($ret == 0)
+ {
+ $langs->load("errors");
+ setEventMessages($langs->trans('ErrorRecordNotFound'), null, 'errors');
+ $error++;
+ }
+ elseif ($ret < 0)
+ {
+ setEventMessages($object->error, $object->errors, 'errors');
+ $error++;
+ }
+}
+if (!$error)
+{
+ $object->fetch_thirdparty();
+}
+else
+{
+ header('Location: '.dol_buildpath('/stocktransfer/stocktransfer_list.php', 1));
+ exit;
+}
+
+
+/*
+ * Add a new contact
+ */
+
+if ($action == 'addcontact' && $user->rights->stocktransfer->stocktransfer->write)
+{
+ if ($object->id > 0)
+ {
+ $contactid = (GETPOST('userid', 'int') ? GETPOST('userid', 'int') : GETPOST('contactid', 'int'));
+ $result = $object->add_contact($contactid, !empty($_POST["typecontact"]) ? $_POST["typecontact"] : $_POST["type"], $_POST["source"]);
+ }
+
+ if ($result >= 0)
+ {
+ header("Location: ".$_SERVER['PHP_SELF']."?id=".$object->id);
+ exit;
+ }
+ else
+ {
+ if ($object->error == 'DB_ERROR_RECORD_ALREADY_EXISTS')
+ {
+ $langs->load("errors");
+ setEventMessages($langs->trans("ErrorThisContactIsAlreadyDefinedAsThisType"), null, 'errors');
+ }
+ else
+ {
+ setEventMessages($object->error, $object->errors, 'errors');
+ }
+ }
+}
+
+// Toggle the status of a contact
+elseif ($action == 'swapstatut' && $user->rights->stocktransfer->stocktransfer->write)
+{
+ if ($object->id > 0)
+ {
+ $result = $object->swapContactStatus(GETPOST('ligne'));
+ }
+}
+
+// Deletes a contact
+elseif ($action == 'deletecontact' && $user->rights->stocktransfer->stocktransfer->write)
+{
+ $result = $object->delete_contact($lineid);
+
+ if ($result >= 0)
+ {
+ header("Location: ".$_SERVER['PHP_SELF']."?id=".$object->id);
+ exit;
+ }
+ else
+ {
+ dol_print_error($db);
+ }
+}
+/*
+elseif ($action == 'setaddress' && $user->rights->stocktransfer->stocktransfer->write)
+{
+ $result=$object->setDeliveryAddress($_POST['fk_address']);
+ if ($result < 0) dol_print_error($db,$object->error);
+}*/
+
+
+/*
+ * View
+ */
+
+llxHeader('', $langs->trans('ModuleStockTransferName'), 'EN:Commercial_Proposals|FR:Proposition_commerciale|ES:Presupuestos');
+
+$form = new Form($db);
+$formcompany = new FormCompany($db);
+$formother = new FormOther($db);
+
+if ($object->id > 0)
+{
+ $head = stocktransferPrepareHead($object);
+ dol_fiche_head($head, 'contact', $langs->trans("StockTransfer"), -1, 'stock');
+
+
+ // Proposal card
+
+ $linkback = ''.$langs->trans("BackToList").'';
+
+
+ $morehtmlref = '';
+ // Ref customer
+ $morehtmlref .= $form->editfieldkey("RefCustomer", 'ref_client', $object->ref_client, $object, 0, 'string', '', 0, 1);
+ $morehtmlref .= $form->editfieldval("RefCustomer", 'ref_client', $object->ref_client, $object, 0, 'string', '', null, null, '', 1);
+ // Thirdparty
+ if(!empty($object->thirdparty)) {
+ $morehtmlref .= '
' . $langs->trans('ThirdParty') . ' : ' . $object->thirdparty->getNomUrl(1, 'customer');
+ }
+ // Project
+ if (!empty($conf->projet->enabled))
+ {
+ $langs->load("projects");
+ $morehtmlref .= '
'.$langs->trans('Project').' ';
+ if ($user->rights->stocktransfer->stocktransfer->write)
+ {
+ if ($action != 'classify') {
+ //$morehtmlref.='
' . img_edit($langs->transnoentitiesnoconv('SetProject')) . '';
+ $morehtmlref .= ' : ';
+ }
+ if ($action == 'classify') {
+ //$morehtmlref.=$form->form_project($_SERVER['PHP_SELF'] . '?id=' . $object->id, $object->socid, $object->fk_project, 'projectid', 0, 0, 1, 1);
+ $morehtmlref .= '
';
+ } else {
+ $morehtmlref .= $form->form_project($_SERVER['PHP_SELF'].'?id='.$object->id, $object->socid, $object->fk_project, 'none', 0, 0, 0, 1);
+ }
+ } else {
+ if (!empty($object->fk_project)) {
+ $proj = new Project($db);
+ $proj->fetch($object->fk_project);
+ $morehtmlref .= '
';
+ $morehtmlref .= $proj->ref;
+ $morehtmlref .= '';
+ } else {
+ $morehtmlref .= '';
+ }
+ }
+ }
+ $morehtmlref .= '
';
+
+ dol_banner_tab($object, 'ref', $linkback, 1, 'ref', 'ref', $morehtmlref, '', 0, '', '', 1);
+
+ dol_fiche_end();
+
+ $user->rights->stocktransfer->write = $user->rights->stocktransfer->stocktransfer->write;
+ // Contacts lines (modules that overwrite templates must declare this into descriptor)
+ $dirtpls = array_merge($conf->modules_parts['tpl'], array('/core/tpl'));
+ foreach ($dirtpls as $reldir)
+ {
+ $res = @include dol_buildpath($reldir.'/contacts.tpl.php');
+ if ($res) break;
+ }
+}
+
+// End of page
+llxFooter();
+$db->close();
diff --git a/htdocs/product/stock/stocktransfer/stocktransfer_document.php b/htdocs/product/stock/stocktransfer/stocktransfer_document.php
new file mode 100644
index 00000000000..bbde110ef0e
--- /dev/null
+++ b/htdocs/product/stock/stocktransfer/stocktransfer_document.php
@@ -0,0 +1,211 @@
+
+ * Copyright (C) 2021 Gauthier VERDOL
+ * Copyright (C) ---Put here your own copyright and developer email---
+ *
+ * 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 .
+ */
+
+/**
+ * \file stocktransfer_document.php
+ * \ingroup stocktransfer
+ * \brief Tab for documents linked to StockTransfer
+ */
+
+// Load Dolibarr environment
+$res = 0;
+// Try main.inc.php into web root known defined into CONTEXT_DOCUMENT_ROOT (not always defined)
+if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) $res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
+// Try main.inc.php into web root detected using web root calculated from SCRIPT_FILENAME
+$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME']; $tmp2 = realpath(__FILE__); $i = strlen($tmp) - 1; $j = strlen($tmp2) - 1;
+while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) { $i--; $j--; }
+if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) $res = @include substr($tmp, 0, ($i + 1))."/main.inc.php";
+if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) $res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php";
+// Try main.inc.php using relative path
+if (!$res && file_exists("../main.inc.php")) $res = @include "../main.inc.php";
+if (!$res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php";
+if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php";
+if (!$res) die("Include of main fails");
+
+require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
+require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
+require_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
+require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php';
+require_once DOL_DOCUMENT_ROOT.'/product/stock/stocktransfer/class/stocktransfer.class.php';
+require_once DOL_DOCUMENT_ROOT.'/product/stock/stocktransfer/lib/stocktransfer_stocktransfer.lib.php';
+
+// Load translation files required by the page
+$langs->loadLangs(array("stocks", "companies", "other", "mails"));
+
+
+$action = GETPOST('action', 'aZ09');
+$confirm = GETPOST('confirm');
+$id = (GETPOST('socid', 'int') ? GETPOST('socid', 'int') : GETPOST('id', 'int'));
+$ref = GETPOST('ref', 'alpha');
+
+// Get parameters
+$limit = GETPOST('limit', 'int') ? GETPOST('limit', 'int') : $conf->liste_limit;
+$sortfield = GETPOST("sortfield", 'alpha');
+$sortorder = GETPOST("sortorder", 'alpha');
+$page = GETPOSTISSET('pageplusone') ? (GETPOST('pageplusone') - 1) : GETPOST("page", 'int');
+if (empty($page) || $page == -1) { $page = 0; } // If $page is not defined, or '' or -1
+$offset = $liste_limit * $page;
+$pageprev = $page - 1;
+$pagenext = $page + 1;
+if (!$sortorder) $sortorder = "ASC";
+if (!$sortfield) $sortfield = "name";
+//if (! $sortfield) $sortfield="position_name";
+
+// Initialize technical objects
+$object = new StockTransfer($db);
+$extrafields = new ExtraFields($db);
+$diroutputmassaction = $conf->stocktransfer->dir_output.'/temp/massgeneration/'.$user->id;
+$hookmanager->initHooks(array('stocktransferdocument', 'globalcard')); // Note that conf->hooks_modules contains array
+// Fetch optionals attributes and labels
+$extrafields->fetch_name_optionals_label($object->table_element);
+
+// Load object
+include DOL_DOCUMENT_ROOT.'/core/actions_fetchobject.inc.php'; // Must be include, not include_once // Must be include, not include_once. Include fetch and fetch_thirdparty but not fetch_optionals
+
+//if ($id > 0 || ! empty($ref)) $upload_dir = $conf->stocktransfer->multidir_output[$object->entity?$object->entity:$conf->entity] . "/stocktransfer/" . dol_sanitizeFileName($object->id);
+if ($id > 0 || !empty($ref)) $upload_dir = $conf->stocktransfer->multidir_output[$object->entity ? $object->entity : $conf->entity]."/stocktransfer/".dol_sanitizeFileName($object->ref);
+
+// Security check - Protection if external user
+//if ($user->socid > 0) accessforbidden();
+//if ($user->socid > 0) $socid = $user->socid;
+//$result = restrictedArea($user, 'stocktransfer', $object->id);
+
+$permissiontoadd = $user->rights->stocktransfer->stocktransfer->write; // Used by the include of actions_addupdatedelete.inc.php
+
+
+
+/*
+ * Actions
+ */
+
+include_once DOL_DOCUMENT_ROOT.'/core/actions_linkedfiles.inc.php';
+
+
+/*
+ * View
+ */
+
+$form = new Form($db);
+
+$title = $langs->trans("ModuleStockTransferName").' - '.$langs->trans("Files");
+$help_url = '';
+//$help_url='EN:Module_Third_Parties|FR:Module_Tiers|ES:Empresas';
+llxHeader('', $title, $help_url);
+
+if ($object->id)
+{
+ /*
+ * Show tabs
+ */
+ $head = stocktransferPrepareHead($object);
+
+ dol_fiche_head($head, 'document', $langs->trans("StockTransfer"), -1, $object->picto);
+
+
+ // Build file list
+ $filearray = dol_dir_list($upload_dir, "files", 0, '', '(\.meta|_preview.*\.png)$', $sortfield, (strtolower($sortorder) == 'desc' ?SORT_DESC:SORT_ASC), 1);
+ $totalsize = 0;
+ foreach ($filearray as $key => $file)
+ {
+ $totalsize += $file['size'];
+ }
+
+ // Object card
+ // ------------------------------------------------------------
+ $linkback = ''.$langs->trans("BackToList").'';
+
+ $morehtmlref = '';
+ /*
+ // Ref customer
+ $morehtmlref.=$form->editfieldkey("RefCustomer", 'ref_client', $object->ref_client, $object, 0, 'string', '', 0, 1);
+ $morehtmlref.=$form->editfieldval("RefCustomer", 'ref_client', $object->ref_client, $object, 0, 'string', '', null, null, '', 1);
+ // Thirdparty
+ $morehtmlref.='
'.$langs->trans('ThirdParty') . ' : ' . (is_object($object->thirdparty) ? $object->thirdparty->getNomUrl(1) : '');
+ // Project
+ if (! empty($conf->projet->enabled))
+ {
+ $langs->load("projects");
+ $morehtmlref.='
'.$langs->trans('Project') . ' ';
+ if ($permissiontoadd)
+ {
+ if ($action != 'classify')
+ //$morehtmlref.='
' . img_edit($langs->transnoentitiesnoconv('SetProject')) . ' : ';
+ $morehtmlref.=' : ';
+ if ($action == 'classify') {
+ //$morehtmlref.=$form->form_project($_SERVER['PHP_SELF'] . '?id=' . $object->id, $object->socid, $object->fk_project, 'projectid', 0, 0, 1, 1);
+ $morehtmlref.='
';
+ } else {
+ $morehtmlref.=$form->form_project($_SERVER['PHP_SELF'] . '?id=' . $object->id, $object->socid, $object->fk_project, 'none', 0, 0, 0, 1);
+ }
+ } else {
+ if (! empty($object->fk_project)) {
+ $proj = new Project($db);
+ $proj->fetch($object->fk_project);
+ $morehtmlref .= ': '.$proj->getNomUrl();
+ } else {
+ $morehtmlref .= '';
+ }
+ }
+ }*/
+ $morehtmlref .= '
';
+
+ dol_banner_tab($object, 'ref', $linkback, 1, 'ref', 'ref', $morehtmlref);
+
+ print '';
+
+ print '
';
+ print '
';
+
+ // Number of files
+ print '| '.$langs->trans("NbOfAttachedFiles").' | '.count($filearray).' |
';
+
+ // Total size
+ print '| '.$langs->trans("TotalSizeOfAttachedFiles").' | '.$totalsize.' '.$langs->trans("bytes").' |
';
+
+ print '
';
+
+ print '
';
+
+ dol_fiche_end();
+
+ $modulepart = 'stocktransfer';
+ //$permission = $user->rights->stocktransfer->stocktransfer->write;
+ $permission = 1;
+ //$permtoedit = $user->rights->stocktransfer->stocktransfer->write;
+ $permtoedit = 1;
+ $param = '&id='.$object->id;
+
+ //$relativepathwithnofile='stocktransfer/' . dol_sanitizeFileName($object->id).'/';
+ $relativepathwithnofile = 'stocktransfer/'.dol_sanitizeFileName($object->ref).'/';
+
+ include_once DOL_DOCUMENT_ROOT.'/core/tpl/document_actions_post_headers.tpl.php';
+}
+else
+{
+ accessforbidden('', 0, 1);
+}
+
+// End of page
+llxFooter();
+$db->close();
diff --git a/htdocs/product/stock/stocktransfer/stocktransfer_list.php b/htdocs/product/stock/stocktransfer/stocktransfer_list.php
new file mode 100644
index 00000000000..10532fe889c
--- /dev/null
+++ b/htdocs/product/stock/stocktransfer/stocktransfer_list.php
@@ -0,0 +1,606 @@
+
+ * Copyright (C) 2021 Gauthier VERDOL
+ * Copyright (C) ---Put here your own copyright and developer email---
+ *
+ * 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 .
+ */
+
+/**
+ * \file stocktransfer_list.php
+ * \ingroup stocktransfer
+ * \brief List page for stocktransfer
+ */
+
+//if (! defined('NOREQUIREDB')) define('NOREQUIREDB', '1'); // Do not create database handler $db
+//if (! defined('NOREQUIREUSER')) define('NOREQUIREUSER', '1'); // Do not load object $user
+//if (! defined('NOREQUIRESOC')) define('NOREQUIRESOC', '1'); // Do not load object $mysoc
+//if (! defined('NOREQUIRETRAN')) define('NOREQUIRETRAN', '1'); // Do not load object $langs
+//if (! defined('NOSCANGETFORINJECTION')) define('NOSCANGETFORINJECTION', '1'); // Do not check injection attack on GET parameters
+//if (! defined('NOSCANPOSTFORINJECTION')) define('NOSCANPOSTFORINJECTION', '1'); // Do not check injection attack on POST parameters
+//if (! defined('NOCSRFCHECK')) define('NOCSRFCHECK', '1'); // Do not check CSRF attack (test on referer + on token if option MAIN_SECURITY_CSRF_WITH_TOKEN is on).
+//if (! defined('NOTOKENRENEWAL')) define('NOTOKENRENEWAL', '1'); // Do not roll the Anti CSRF token (used if MAIN_SECURITY_CSRF_WITH_TOKEN is on)
+//if (! defined('NOSTYLECHECK')) define('NOSTYLECHECK', '1'); // Do not check style html tag into posted data
+//if (! defined('NOIPCHECK')) define('NOIPCHECK', '1'); // Do not check IP defined into conf $dolibarr_main_restrict_ip
+//if (! defined('NOREQUIREMENU')) define('NOREQUIREMENU', '1'); // If there is no need to load and show top and left menu
+//if (! defined('NOREQUIREHTML')) define('NOREQUIREHTML', '1'); // If we don't need to load the html.form.class.php
+//if (! defined('NOREQUIREAJAX')) define('NOREQUIREAJAX', '1'); // Do not load ajax.lib.php library
+//if (! defined("NOLOGIN")) define("NOLOGIN", '1'); // If this page is public (can be called outside logged session)
+//if (! defined("MAIN_LANG_DEFAULT")) define('MAIN_LANG_DEFAULT', 'auto'); // Force lang to a particular value
+//if (! defined("MAIN_AUTHENTICATION_MODE")) define('MAIN_AUTHENTICATION_MODE', 'aloginmodule'); // Force authentication handler
+//if (! defined("NOREDIRECTBYMAINTOLOGIN")) define('NOREDIRECTBYMAINTOLOGIN', '1'); // The main.inc.php does not make a redirect if not logged, instead show simple error message
+//if (! defined("XFRAMEOPTIONS_ALLOWALL")) define('XFRAMEOPTIONS_ALLOWALL', '1'); // Do not add the HTTP header 'X-Frame-Options: SAMEORIGIN' but 'X-Frame-Options: ALLOWALL'
+
+// Load Dolibarr environment
+$res = 0;
+// Try main.inc.php into web root known defined into CONTEXT_DOCUMENT_ROOT (not always defined)
+if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) $res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
+// Try main.inc.php into web root detected using web root calculated from SCRIPT_FILENAME
+$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME']; $tmp2 = realpath(__FILE__); $i = strlen($tmp) - 1; $j = strlen($tmp2) - 1;
+while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) { $i--; $j--; }
+if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) $res = @include substr($tmp, 0, ($i + 1))."/main.inc.php";
+if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) $res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php";
+// Try main.inc.php using relative path
+if (!$res && file_exists("../main.inc.php")) $res = @include "../main.inc.php";
+if (!$res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php";
+if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php";
+if (!$res) die("Include of main fails");
+
+require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php';
+require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
+require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
+
+// load stocktransfer libraries
+require_once __DIR__.'/class/stocktransfer.class.php';
+
+// for other modules
+//dol_include_once('/othermodule/class/otherobject.class.php');
+
+// Load translation files required by the page
+$langs->loadLangs(array("stocks", "other"));
+
+$action = GETPOST('action', 'aZ09') ?GETPOST('action', 'aZ09') : 'view'; // The action 'add', 'create', 'edit', 'update', 'view', ...
+$massaction = GETPOST('massaction', 'alpha'); // The bulk action (combo box choice into lists)
+$show_files = GETPOST('show_files', 'int'); // Show files area generated by bulk actions ?
+$confirm = GETPOST('confirm', 'alpha'); // Result of a confirmation
+$cancel = GETPOST('cancel', 'alpha'); // We click on a Cancel button
+$toselect = GETPOST('toselect', 'array'); // Array of ids of elements selected into a list
+$contextpage = GETPOST('contextpage', 'aZ') ? GETPOST('contextpage', 'aZ') : 'stocktransferlist'; // To manage different context of search
+$backtopage = GETPOST('backtopage', 'alpha'); // Go back to a dedicated page
+$optioncss = GETPOST('optioncss', 'aZ'); // Option for the css output (always '' except when 'print')
+
+$id = GETPOST('id', 'int');
+
+// Load variable for pagination
+$limit = GETPOST('limit', 'int') ? GETPOST('limit', 'int') : $conf->liste_limit;
+$sortfield = GETPOST('sortfield', 'alpha');
+$sortorder = GETPOST('sortorder', 'alpha');
+$page = GETPOSTISSET('pageplusone') ? (GETPOST('pageplusone') - 1) : GETPOST("page", 'int');
+if (empty($page) || $page < 0 || GETPOST('button_search', 'alpha') || GETPOST('button_removefilter', 'alpha')) { $page = 0; } // If $page is not defined, or '' or -1 or if we click on clear filters
+$offset = $limit * $page;
+$pageprev = $page - 1;
+$pagenext = $page + 1;
+
+// Initialize technical objects
+$object = new StockTransfer($db);
+$extrafields = new ExtraFields($db);
+$diroutputmassaction = $conf->stocktransfer->dir_output.'/temp/massgeneration/'.$user->id;
+$hookmanager->initHooks(array('stocktransferlist')); // Note that conf->hooks_modules contains array
+
+// Fetch optionals attributes and labels
+$extrafields->fetch_name_optionals_label($object->table_element);
+//$extrafields->fetch_name_optionals_label($object->table_element_line);
+
+$search_array_options = $extrafields->getOptionalsFromPost($object->table_element, '', 'search_');
+
+// Default sort order (if not yet defined by previous GETPOST)
+if (!$sortfield) $sortfield = "t.".key($object->fields); // Set here default search field. By default 1st field in definition.
+if (!$sortorder) $sortorder = "ASC";
+
+// Initialize array of search criterias
+$search_all = GETPOST('search_all', 'alphanohtml') ? trim(GETPOST('search_all', 'alphanohtml')) : trim(GETPOST('sall', 'alphanohtml'));
+$search = array();
+foreach ($object->fields as $key => $val)
+{
+ if (GETPOST('search_'.$key, 'alpha') !== '') $search[$key] = GETPOST('search_'.$key, 'alpha');
+}
+
+// List of fields to search into when doing a "search in all"
+$fieldstosearchall = array();
+foreach ($object->fields as $key => $val)
+{
+ if ($val['searchall']) $fieldstosearchall['t.'.$key] = $val['label'];
+}
+
+// Definition of fields for list
+$arrayfields = array();
+foreach ($object->fields as $key => $val)
+{
+ // If $val['visible']==0, then we never show the field
+ if (!empty($val['visible'])) $arrayfields['t.'.$key] = array('label'=>$val['label'], 'checked'=>(($val['visible'] < 0) ? 0 : 1), 'enabled'=>($val['enabled'] && ($val['visible'] != 3)), 'position'=>$val['position']);
+}
+// Extra fields
+if (is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label']) > 0)
+{
+ foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $val)
+ {
+ if (!empty($extrafields->attributes[$object->table_element]['list'][$key])) {
+ $arrayfields["ef.".$key] = array(
+ 'label'=>$extrafields->attributes[$object->table_element]['label'][$key],
+ 'checked'=>(($extrafields->attributes[$object->table_element]['list'][$key] < 0) ? 0 : 1),
+ 'position'=>$extrafields->attributes[$object->table_element]['pos'][$key],
+ 'enabled'=>(abs($extrafields->attributes[$object->table_element]['list'][$key]) != 3 && $extrafields->attributes[$object->table_element]['perms'][$key]),
+ 'langfile'=>$extrafields->attributes[$object->table_element]['langfile'][$key]
+ );
+ }
+ }
+}
+$object->fields = dol_sort_array($object->fields, 'position');
+$arrayfields = dol_sort_array($arrayfields, 'position');
+
+$permissiontoread = $user->rights->stocktransfer->stocktransfer->read;
+$permissiontoadd = $user->rights->stocktransfer->stocktransfer->write;
+$permissiontodelete = $user->rights->stocktransfer->stocktransfer->delete;
+
+// Security check
+if (empty($conf->stocktransfer->enabled)) accessforbidden('Module not enabled');
+$socid = 0;
+if ($user->socid > 0) // Protection if external user
+{
+ //$socid = $user->socid;
+ accessforbidden();
+}
+//$result = restrictedArea($user, 'stocktransfer', $id, '');
+//if (!$permissiontoread) accessforbidden();
+
+
+
+/*
+ * Actions
+ */
+
+if (GETPOST('cancel', 'alpha')) { $action = 'list'; $massaction = ''; }
+if (!GETPOST('confirmmassaction', 'alpha') && $massaction != 'presend' && $massaction != 'confirm_presend') { $massaction = ''; }
+
+$parameters = array();
+$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))
+{
+ // Selection of new fields
+ include DOL_DOCUMENT_ROOT.'/core/actions_changeselectedfields.inc.php';
+
+ // Purge search criteria
+ if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')) // All tests are required to be compatible with all browsers
+ {
+ foreach ($object->fields as $key => $val)
+ {
+ $search[$key] = '';
+ }
+ $toselect = '';
+ $search_array_options = array();
+ }
+ if (GETPOST('button_removefilter_x', 'alpha') || GETPOST('button_removefilter.x', 'alpha') || GETPOST('button_removefilter', 'alpha')
+ || GETPOST('button_search_x', 'alpha') || GETPOST('button_search.x', 'alpha') || GETPOST('button_search', 'alpha'))
+ {
+ $massaction = ''; // Protection to avoid mass action if we force a new search during a mass action confirmation
+ }
+
+ // Mass actions
+ $objectclass = 'StockTransfer';
+ $objectlabel = 'StockTransfer';
+ $uploaddir = $conf->stocktransfer->dir_output;
+ include DOL_DOCUMENT_ROOT.'/core/actions_massactions.inc.php';
+}
+
+
+
+/*
+ * View
+ */
+
+$form = new Form($db);
+
+$now = dol_now();
+
+//$help_url="EN:Module_StockTransfer|FR:Module_StockTransfer_FR|ES:Módulo_StockTransfer";
+$help_url = '';
+$title = $langs->trans('StockTransferList');
+
+
+// Build and execute select
+// --------------------------------------------------------------------
+$sql = 'SELECT ';
+foreach ($object->fields as $key => $val)
+{
+ $sql .= 't.'.$key.', ';
+}
+// Add fields from extrafields
+if (!empty($extrafields->attributes[$object->table_element]['label'])) {
+ foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $val) $sql .= ($extrafields->attributes[$object->table_element]['type'][$key] != 'separate' ? "ef.".$key.' as options_'.$key.', ' : '');
+}
+// Add fields from hooks
+$parameters = array();
+$reshook = $hookmanager->executeHooks('printFieldListSelect', $parameters, $object); // Note that $action and $object may have been modified by hook
+$sql .= preg_replace('/^,/', '', $hookmanager->resPrint);
+$sql = preg_replace('/,\s*$/', '', $sql);
+$sql .= " FROM ".MAIN_DB_PREFIX.$object->table_element." as t";
+if (is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label'])) $sql .= " LEFT JOIN ".MAIN_DB_PREFIX.$object->table_element."_extrafields as ef on (t.rowid = ef.fk_object)";
+if ($object->ismultientitymanaged == 1) $sql .= " WHERE t.entity IN (".getEntity($object->element).")";
+else $sql .= " WHERE 1 = 1";
+foreach ($search as $key => $val)
+{
+ if ($key == 'status' && $search[$key] == -1) continue;
+ $mode_search = (($object->isInt($object->fields[$key]) || $object->isFloat($object->fields[$key])) ? 1 : 0);
+ if (strpos($object->fields[$key]['type'], 'integer:') === 0) {
+ if ($search[$key] == '-1') $search[$key] = '';
+ $mode_search = 2;
+ }
+ if ($search[$key] != '') $sql .= natural_search($key, $search[$key], (($key == 'status') ? 2 : $mode_search));
+}
+if ($search_all) $sql .= natural_search(array_keys($fieldstosearchall), $search_all);
+//$sql.= dolSqlDateFilter("t.field", $search_xxxday, $search_xxxmonth, $search_xxxyear);
+// Add where from extra fields
+include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_sql.tpl.php';
+// Add where from hooks
+$parameters = array();
+$reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $object); // Note that $action and $object may have been modified by hook
+$sql .= $hookmanager->resPrint;
+
+/* If a group by is required
+$sql.= " GROUP BY ";
+foreach($object->fields as $key => $val)
+{
+ $sql.='t.'.$key.', ';
+}
+// Add fields from extrafields
+if (! empty($extrafields->attributes[$object->table_element]['label'])) {
+ foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $val) $sql.=($extrafields->attributes[$object->table_element]['type'][$key] != 'separate' ? "ef.".$key.', ' : '');
+}
+// Add where from hooks
+$parameters=array();
+$reshook=$hookmanager->executeHooks('printFieldListGroupBy',$parameters); // Note that $action and $object may have been modified by hook
+$sql.=$hookmanager->resPrint;
+$sql=preg_replace('/,\s*$/','', $sql);
+*/
+
+$sql .= $db->order($sortfield, $sortorder);
+
+// Count total nb of records
+$nbtotalofrecords = '';
+if (empty($conf->global->MAIN_DISABLE_FULL_SCANLIST))
+{
+ $resql = $db->query($sql);
+ $nbtotalofrecords = $db->num_rows($resql);
+ if (($page * $limit) > $nbtotalofrecords) // if total of record found is smaller than page * limit, goto and load page 0
+ {
+ $page = 0;
+ $offset = 0;
+ }
+}
+// if total of record found is smaller than limit, no need to do paging and to restart another select with limits set.
+if (is_numeric($nbtotalofrecords) && ($limit > $nbtotalofrecords || empty($limit)))
+{
+ $num = $nbtotalofrecords;
+}
+else
+{
+ if ($limit) $sql .= $db->plimit($limit + 1, $offset);
+
+ $resql = $db->query($sql);
+ if (!$resql)
+ {
+ dol_print_error($db);
+ exit;
+ }
+
+ $num = $db->num_rows($resql);
+}
+
+// Direct jump if only one record found
+if ($num == 1 && !empty($conf->global->MAIN_SEARCH_DIRECT_OPEN_IF_ONLY_ONE) && $search_all && !$page)
+{
+ $obj = $db->fetch_object($resql);
+ $id = $obj->rowid;
+ header("Location: ".dol_buildpath('/product/stock/stocktransfer/stocktransfer_card.php', 1).'?id='.$id);
+ exit;
+}
+
+
+// Output page
+// --------------------------------------------------------------------
+
+llxHeader('', $title, $help_url);
+
+// Example : Adding jquery code
+print '';
+
+$arrayofselected = is_array($toselect) ? $toselect : array();
+
+$param = '';
+if (!empty($contextpage) && $contextpage != $_SERVER["PHP_SELF"]) $param .= '&contextpage='.urlencode($contextpage);
+if ($limit > 0 && $limit != $conf->liste_limit) $param .= '&limit='.urlencode($limit);
+foreach ($search as $key => $val)
+{
+ if (is_array($search[$key]) && count($search[$key])) foreach ($search[$key] as $skey) $param .= '&search_'.$key.'[]='.urlencode($skey);
+ else $param .= '&search_'.$key.'='.urlencode($search[$key]);
+}
+if ($optioncss != '') $param .= '&optioncss='.urlencode($optioncss);
+// Add $param from extra fields
+include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_param.tpl.php';
+
+// List of mass actions available
+$arrayofmassactions = array(
+ //'validate'=>$langs->trans("Validate"),
+ //'generate_doc'=>$langs->trans("ReGeneratePDF"),
+ //'builddoc'=>$langs->trans("PDFMerge"),
+ //'presend'=>$langs->trans("SendByMail"),
+);
+if ($permissiontodelete) $arrayofmassactions['predelete'] = ''.$langs->trans("Delete");
+if (GETPOST('nomassaction', 'int') || in_array($massaction, array('presend', 'predelete'))) $arrayofmassactions = array();
+$massactionbutton = $form->selectMassAction('', $arrayofmassactions);
+
+print ''."\n";
+
+if (in_array('builddoc', $arrayofmassactions) && ($nbtotalofrecords === '' || $nbtotalofrecords))
+{
+ $hidegeneratedfilelistifempty = 1;
+ if ($massaction == 'builddoc' || $action == 'remove_file' || $show_files) $hidegeneratedfilelistifempty = 0;
+
+ require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php';
+ $formfile = new FormFile($db);
+
+ // Show list of available documents
+ $urlsource = $_SERVER['PHP_SELF'].'?sortfield='.$sortfield.'&sortorder='.$sortorder;
+ $urlsource .= str_replace('&', '&', $param);
+
+ $filedir = $diroutputmassaction;
+ $genallowed = $permissiontoread;
+ $delallowed = $permissiontoadd;
+
+ print $formfile->showdocuments('massfilesarea_stocktransfer', '', $filedir, $urlsource, 0, $delallowed, '', 1, 1, 0, 48, 1, $param, $title, '', '', '', null, $hidegeneratedfilelistifempty);
+}
+
+// End of page
+llxFooter();
+$db->close();
diff --git a/htdocs/product/stock/stocktransfer/stocktransfer_note.php b/htdocs/product/stock/stocktransfer/stocktransfer_note.php
new file mode 100644
index 00000000000..085914936b7
--- /dev/null
+++ b/htdocs/product/stock/stocktransfer/stocktransfer_note.php
@@ -0,0 +1,163 @@
+
+ * Copyright (C) 2021 Gauthier VERDOL
+ * Copyright (C) ---Put here your own copyright and developer email---
+ *
+ * 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 .
+ */
+
+/**
+ * \file stocktransfer_note.php
+ * \ingroup stocktransfer
+ * \brief Car with notes on StockTransfer
+ */
+
+// Load Dolibarr environment
+$res = 0;
+// Try main.inc.php into web root known defined into CONTEXT_DOCUMENT_ROOT (not always defined)
+if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) $res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php";
+// Try main.inc.php into web root detected using web root calculated from SCRIPT_FILENAME
+$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME']; $tmp2 = realpath(__FILE__); $i = strlen($tmp) - 1; $j = strlen($tmp2) - 1;
+while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) { $i--; $j--; }
+if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) $res = @include substr($tmp, 0, ($i + 1))."/main.inc.php";
+if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) $res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php";
+// Try main.inc.php using relative path
+if (!$res && file_exists("../main.inc.php")) $res = @include "../main.inc.php";
+if (!$res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php";
+if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php";
+if (!$res) die("Include of main fails");
+
+require_once DOL_DOCUMENT_ROOT.'/product/stock/stocktransfer/class/stocktransfer.class.php';
+require_once DOL_DOCUMENT_ROOT.'/product/stock/stocktransfer/lib/stocktransfer_stocktransfer.lib.php';
+
+// Load translation files required by the page
+$langs->loadLangs(array("stocks", "companies"));
+
+// Get parameters
+$id = GETPOST('id', 'int');
+$ref = GETPOST('ref', 'alpha');
+$action = GETPOST('action', 'alpha');
+$cancel = GETPOST('cancel', 'aZ09');
+$backtopage = GETPOST('backtopage', 'alpha');
+
+// Initialize technical objects
+$object = new StockTransfer($db);
+$extrafields = new ExtraFields($db);
+$diroutputmassaction = $conf->stocktransfer->dir_output.'/temp/massgeneration/'.$user->id;
+$hookmanager->initHooks(array('stocktransfernote', 'globalcard')); // Note that conf->hooks_modules contains array
+// Fetch optionals attributes and labels
+$extrafields->fetch_name_optionals_label($object->table_element);
+
+// Security check - Protection if external user
+//if ($user->socid > 0) accessforbidden();
+//if ($user->socid > 0) $socid = $user->socid;
+//$result = restrictedArea($user, 'stocktransfer', $id);
+
+// Load object
+include DOL_DOCUMENT_ROOT.'/core/actions_fetchobject.inc.php'; // Must be include, not include_once // Must be include, not include_once. Include fetch and fetch_thirdparty but not fetch_optionals
+if ($id > 0 || !empty($ref)) $upload_dir = $conf->stocktransfer->multidir_output[$object->entity]."/".$object->id;
+
+$permissionnote = $user->rights->stocktransfer->stocktransfer->write; // Used by the include of actions_setnotes.inc.php
+$permissiontoadd = $user->rights->stocktransfer->stocktransfer->write; // Used by the include of actions_addupdatedelete.inc.php
+
+
+
+/*
+ * Actions
+ */
+
+include DOL_DOCUMENT_ROOT.'/core/actions_setnotes.inc.php'; // Must be include, not include_once
+
+
+/*
+ * View
+ */
+
+$form = new Form($db);
+
+//$help_url='EN:Customers_Orders|FR:Commandes_Clients|ES:Pedidos de clientes';
+$help_url = '';
+llxHeader('', $langs->trans('ModuleStockTransferName'), $help_url);
+
+if ($id > 0 || !empty($ref))
+{
+ $object->fetch_thirdparty();
+
+ $head = stocktransferPrepareHead($object);
+
+ dol_fiche_head($head, 'note', $langs->trans("StockTransfer"), -1, $object->picto);
+
+ // Object card
+ // ------------------------------------------------------------
+ $linkback = ''.$langs->trans("BackToList").'';
+
+ $morehtmlref = '';
+ /*
+ // Ref customer
+ $morehtmlref.=$form->editfieldkey("RefCustomer", 'ref_client', $object->ref_client, $object, 0, 'string', '', 0, 1);
+ $morehtmlref.=$form->editfieldval("RefCustomer", 'ref_client', $object->ref_client, $object, 0, 'string', '', null, null, '', 1);
+ // Thirdparty
+ $morehtmlref.='
'.$langs->trans('ThirdParty') . ' : ' . (is_object($object->thirdparty) ? $object->thirdparty->getNomUrl(1) : '');
+ // Project
+ if (! empty($conf->projet->enabled))
+ {
+ $langs->load("projects");
+ $morehtmlref.='
'.$langs->trans('Project') . ' ';
+ if ($permissiontoadd)
+ {
+ if ($action != 'classify')
+ //$morehtmlref.='
' . img_edit($langs->transnoentitiesnoconv('SetProject')) . ' : ';
+ $morehtmlref.=' : ';
+ if ($action == 'classify') {
+ //$morehtmlref.=$form->form_project($_SERVER['PHP_SELF'] . '?id=' . $object->id, $object->socid, $object->fk_project, 'projectid', 0, 0, 1, 1);
+ $morehtmlref.='
';
+ } else {
+ $morehtmlref.=$form->form_project($_SERVER['PHP_SELF'] . '?id=' . $object->id, $object->socid, $object->fk_project, 'none', 0, 0, 0, 1);
+ }
+ } else {
+ if (! empty($object->fk_project)) {
+ $proj = new Project($db);
+ $proj->fetch($object->fk_project);
+ $morehtmlref .= ': '.$proj->getNomUrl();
+ } else {
+ $morehtmlref .= '';
+ }
+ }
+ }*/
+ $morehtmlref .= '
';
+
+
+ dol_banner_tab($object, 'ref', $linkback, 1, 'ref', 'ref', $morehtmlref);
+
+
+ print '';
+ print '
';
+
+
+ $cssclass = "titlefield";
+ include DOL_DOCUMENT_ROOT.'/core/tpl/notes.tpl.php';
+
+ print '
';
+
+ dol_fiche_end();
+}
+
+// End of page
+llxFooter();
+$db->close();