From 5ea9fc7467f22281c5525f18055ead800e85fb98 Mon Sep 17 00:00:00 2001
From: Laurent Destailleur <eldy@destailleur.fr>
Date: Mon, 12 Sep 2016 17:27:44 +0200
Subject: [PATCH] NEW Support extrafields on product lot.

---
 dev/skeletons/skeleton_card.php               |  27 ++-
 dev/skeletons/skeleton_class.class.php        |  10 +
 htdocs/core/class/commonobject.class.php      |   2 +-
 htdocs/core/lib/product.lib.php               |  72 +++++++
 htdocs/core/modules/modProductBatch.class.php |   2 +-
 htdocs/core/tpl/extrafields_view.tpl.php      |   1 +
 .../install/mysql/migration/4.0.0-5.0.0.sql   |  16 ++
 .../llx_product_lot_extrafields.key.sql       |  21 ++
 .../tables/llx_product_lot_extrafields.sql    |  26 +++
 htdocs/langs/en_US/productbatch.lang          |   1 +
 htdocs/product/admin/product_extrafields.php  |   7 +-
 .../product/admin/product_lot_extrafields.php | 123 +++++++++++
 .../product/stock/class/productlot.class.php  |  66 +++++-
 htdocs/product/stock/productlot_card.php      | 203 ++++++++++++------
 htdocs/product/stock/productlot_list.php      |  14 +-
 15 files changed, 493 insertions(+), 98 deletions(-)
 create mode 100644 htdocs/install/mysql/tables/llx_product_lot_extrafields.key.sql
 create mode 100644 htdocs/install/mysql/tables/llx_product_lot_extrafields.sql
 create mode 100644 htdocs/product/admin/product_lot_extrafields.php

diff --git a/dev/skeletons/skeleton_card.php b/dev/skeletons/skeleton_card.php
index 53cacc2b10f..9ebcdfeb8f7 100644
--- a/dev/skeletons/skeleton_card.php
+++ b/dev/skeletons/skeleton_card.php
@@ -59,25 +59,27 @@ $myparam	= GETPOST('myparam','alpha');
 $search_field1=GETPOST("search_field1");
 $search_field2=GETPOST("search_field2");
 
+if (empty($action) && empty($id) && empty($ref)) $action='list';
+
 // Protection if external user
 if ($user->societe_id > 0)
 {
 	//accessforbidden();
 }
+//$result = restrictedArea($user, 'mymodule', $id);
 
-if (empty($action) && empty($id) && empty($ref)) $action='list';
 
-// Load object if id or ref is provided as parameter
-$object=new Skeleton_Class($db);
-if (($id > 0 || ! empty($ref)) && $action != 'add')
-{
-	$result=$object->fetch($id,$ref);
-	if ($result < 0) dol_print_error($db);
-}
+$object = new Skeleton_Class($db);
+$extrafields = new ExtraFields($db);
+
+// fetch optionals attributes and labels
+$extralabels = $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
 
 // Initialize technical object to manage hooks of modules. Note that conf->hooks_modules contains array array
 $hookmanager->initHooks(array('skeleton'));
-$extrafields = new ExtraFields($db);
 
 
 
@@ -281,8 +283,13 @@ if (($id || $ref) && $action == 'edit')
 
 
 // Part to show record
-if ($id && (empty($action) || $action == 'view' || $action == 'delete'))
+if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'create')))
 {
+    $res = $object->fetch_optionals($object->id, $extralabels);
+
+	$head = commande_prepare_head($object);
+	dol_fiche_head($head, 'order', $langs->trans("CustomerOrder"), 0, 'order');
+		
 	print load_fiche_titre($langs->trans("MyModule"));
     
 	dol_fiche_head();
diff --git a/dev/skeletons/skeleton_class.class.php b/dev/skeletons/skeleton_class.class.php
index 3e4c2701896..d4772489f7b 100644
--- a/dev/skeletons/skeleton_class.class.php
+++ b/dev/skeletons/skeleton_class.class.php
@@ -181,6 +181,16 @@ class Skeleton_Class extends CommonObject
 				$this->prop2 = $obj->field2;
 				//...
 			}
+			
+			// Retrieve all extrafields for invoice
+			// fetch optionals attributes and labels
+			require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
+			$extrafields=new ExtraFields($this->db);
+			$extralabels=$extrafields->fetch_name_optionals_label($this->table_element,true);
+			$this->fetch_optionals($this->id,$extralabels);
+
+			// $this->fetch_lines();
+			
 			$this->db->free($resql);
 
 			if ($numrows) {
diff --git a/htdocs/core/class/commonobject.class.php b/htdocs/core/class/commonobject.class.php
index 07195cf0d50..9709402152f 100644
--- a/htdocs/core/class/commonobject.class.php
+++ b/htdocs/core/class/commonobject.class.php
@@ -1267,7 +1267,7 @@ abstract class CommonObject
      *		@param	int		$nodbprefix	Do not include DB prefix to forge table name
      *      @return int         		<0 if KO, >0 if OK
      */
-    function load_previous_next_ref($filter,$fieldid,$nodbprefix=0)
+    function load_previous_next_ref($filter, $fieldid, $nodbprefix=0)
     {
         global $user;
 
diff --git a/htdocs/core/lib/product.lib.php b/htdocs/core/lib/product.lib.php
index 096a1efbe99..1c546de39c3 100644
--- a/htdocs/core/lib/product.lib.php
+++ b/htdocs/core/lib/product.lib.php
@@ -153,6 +153,47 @@ function product_prepare_head($object)
 	return $head;
 }
 
+/**
+ * Prepare array with list of tabs
+ *
+ * @param   ProductLot	$object		Object related to tabs
+ * @return  array		     		Array of tabs to show
+ */
+function productlot_prepare_head($object)
+{
+    global $db, $langs, $conf, $user;
+    $langs->load("products");
+    $langs->load("productbatch");
+    
+    $h = 0;
+    $head = array();
+
+    $head[$h][0] = DOL_URL_ROOT."/product/stock/productlot_card.php?id=".$object->id;
+    $head[$h][1] = $langs->trans("Card");
+    $head[$h][2] = 'card';
+    $h++;
+
+    // Show more tabs from modules
+    // Entries must be declared in modules descriptor with line
+    // $this->tabs = array('entity:+tabname:Title:@mymodule:/mymodule/mypage.php?id=__ID__');   to add new tab
+    // $this->tabs = array('entity:-tabname);   												to remove a tab
+    complete_head_from_modules($conf,$langs,$object,$head,$h,'productlot');
+
+    complete_head_from_modules($conf,$langs,$object,$head,$h,'productlot', 'remove');
+
+    // Log
+    /*
+    $head[$h][0] = DOL_URL_ROOT.'/product/info.php?id='.$object->id;
+    $head[$h][1] = $langs->trans("Info");
+    $head[$h][2] = 'info';
+    $h++;
+    */
+    
+    return $head;
+}
+
+
+
 /**
 *  Return array head with list of tabs to view object informations.
 *
@@ -197,6 +238,37 @@ function product_admin_prepare_head()
 }
 
 
+
+/**
+ *  Return array head with list of tabs to view object informations.
+ *
+ *  @return	array   	        head array with tabs
+ */
+function product_lot_admin_prepare_head()
+{
+    global $langs, $conf, $user;
+
+    $h = 0;
+    $head = array();
+
+    // Show more tabs from modules
+    // Entries must be declared in modules descriptor with line
+    // $this->tabs = array('entity:+tabname:Title:@mymodule:/mymodule/mypage.php?id=__ID__');   to add new tab
+    // $this->tabs = array('entity:-tabname);   												to remove a tab
+    complete_head_from_modules($conf,$langs,null,$head,$h,'product_lot_admin');
+
+    $head[$h][0] = DOL_URL_ROOT.'/product/admin/product_lot_extrafields.php';
+    $head[$h][1] = $langs->trans("ExtraFields");
+    $head[$h][2] = 'attributes';
+    $h++;
+
+    complete_head_from_modules($conf,$langs,null,$head,$h,'product_lot_admin','remove');
+
+    return $head;
+}
+
+
+
 /**
  * Show stats for company
  *
diff --git a/htdocs/core/modules/modProductBatch.class.php b/htdocs/core/modules/modProductBatch.class.php
index af2c98cc79d..ca0e2829819 100644
--- a/htdocs/core/modules/modProductBatch.class.php
+++ b/htdocs/core/modules/modProductBatch.class.php
@@ -66,7 +66,7 @@ class modProductBatch extends DolibarrModules
 		$this->dirs = array();
 
 		// Config pages. Put here list of php page, stored into productdluo/admin directory, to use to setup module.
-		$this->config_page_url = array();
+		$this->config_page_url = array("product_lot_extrafields.php@product");
 
 		// Dependencies
 		$this->depends = array("modProduct","modStock","modExpedition","modFournisseur");		// List of modules id that must be enabled if this module is enabled. modExpedition is required to manage batch exit (by manual stock decrease on shipment), modSupplier to manage batch entry (after supplier order).
diff --git a/htdocs/core/tpl/extrafields_view.tpl.php b/htdocs/core/tpl/extrafields_view.tpl.php
index f4b817b583d..4cfc4a20afb 100644
--- a/htdocs/core/tpl/extrafields_view.tpl.php
+++ b/htdocs/core/tpl/extrafields_view.tpl.php
@@ -62,6 +62,7 @@ if (empty($reshook) && ! empty($extrafields->attribute_label))
 			if ($object->element=='invoice_supplier') $permok=$user->rights->fournisseur->facture->creer;
 			if ($object->element=='shipping') $permok=$user->rights->expedition->creer;
 			if ($object->element=='delivery') $permok=$user->rights->expedition->livraison->creer;
+			if ($object->element=='productlot') $permok=$user->rights->stock->creer;
 
 			if (($object->statut == 0 || $extrafields->attribute_alwayseditable[$key])
 				&& $permok && ($action != 'edit_extras' || GETPOST('attribute') != $key))
diff --git a/htdocs/install/mysql/migration/4.0.0-5.0.0.sql b/htdocs/install/mysql/migration/4.0.0-5.0.0.sql
index b236ba702af..a0c74af9482 100644
--- a/htdocs/install/mysql/migration/4.0.0-5.0.0.sql
+++ b/htdocs/install/mysql/migration/4.0.0-5.0.0.sql
@@ -21,6 +21,12 @@
 -- -- VPGSQL8.2 DELETE FROM llx_usergroup_user      WHERE fk_user      NOT IN (SELECT rowid from llx_user);
 -- -- VMYSQL4.1 DELETE FROM llx_usergroup_user      WHERE fk_usergroup NOT IN (SELECT rowid from llx_usergroup);
 
+
+-- VPGSQL8.2 ALTER TABLE llx_product_lot ALTER COLUMN entity SET DEFAULT 1;
+ALTER TABLE llx_product_lot MODIFY COLUMN entity integer DEFAULT 1;
+UPDATE llx_product_lot SET entity = 1 WHERE entity IS NULL;
+
+
 DELETE FROM llx_menu where module='expensereport';
 
 ALTER TABLE llx_user DROP COLUMN phenix_login;
@@ -79,6 +85,16 @@ create table llx_expensereport_extrafields
 
 ALTER TABLE llx_expensereport_extrafields ADD INDEX idx_expensereport_extrafields (fk_object);
 
+create table llx_product_lot_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_product_lot_extrafields ADD INDEX idx_product_lot_extrafields (fk_object);
+
 
 
 
diff --git a/htdocs/install/mysql/tables/llx_product_lot_extrafields.key.sql b/htdocs/install/mysql/tables/llx_product_lot_extrafields.key.sql
new file mode 100644
index 00000000000..aabf13c3ded
--- /dev/null
+++ b/htdocs/install/mysql/tables/llx_product_lot_extrafields.key.sql
@@ -0,0 +1,21 @@
+-- ===================================================================
+-- Copyright (C) 2016 Laurent Destailleur <eldy@users.sourceforge.net>
+--
+-- 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 <http://www.gnu.org/licenses/>.
+--
+-- ===================================================================
+
+
+ALTER TABLE llx_product_lot_extrafields ADD INDEX idx_product_lot_extrafields (fk_object);
+
diff --git a/htdocs/install/mysql/tables/llx_product_lot_extrafields.sql b/htdocs/install/mysql/tables/llx_product_lot_extrafields.sql
new file mode 100644
index 00000000000..7e18c8f68ec
--- /dev/null
+++ b/htdocs/install/mysql/tables/llx_product_lot_extrafields.sql
@@ -0,0 +1,26 @@
+-- ========================================================================
+-- Copyright (C) 2016 Laurent Destailleur <eldy@users.sourceforge.net>
+--
+-- 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 <http://www.gnu.org/licenses/>.
+--
+-- ========================================================================
+
+create table llx_product_lot_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/productbatch.lang b/htdocs/langs/en_US/productbatch.lang
index 00ccda00bf5..cdf1313509d 100644
--- a/htdocs/langs/en_US/productbatch.lang
+++ b/htdocs/langs/en_US/productbatch.lang
@@ -19,3 +19,4 @@ printQty=Qty: %d
 AddDispatchBatchLine=Add a line for Shelf Life dispatching
 WhenProductBatchModuleOnOptionAreForced=When module Lot/Serial is on, increase/decrease stock mode is forced to last choice and can't be edited. Other options can be defined as you want.
 ProductDoesNotUseBatchSerial=This product does not use lot/serial number
+ProductLotSetup=Setup of module lot/serial
diff --git a/htdocs/product/admin/product_extrafields.php b/htdocs/product/admin/product_extrafields.php
index d0e42276880..ca5944c421f 100644
--- a/htdocs/product/admin/product_extrafields.php
+++ b/htdocs/product/admin/product_extrafields.php
@@ -20,7 +20,7 @@
  */
 
 /**
- *      \file       htdocs/societe/admin/societe_extrafields.php
+ *      \file       htdocs/product/admin/product_extrafields.php
  *		\ingroup    societe
  *		\brief      Page to setup extra fields of third party
  */
@@ -73,8 +73,9 @@ else if (empty($conf->service->enabled))
 	$textobject = $langs->trans('Products');
 }
 
-$help_url='EN:Module Third Parties setup|FR:Paramétrage_du_module_Tiers';
-llxHeader('',$title);
+//$help_url='EN:Module Third Parties setup|FR:Paramétrage_du_module_Tiers';
+$help_url='';
+llxHeader('',$title,$help_url);
 
 
 $linkback='<a href="'.DOL_URL_ROOT.'/admin/modules.php">'.$langs->trans("BackToModuleList").'</a>';
diff --git a/htdocs/product/admin/product_lot_extrafields.php b/htdocs/product/admin/product_lot_extrafields.php
new file mode 100644
index 00000000000..28b4ee7db1d
--- /dev/null
+++ b/htdocs/product/admin/product_lot_extrafields.php
@@ -0,0 +1,123 @@
+<?php
+/* Copyright (C) 2001-2002	Rodolphe Quiedeville	<rodolphe@quiedeville.org>
+ * Copyright (C) 2003		Jean-Louis Bergamo		<jlb@j1b.org>
+ * Copyright (C) 2004-2011	Laurent Destailleur		<eldy@users.sourceforge.net>
+ * Copyright (C) 2012		Marcos García			<marcosgdf@gmail.com>
+ * Copyright (C) 2012		Regis Houssin			<regis.houssin@capnetworks.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ *      \file       htdocs/product/admin/product_lot_extrafields.php
+ *		\ingroup    societe
+ *		\brief      Page to setup extra fields of third party
+ */
+
+require '../../main.inc.php';
+require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
+require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
+
+$langs->load("companies");
+$langs->load("admin");
+$langs->load("products");
+$langs->load("productbatch");
+
+$extrafields = new ExtraFields($db);
+$form = new Form($db);
+
+// List of supported format
+$tmptype2label=ExtraFields::$type2label;
+$type2label=array('');
+foreach ($tmptype2label as $key => $val) $type2label[$key]=$langs->trans($val);
+
+$action=GETPOST('action', 'alpha');
+$attrname=GETPOST('attrname', 'alpha');
+$elementtype='product_lot'; //Must be the $element of the class that manage extrafield
+
+if (!$user->admin) accessforbidden();
+
+
+/*
+ * Actions
+ */
+
+require DOL_DOCUMENT_ROOT.'/core/actions_extrafields.inc.php';
+
+
+
+/*
+ * View
+ */
+
+$title = $langs->trans('ProductLotSetup');
+$textobject = $langs->trans("Batch");
+
+//$help_url='EN:Module Third Parties setup|FR:Paramétrage_du_module_Tiers';
+$help_url='';
+llxHeader('',$title,$help_url);
+
+
+$linkback='<a href="'.DOL_URL_ROOT.'/admin/modules.php">'.$langs->trans("BackToModuleList").'</a>';
+print load_fiche_titre($title,$linkback,'title_setup');
+
+
+$head = product_lot_admin_prepare_head();
+
+dol_fiche_head($head, 'attributes', $textobject, 0, 'stock');
+
+require DOL_DOCUMENT_ROOT.'/core/tpl/admin_extrafields_view.tpl.php';
+
+dol_fiche_end();
+
+
+// Buttons
+if ($action != 'create' && $action != 'edit')
+{
+    print '<div class="tabsAction">';
+    print "<a class=\"butAction\" href=\"".$_SERVER["PHP_SELF"]."?action=create\">".$langs->trans("NewAttribute")."</a>";
+    print "</div>";
+}
+
+
+/* ************************************************************************** */
+/*                                                                            */
+/* Creation of an optional field											  */
+/*                                                                            */
+/* ************************************************************************** */
+
+if ($action == 'create')
+{
+    print "<br>";
+    print load_fiche_titre($langs->trans('NewAttribute'));
+
+    require DOL_DOCUMENT_ROOT.'/core/tpl/admin_extrafields_add.tpl.php';
+}
+
+/* ************************************************************************** */
+/*                                                                            */
+/* Edition of an optional field                                               */
+/*                                                                            */
+/* ************************************************************************** */
+if ($action == 'edit' && ! empty($attrname))
+{
+    print "<br>";
+    print load_fiche_titre($langs->trans("FieldEdition", $attrname));
+
+    require DOL_DOCUMENT_ROOT.'/core/tpl/admin_extrafields_edit.tpl.php';
+}
+
+llxFooter();
+
+$db->close();
diff --git a/htdocs/product/stock/class/productlot.class.php b/htdocs/product/stock/class/productlot.class.php
index 475d7ef694a..dd0c94d94d7 100644
--- a/htdocs/product/stock/class/productlot.class.php
+++ b/htdocs/product/stock/class/productlot.class.php
@@ -43,7 +43,10 @@ class Productlot extends CommonObject
 	 * @var string Name of table without prefix where object is stored
 	 */
 	public $table_element = 'product_lot';
-
+	
+	public $isnolinkedbythird = 1;
+    public $ismultientitymanaged = 1;
+    
 	/**
 	 * @var ProductlotLine[] Lines
 	 */
@@ -190,13 +193,12 @@ class Productlot extends CommonObject
 	 *
 	 * @return int <0 if KO, 0 if not found, >0 if OK
 	 */
-	public function fetch($id = 0, $product_id = null, $batch = null)
+	public function fetch($id = 0, $product_id = 0, $batch = '')
 	{
 		dol_syslog(__METHOD__, LOG_DEBUG);
 
 		$sql = 'SELECT';
 		$sql .= ' t.rowid,';
-		
 		$sql .= " t.entity,";
 		$sql .= " t.fk_product,";
 		$sql .= " t.batch,";
@@ -207,11 +209,9 @@ class Productlot extends CommonObject
 		$sql .= " t.fk_user_creat,";
 		$sql .= " t.fk_user_modif,";
 		$sql .= " t.import_key";
-
-		
 		$sql .= ' FROM ' . MAIN_DB_PREFIX . $this->table_element . ' as t';
-		if ((null !== $product_id) && (null !== $batch)) {
-			$sql .= ' WHERE t.batch = ' . '\'' . $batch . '\' AND t.fk_product = ' . $product_id;
+		if ($product_id > 0 && $batch != '') {
+			$sql .= ' WHERE t.batch = ' . '\'' . $this->db->escape($batch) . '\' AND t.fk_product = ' . $product_id;
 		} else {
 			$sql .= ' WHERE t.rowid = ' . $id;
 		}
@@ -223,10 +223,13 @@ class Productlot extends CommonObject
 				$obj = $this->db->fetch_object($resql);
 
 				$this->id = $obj->rowid;
+				$this->ref = $obj->rowid;
+				//$this->ref = $obj->fk_product.'_'.$obj->batch;
+				
+				$this->batch = $obj->batch;
 				
 				$this->entity = $obj->entity;
 				$this->fk_product = $obj->fk_product;
-				$this->batch = $obj->batch;
 				$this->eatby = $this->db->jdate($obj->eatby);
 				$this->sellby = $this->db->jdate($obj->sellby);
 				$this->datec = $this->db->jdate($obj->datec);
@@ -235,7 +238,12 @@ class Productlot extends CommonObject
 				$this->fk_user_modif = $obj->fk_user_modif;
 				$this->import_key = $obj->import_key;
 
-				
+				// Retrieve all extrafields for invoice
+				// fetch optionals attributes and labels
+				require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
+				$extrafields=new ExtraFields($this->db);
+				$extralabels=$extrafields->fetch_name_optionals_label($this->table_element,true);
+				$this->fetch_optionals($this->id,$extralabels);				
 			}
 			$this->db->free($resql);
 
@@ -528,6 +536,46 @@ class Productlot extends CommonObject
 	}
 	
 	/**
+	 *  Return a link to the user card (with optionaly the picto)
+	 * 	Use this->id,this->lastname, this->firstname
+	 *
+	 *	@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
+     *  @param	integer	$notooltip			1=Disable tooltip
+     *  @param	int		$maxlen				Max length of visible user name
+     *  @param  string  $morecss            Add more css on link
+	 *	@return	string						String with URL
+	 */
+	function getNomUrl($withpicto=0, $option='', $notooltip=0, $maxlen=24, $morecss='')
+	{
+		global $langs, $conf, $db;
+        global $dolibarr_main_authentication, $dolibarr_main_demo;
+        global $menumanager;
+
+
+        $result = '';
+        $companylink = '';
+
+        $label = '<u>' . $langs->trans("Batch") . '</u>';
+        $label.= '<div width="100%">';
+        $label.= '<b>' . $langs->trans('Batch') . ':</b> ' . $this->batch;
+
+        $link = '<a href="'.DOL_URL_ROOT.'/product/stock/productlot_card.php?id='.$this->id.'"';
+        $link.= ($notooltip?'':' title="'.dol_escape_htmltag($label, 1).'" class="classfortooltip'.($morecss?' '.$morecss:'').'"');
+        $link.= '>';
+		$linkend='</a>';
+
+        if ($withpicto)
+        {
+            $result.=($link.img_object(($notooltip?'':$label), 'label', ($notooltip?'':'class="classfortooltip"')).$linkend);
+            if ($withpicto != 2) $result.=' ';
+		}
+		$result.= $link . $this->batch . $linkend;
+		return $result;
+	}
+	
+	
+	/** 
 	 * Initialise object with example values
 	 * Id must be 0 if object instance is a specimen
 	 *
diff --git a/htdocs/product/stock/productlot_card.php b/htdocs/product/stock/productlot_card.php
index 8cfeb861737..fccf90f26b5 100644
--- a/htdocs/product/stock/productlot_card.php
+++ b/htdocs/product/stock/productlot_card.php
@@ -32,18 +32,23 @@ if (! $res && file_exists("../../../../dolibarr/htdocs/main.inc.php")) $res=@inc
 if (! $res) die("Include of main fails");
 // Change this following line to use the correct relative path from htdocs
 include_once(DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php');
-dol_include_once('/stock/class/productlot.class.php');
+include_once(DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php');
+include_once(DOL_DOCUMENT_ROOT.'/product/class/product.class.php');
+require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
+dol_include_once('/product/stock/class/productlot.class.php');
 
 // Load traductions files requiredby by page
 $langs->load("stock");
 $langs->load("other");
+$langs->load("productbatch");
 
 // Get parameters
 $id			= GETPOST('id','int');
 $action		= GETPOST('action','alpha');
 $backtopage = GETPOST('backtopage');
-$myparam	= GETPOST('myparam','alpha');
-
+$batch  	= GETPOST('batch','alpha');
+$productid  = GETPOST('productid','int');
+$ref        = GETPOST('ref','alpha');       // ref is productid_batch
 
 $search_entity=GETPOST('search_entity','int');
 $search_fk_product=GETPOST('search_fk_product','int');
@@ -52,35 +57,48 @@ $search_fk_user_creat=GETPOST('search_fk_user_creat','int');
 $search_fk_user_modif=GETPOST('search_fk_user_modif','int');
 $search_import_key=GETPOST('search_import_key','int');
 
+if (empty($action) && empty($id) && empty($ref)) $action='list';
 
 
 // Protection if external user
 if ($user->societe_id > 0)
 {
-	//accessforbidden();
+    //accessforbidden();
 }
+//$result = restrictedArea($user, 'mymodule', $id);
 
-if (empty($action) && empty($id) && empty($ref)) $action='list';
 
-// Load object if id or ref is provided as parameter
-$object=new Productlot($db);
-if (($id > 0 || ! empty($ref)) && $action != 'add')
+$object = new ProductLot($db);
+$extrafields = new ExtraFields($db);
+
+// fetch optionals attributes and labels
+$extralabels = $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. Include fetch and fetch_thirdparty but not fetch_optionals
+if ($id || $ref)
 {
-	$result=$object->fetch($id,$ref);
-	if ($result < 0) dol_print_error($db);
+    if ($ref)
+    {
+        $tmp=explode('_',$ref);
+        $productid=$tmp[0];
+        $batch=$tmp[1];
+    }
+    $object->fetch($id, $productid, $batch);
 }
 
 // Initialize technical object to manage hooks of modules. Note that conf->hooks_modules contains array array
-$hookmanager->initHooks(array('productlot'));
-$extrafields = new ExtraFields($db);
+$hookmanager->initHooks(array('productlotcard','globalcard'));
+
 
+$permissionnote = $user->rights->stock->creer; 		// Used by the include of actions_setnotes.inc.php
+$permissiondellink = $user->rights->stock->creer; 	// Used by the include of actions_dellink.inc.php
+$permissionedit = $user->rights->stock->creer; 		// Used by the include of actions_lineupdown.inc.php
 
 
-/*******************************************************************
-* ACTIONS
-*
-* Put here all code to do according to value of "action" parameter
-********************************************************************/
+/*
+ * Actions
+ */
 
 $parameters=array();
 $reshook=$hookmanager->executeHooks('doActions',$parameters,$object,$action);    // Note that $action and $object may have been modified by some hooks
@@ -88,6 +106,33 @@ if ($reshook < 0) setEventMessages($hookmanager->error, $hookmanager->errors, 'e
 
 if (empty($reshook))
 {
+    if ($action == 'update_extras')
+    {
+        // Fill array 'array_options' with data from update form
+        $extralabels = $extrafields->fetch_name_optionals_label($object->table_element);
+        $ret = $extrafields->setOptionalsFromPost($extralabels, $object, GETPOST('attribute'));
+        if ($ret < 0) $error++;
+    
+        if (! $error)
+        {
+            // Actions on extra fields (by external module or standard code)
+            $hookmanager->initHooks(array('productlotdao'));
+            $parameters = array('id' => $object->id);
+            $reshook = $hookmanager->executeHooks('insertExtraFields', $parameters, $object, $action); // Note that $action and $object may have been modified by
+            // some hooks
+            if (empty($reshook)) {
+                $result = $object->insertExtraFields();
+                if ($result < 0) {
+                    $error++;
+                }
+            } else if ($reshook < 0)
+                $error++;
+        }
+    
+        if ($error)
+            $action = 'edit_extras';
+    }
+    
 	// Action to add record
 	if ($action == 'add')
 	{
@@ -102,12 +147,12 @@ if (empty($reshook))
 
 		/* object_prop_getpost_prop */
 		
-	$object->entity=GETPOST('entity','int');
-	$object->fk_product=GETPOST('fk_product','int');
-	$object->batch=GETPOST('batch','alpha');
-	$object->fk_user_creat=GETPOST('fk_user_creat','int');
-	$object->fk_user_modif=GETPOST('fk_user_modif','int');
-	$object->import_key=GETPOST('import_key','int');
+    	$object->entity=GETPOST('entity','int');
+    	$object->fk_product=GETPOST('fk_product','int');
+    	$object->batch=GETPOST('batch','alpha');
+    	$object->fk_user_creat=GETPOST('fk_user_creat','int');
+    	$object->fk_user_modif=GETPOST('fk_user_modif','int');
+    	$object->import_key=GETPOST('import_key','int');
 
 		
 
@@ -147,16 +192,13 @@ if (empty($reshook))
 	if ($action == 'update' && ! GETPOST('cancel'))
 	{
 		$error=0;
-
-		
-	$object->entity=GETPOST('entity','int');
-	$object->fk_product=GETPOST('fk_product','int');
-	$object->batch=GETPOST('batch','alpha');
-	$object->fk_user_creat=GETPOST('fk_user_creat','int');
-	$object->fk_user_modif=GETPOST('fk_user_modif','int');
-	$object->import_key=GETPOST('import_key','int');
-
 		
+    	$object->entity=GETPOST('entity','int');
+    	$object->fk_product=GETPOST('fk_product','int');
+    	$object->batch=GETPOST('batch','alpha');
+    	$object->fk_user_creat=GETPOST('fk_user_creat','int');
+    	$object->fk_user_modif=GETPOST('fk_user_modif','int');
+    	$object->import_key=GETPOST('import_key','int');
 
 		if (empty($object->ref))
 		{
@@ -207,13 +249,11 @@ if (empty($reshook))
 
 
 
-/***************************************************
-* VIEW
-*
-* Put here all code to build page
-****************************************************/
+/*
+ * View
+ */
 
-llxHeader('','MyPageName','');
+llxHeader('','ProductLot','');
 
 $form=new Form($db);
 
@@ -239,7 +279,7 @@ jQuery(document).ready(function() {
 // Part to create
 if ($action == 'create')
 {
-	print load_fiche_titre($langs->trans("NewMyModule"));
+	print load_fiche_titre($langs->trans("Batch"));
 
 	print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'">';
 	print '<input type="hidden" name="action" value="add">';
@@ -250,12 +290,12 @@ if ($action == 'create')
 	print '<table class="border centpercent">'."\n";
 	// print '<tr><td class="fieldrequired">'.$langs->trans("Label").'</td><td><input class="flat" type="text" size="36" name="label" value="'.$label.'"></td></tr>';
 	// 
-print '<tr><td class="fieldrequired">'.$langs->trans("Fieldentity").'</td><td><input class="flat" type="text" name="entity" value="'.GETPOST('entity').'"></td></tr>';
-print '<tr><td class="fieldrequired">'.$langs->trans("Fieldfk_product").'</td><td><input class="flat" type="text" name="fk_product" value="'.GETPOST('fk_product').'"></td></tr>';
-print '<tr><td class="fieldrequired">'.$langs->trans("Fieldbatch").'</td><td><input class="flat" type="text" name="batch" value="'.GETPOST('batch').'"></td></tr>';
-print '<tr><td class="fieldrequired">'.$langs->trans("Fieldfk_user_creat").'</td><td><input class="flat" type="text" name="fk_user_creat" value="'.GETPOST('fk_user_creat').'"></td></tr>';
-print '<tr><td class="fieldrequired">'.$langs->trans("Fieldfk_user_modif").'</td><td><input class="flat" type="text" name="fk_user_modif" value="'.GETPOST('fk_user_modif').'"></td></tr>';
-print '<tr><td class="fieldrequired">'.$langs->trans("Fieldimport_key").'</td><td><input class="flat" type="text" name="import_key" value="'.GETPOST('import_key').'"></td></tr>';
+    print '<tr><td class="fieldrequired">'.$langs->trans("Fieldentity").'</td><td><input class="flat" type="text" name="entity" value="'.GETPOST('entity').'"></td></tr>';
+    print '<tr><td class="fieldrequired">'.$langs->trans("Fieldfk_product").'</td><td><input class="flat" type="text" name="fk_product" value="'.GETPOST('fk_product').'"></td></tr>';
+    print '<tr><td class="fieldrequired">'.$langs->trans("Fieldbatch").'</td><td><input class="flat" type="text" name="batch" value="'.GETPOST('batch').'"></td></tr>';
+    print '<tr><td class="fieldrequired">'.$langs->trans("Fieldfk_user_creat").'</td><td><input class="flat" type="text" name="fk_user_creat" value="'.GETPOST('fk_user_creat').'"></td></tr>';
+    print '<tr><td class="fieldrequired">'.$langs->trans("Fieldfk_user_modif").'</td><td><input class="flat" type="text" name="fk_user_modif" value="'.GETPOST('fk_user_modif').'"></td></tr>';
+    print '<tr><td class="fieldrequired">'.$langs->trans("Fieldimport_key").'</td><td><input class="flat" type="text" name="import_key" value="'.GETPOST('import_key').'"></td></tr>';
 
 	print '</table>'."\n";
 
@@ -271,7 +311,7 @@ print '<tr><td class="fieldrequired">'.$langs->trans("Fieldimport_key").'</td><t
 // Part to edit record
 if (($id || $ref) && $action == 'edit')
 {
-	print load_fiche_titre($langs->trans("MyModule"));
+	print load_fiche_titre($langs->trans("Batch"));
     
 	print '<form method="POST" action="'.$_SERVER["PHP_SELF"].'">';
 	print '<input type="hidden" name="action" value="update">';
@@ -283,13 +323,20 @@ if (($id || $ref) && $action == 'edit')
 	print '<table class="border centpercent">'."\n";
 	// print '<tr><td class="fieldrequired">'.$langs->trans("Label").'</td><td><input class="flat" type="text" size="36" name="label" value="'.$label.'"></td></tr>';
 	// 
-print '<tr><td class="fieldrequired">'.$langs->trans("Fieldentity").'</td><td><input class="flat" type="text" name="entity" value="'.$object->entity.'"></td></tr>';
-print '<tr><td class="fieldrequired">'.$langs->trans("Fieldfk_product").'</td><td><input class="flat" type="text" name="fk_product" value="'.$object->fk_product.'"></td></tr>';
-print '<tr><td class="fieldrequired">'.$langs->trans("Fieldbatch").'</td><td><input class="flat" type="text" name="batch" value="'.$object->batch.'"></td></tr>';
-print '<tr><td class="fieldrequired">'.$langs->trans("Fieldfk_user_creat").'</td><td><input class="flat" type="text" name="fk_user_creat" value="'.$object->fk_user_creat.'"></td></tr>';
-print '<tr><td class="fieldrequired">'.$langs->trans("Fieldfk_user_modif").'</td><td><input class="flat" type="text" name="fk_user_modif" value="'.$object->fk_user_modif.'"></td></tr>';
-print '<tr><td class="fieldrequired">'.$langs->trans("Fieldimport_key").'</td><td><input class="flat" type="text" name="import_key" value="'.$object->import_key.'"></td></tr>';
-
+	print '<tr><td class="titlefield">'.$langs->trans("Batch").'</td><td>'.$object->batch.'</td></tr>';
+	print '<tr><td>'.$langs->trans("Product").'</td><td>';
+	$producttmp = new Product($db);
+	$producttmp->fetch($object->fk_product);
+	print $producttmp->getNomUrl(1, 'stock');
+	print '</td></tr>';
+	
+	print '<tr><td>'.$langs->trans("Eatby").'</td><td>'.$object->eatby.'</td></tr>';
+	print '<tr><td>'.$langs->trans("Sellby").'</td><td>'.$object->sellby.'</td></tr>';
+	
+	// Other attributes
+	$cols = 2;
+	include DOL_DOCUMENT_ROOT . '/core/tpl/extrafields_view.tpl.php';
+	
 	print '</table>';
 	
 	dol_fiche_end();
@@ -304,27 +351,44 @@ print '<tr><td class="fieldrequired">'.$langs->trans("Fieldimport_key").'</td><t
 
 
 // Part to show record
-if ($id && (empty($action) || $action == 'view' || $action == 'delete'))
+if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'create')))
 {
-	print load_fiche_titre($langs->trans("MyModule"));
+	$res = $object->fetch_optionals($object->id, $extralabels);
+	
+    print load_fiche_titre($langs->trans("Batch"));
     
-	dol_fiche_head();
-
+    $head = productlot_prepare_head($object);
+	dol_fiche_head($head, 'card', $langs->trans("Batch"), 0, 'stock');
+	
+	    
 	if ($action == 'delete') {
-		$formconfirm = $form->formconfirm($_SERVER["PHP_SELF"] . '?id=' . $object->id, $langs->trans('DeleteMyOjbect'), $langs->trans('ConfirmDeleteMyObject'), 'confirm_delete', '', 0, 1);
+		$formconfirm = $form->formconfirm($_SERVER["PHP_SELF"] . '?id=' . $object->id, $langs->trans('DeleteBatch'), $langs->trans('ConfirmDeleteBatch'), 'confirm_delete', '', 0, 1);
 		print $formconfirm;
 	}
 	
 	print '<table class="border centpercent">'."\n";
-	// print '<tr><td class="fieldrequired">'.$langs->trans("Label").'</td><td><input class="flat" type="text" size="36" name="label" value="'.$label.'"></td></tr>';
-	// 
-print '<tr><td class="fieldrequired">'.$langs->trans("Fieldentity").'</td><td>$object->entity</td></tr>';
-print '<tr><td class="fieldrequired">'.$langs->trans("Fieldfk_product").'</td><td>$object->fk_product</td></tr>';
-print '<tr><td class="fieldrequired">'.$langs->trans("Fieldbatch").'</td><td>$object->batch</td></tr>';
-print '<tr><td class="fieldrequired">'.$langs->trans("Fieldfk_user_creat").'</td><td>$object->fk_user_creat</td></tr>';
-print '<tr><td class="fieldrequired">'.$langs->trans("Fieldfk_user_modif").'</td><td>$object->fk_user_modif</td></tr>';
-print '<tr><td class="fieldrequired">'.$langs->trans("Fieldimport_key").'</td><td>$object->import_key</td></tr>';
-
+	
+	$linkback = '<a href="' . DOL_URL_ROOT . '/product/stock/productlot_list.php' . '">' . $langs->trans("BackToList") . '</a>';
+	
+	// Ref
+	print '<tr><td class="titlefield">' . $langs->trans('Batch') . '</td>';
+	print '<td colspan="3">';
+	print $form->showrefnav($object, 'id', $linkback, 1, 'rowid', 'batch');
+	print '</td>';
+	print '</tr>';
+	
+    print '<tr><td>'.$langs->trans("Product").'</td><td>';
+    $producttmp = new Product($db);
+    $producttmp->fetch($object->fk_product);
+    print $producttmp->getNomUrl(1, 'stock');
+    print '</td></tr>';
+    print '<tr><td>'.$langs->trans("Eatby").'</td><td>'.$object->eatby.'</td></tr>';
+    print '<tr><td>'.$langs->trans("Sellby").'</td><td>'.$object->sellby.'</td></tr>';
+
+    // Other attributes
+    $cols = 2;
+    include DOL_DOCUMENT_ROOT . '/core/tpl/extrafields_view.tpl.php';
+    
 	print '</table>';
 	
 	dol_fiche_end();
@@ -338,15 +402,16 @@ print '<tr><td class="fieldrequired">'.$langs->trans("Fieldimport_key").'</td><t
 
 	if (empty($reshook))
 	{
-		if ($user->rights->stock->write)
+/*TODO 		if ($user->rights->stock->lire)
 		{
 			print '<div class="inline-block divButAction"><a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&amp;action=edit">'.$langs->trans("Modify").'</a></div>'."\n";
 		}
 
-		if ($user->rights->stock->delete)
+		if ($user->rights->stock->supprimer)
 		{
 			print '<div class="inline-block divButAction"><a class="butActionDelete" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&amp;action=delete">'.$langs->trans('Delete').'</a></div>'."\n";
 		}
+*/
 	}
 	print '</div>'."\n";
 
diff --git a/htdocs/product/stock/productlot_list.php b/htdocs/product/stock/productlot_list.php
index 6c3cdc22f6f..4ead8e983fe 100644
--- a/htdocs/product/stock/productlot_list.php
+++ b/htdocs/product/stock/productlot_list.php
@@ -81,11 +81,11 @@ if ($user->societe_id > 0)
 }
 
 // Initialize technical object to manage hooks. Note that conf->hooks_modules contains array
-$hookmanager->initHooks(array('productbatchlist'));
+$hookmanager->initHooks(array('product_lotlist'));
 $extrafields = new ExtraFields($db);
 
 // fetch optionals attributes and labels
-$extralabels = $extrafields->fetch_name_optionals_label('productbatch');
+$extralabels = $extrafields->fetch_name_optionals_label('product_lot');
 $search_array_options=$extrafields->getOptionalsFromPost($extralabels,'','search_');
 
 // List of fields to search into when doing a "search in all"
@@ -224,7 +224,7 @@ $parameters=array();
 $reshook=$hookmanager->executeHooks('printFieldListSelect',$parameters);    // Note that $action and $object may have been modified by hook
 $sql.=$hookmanager->resPrint;
 $sql.= " FROM ".MAIN_DB_PREFIX."product_lot as t";
-if (is_array($extrafields->attribute_label) && count($extrafields->attribute_label)) $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."product_lot_extrafields as ef on (u.rowid = ef.fk_object)";
+if (is_array($extrafields->attribute_label) && count($extrafields->attribute_label)) $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."product_lot_extrafields as ef on (t.rowid = ef.fk_object)";
 $sql.= ", ".MAIN_DB_PREFIX."product as p";
 $sql.= " WHERE p.rowid = t.fk_product";
 //$sql.= " WHERE u.entity IN (".getEntity('mytable',1).")";
@@ -436,7 +436,8 @@ if ($resql)
     print '</td>';
 	print '</tr>'."\n";
         
-    
+	$productlot = new Productlot($db);
+	
 	$i=0;
 	$var=true;
 	$totalarray=array();
@@ -447,6 +448,9 @@ if ($resql)
         {
             $var = !$var;
             
+            $productlot->id = $obj->rowid;
+            $productlot->batch = $obj->batch;
+            
             // You can use here results
             print '<tr '.$bc[$var].'>';
             if (! empty($arrayfields['t.entity']['checked'])) 
@@ -456,7 +460,7 @@ if ($resql)
             }
             if (! empty($arrayfields['t.batch']['checked'])) 
             {
-                print '<td>'.$obj->batch.'</td>';
+                print '<td>'.$productlot->getNomUrl().'</td>';
     		    if (! $i) $totalarray['nbfield']++;
             }
             if (! empty($arrayfields['t.fk_product']['checked'])) 
-- 
GitLab