html.formproduct.class.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. <?php
  2. /* Copyright (C) 2008-2009 Laurent Destailleur <eldy@users.sourceforge.net>
  3. * Copyright (C) 2015-2017 Francis Appels <francis.appels@yahoo.com>
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. /**
  19. * \file htdocs/product/class/html.formproduct.class.php
  20. * \brief Fichier de la classe des fonctions predefinie de composants html
  21. */
  22. require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
  23. /**
  24. * Class with static methods for building HTML components related to products
  25. * Only components common to products and services must be here.
  26. */
  27. class FormProduct
  28. {
  29. /**
  30. * @var DoliDB Database handler.
  31. */
  32. public $db;
  33. /**
  34. * @var string Error code (or message)
  35. */
  36. public $error='';
  37. // Cache arrays
  38. var $cache_warehouses=array();
  39. var $cache_lot=array();
  40. /**
  41. * Constructor
  42. *
  43. * @param DoliDB $db Database handler
  44. */
  45. function __construct($db)
  46. {
  47. $this->db = $db;
  48. }
  49. /**
  50. * Load in cache array list of warehouses
  51. * If fk_product is not 0, we do not use cache
  52. *
  53. * @param int $fk_product Add quantity of stock in label for product with id fk_product. Nothing if 0.
  54. * @param string $batch Add quantity of batch stock in label for product with batch name batch, batch name precedes batch_id. Nothing if ''.
  55. * @param string $status warehouse status filter, following comma separated filter options can be used
  56. * 'warehouseopen' = select products from open warehouses,
  57. * 'warehouseclosed' = select products from closed warehouses,
  58. * 'warehouseinternal' = select products from warehouses for internal correct/transfer only
  59. * @param boolean $sumStock sum total stock of a warehouse, default true
  60. * @param array $exclude warehouses ids to exclude
  61. * @return int Nb of loaded lines, 0 if already loaded, <0 if KO
  62. */
  63. function loadWarehouses($fk_product=0, $batch = '', $status='', $sumStock = true, $exclude='')
  64. {
  65. global $conf, $langs;
  66. if (empty($fk_product) && count($this->cache_warehouses)) return 0; // Cache already loaded and we do not want a list with information specific to a product
  67. if (is_array($exclude)) $excludeGroups = implode("','",$exclude);
  68. $warehouseStatus = array();
  69. if (preg_match('/warehouseclosed/', $status))
  70. {
  71. $warehouseStatus[] = Entrepot::STATUS_CLOSED;
  72. }
  73. if (preg_match('/warehouseopen/', $status))
  74. {
  75. $warehouseStatus[] = Entrepot::STATUS_OPEN_ALL;
  76. }
  77. if (preg_match('/warehouseinternal/', $status))
  78. {
  79. $warehouseStatus[] = Entrepot::STATUS_OPEN_INTERNAL;
  80. }
  81. $sql = "SELECT e.rowid, e.ref as label, e.description, e.fk_parent";
  82. if (!empty($fk_product))
  83. {
  84. if (!empty($batch))
  85. {
  86. $sql.= ", pb.qty as stock";
  87. }
  88. else
  89. {
  90. $sql.= ", ps.reel as stock";
  91. }
  92. }
  93. else if ($sumStock)
  94. {
  95. $sql.= ", sum(ps.reel) as stock";
  96. }
  97. $sql.= " FROM ".MAIN_DB_PREFIX."entrepot as e";
  98. $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."product_stock as ps on ps.fk_entrepot = e.rowid";
  99. if (!empty($fk_product))
  100. {
  101. $sql.= " AND ps.fk_product = '".$fk_product."'";
  102. if (!empty($batch))
  103. {
  104. $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."product_batch as pb on pb.fk_product_stock = ps.rowid AND pb.batch = '".$batch."'";
  105. }
  106. }
  107. $sql.= " WHERE e.entity IN (".getEntity('stock').")";
  108. if (count($warehouseStatus))
  109. {
  110. $sql.= " AND e.statut IN (".$this->db->escape(implode(',',$warehouseStatus)).")";
  111. }
  112. else
  113. {
  114. $sql.= " AND e.statut = 1";
  115. }
  116. if(!empty($exclude)) $sql.= ' AND e.rowid NOT IN('.$this->db->escape(implode(',', $exclude)).')';
  117. if ($sumStock && empty($fk_product)) $sql.= " GROUP BY e.rowid, e.ref, e.description, e.fk_parent";
  118. $sql.= " ORDER BY e.ref";
  119. dol_syslog(get_class($this).'::loadWarehouses', LOG_DEBUG);
  120. $resql = $this->db->query($sql);
  121. if ($resql)
  122. {
  123. $num = $this->db->num_rows($resql);
  124. $i = 0;
  125. while ($i < $num)
  126. {
  127. $obj = $this->db->fetch_object($resql);
  128. if ($sumStock) $obj->stock = price2num($obj->stock,5);
  129. $this->cache_warehouses[$obj->rowid]['id'] =$obj->rowid;
  130. $this->cache_warehouses[$obj->rowid]['label']=$obj->label;
  131. $this->cache_warehouses[$obj->rowid]['parent_id']=$obj->fk_parent;
  132. $this->cache_warehouses[$obj->rowid]['description'] = $obj->description;
  133. $this->cache_warehouses[$obj->rowid]['stock'] = $obj->stock;
  134. $i++;
  135. }
  136. // Full label init
  137. foreach($this->cache_warehouses as $obj_rowid=>$tab) {
  138. $this->cache_warehouses[$obj_rowid]['full_label'] = $this->get_parent_path($tab);
  139. }
  140. return $num;
  141. }
  142. else
  143. {
  144. dol_print_error($this->db);
  145. return -1;
  146. }
  147. }
  148. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  149. /**
  150. * Return full path to current warehouse in $tab (recursive function)
  151. *
  152. * @param array $tab warehouse data in $this->cache_warehouses line
  153. * @param String $final_label full label with all parents, separated by ' >> ' (completed on each call)
  154. * @return String full label with all parents, separated by ' >> '
  155. */
  156. private function get_parent_path($tab, $final_label='')
  157. {
  158. //phpcs:enable
  159. if(empty($final_label)) $final_label = $tab['label'];
  160. if(empty($tab['parent_id'])) return $final_label;
  161. else {
  162. if(!empty($this->cache_warehouses[$tab['parent_id']])) {
  163. $final_label = $this->cache_warehouses[$tab['parent_id']]['label'].' >> '.$final_label;
  164. return $this->get_parent_path($this->cache_warehouses[$tab['parent_id']], $final_label);
  165. }
  166. }
  167. return $final_label;
  168. }
  169. /**
  170. * Return list of warehouses
  171. *
  172. * @param int $selected Id of preselected warehouse ('' for no value, 'ifone'=select value if one value otherwise no value)
  173. * @param string $htmlname Name of html select html
  174. * @param string $filterstatus warehouse status filter, following comma separated filter options can be used
  175. * 'warehouseopen' = select products from open warehouses,
  176. * 'warehouseclosed' = select products from closed warehouses,
  177. * 'warehouseinternal' = select products from warehouses for internal correct/transfer only
  178. * @param int $empty 1=Can be empty, 0 if not
  179. * @param int $disabled 1=Select is disabled
  180. * @param int $fk_product Add quantity of stock in label for product with id fk_product. Nothing if 0.
  181. * @param string $empty_label Empty label if needed (only if $empty=1)
  182. * @param int $showstock 1=Show stock count
  183. * @param int $forcecombo 1=Force combo iso ajax select2
  184. * @param array $events Events to add to select2
  185. * @param string $morecss Add more css classes to HTML select
  186. * @param array $exclude Warehouses ids to exclude
  187. * @param int $showfullpath 1=Show full path of name (parent ref into label), 0=Show only ref of current warehouse
  188. * @return string HTML select
  189. */
  190. function selectWarehouses($selected='',$htmlname='idwarehouse',$filterstatus='',$empty=0,$disabled=0,$fk_product=0,$empty_label='', $showstock=0, $forcecombo=0, $events=array(), $morecss='minwidth200', $exclude='', $showfullpath=1)
  191. {
  192. global $conf,$langs,$user;
  193. dol_syslog(get_class($this)."::selectWarehouses $selected, $htmlname, $filterstatus, $empty, $disabled, $fk_product, $empty_label, $showstock, $forcecombo, $morecss",LOG_DEBUG);
  194. $out='';
  195. if (empty($conf->global->ENTREPOT_EXTRA_STATUS)) $filterstatus = '';
  196. $this->loadWarehouses($fk_product, '', $filterstatus, true, $exclude);
  197. $nbofwarehouses=count($this->cache_warehouses);
  198. if ($conf->use_javascript_ajax && ! $forcecombo)
  199. {
  200. include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
  201. $comboenhancement = ajax_combobox($htmlname, $events);
  202. $out.= $comboenhancement;
  203. }
  204. $out.='<select class="flat'.($morecss?' '.$morecss:'').'"'.($disabled?' disabled':'').' id="'.$htmlname.'" name="'.($htmlname.($disabled?'_disabled':'')).'">';
  205. if ($empty) $out.='<option value="-1">'.($empty_label?$empty_label:'&nbsp;').'</option>';
  206. foreach($this->cache_warehouses as $id => $arraytypes)
  207. {
  208. $out.='<option value="'.$id.'"';
  209. if ($selected == $id || ($selected == 'ifone' && $nbofwarehouses == 1)) $out.=' selected';
  210. $out.='>';
  211. if ($showfullpath) $out.=$arraytypes['full_label'];
  212. else $out.=$arraytypes['label'];
  213. if (($fk_product || ($showstock > 0)) && ($arraytypes['stock'] != 0 || ($showstock > 0))) $out.=' ('.$langs->trans("Stock").':'.$arraytypes['stock'].')';
  214. $out.='</option>';
  215. }
  216. $out.='</select>';
  217. if ($disabled) $out.='<input type="hidden" name="'.$htmlname.'" value="'.(($selected>0)?$selected:'').'">';
  218. return $out;
  219. }
  220. /**
  221. * Display form to select warehouse
  222. *
  223. * @param string $page Page
  224. * @param int $selected Id of warehouse
  225. * @param string $htmlname Name of select html field
  226. * @param int $addempty 1=Add an empty value in list, 2=Add an empty value in list only if there is more than 2 entries.
  227. * @return void
  228. */
  229. function formSelectWarehouses($page, $selected='', $htmlname='warehouse_id', $addempty=0)
  230. {
  231. global $langs;
  232. if ($htmlname != "none") {
  233. print '<form method="POST" action="'.$page.'">';
  234. print '<input type="hidden" name="action" value="setwarehouse">';
  235. print '<input type="hidden" name="token" value="'.$_SESSION['newtoken'].'">';
  236. print '<table class="nobordernopadding" cellpadding="0" cellspacing="0">';
  237. print '<tr><td>';
  238. print $this->selectWarehouses($selected, $htmlname, '', $addempty);
  239. print '</td>';
  240. print '<td align="left"><input type="submit" class="button" value="'.$langs->trans("Modify").'"></td>';
  241. print '</tr></table></form>';
  242. } else {
  243. if ($selected) {
  244. require_once DOL_DOCUMENT_ROOT .'/product/stock/class/entrepot.class.php';
  245. $warehousestatic=new Entrepot($this->db);
  246. $warehousestatic->fetch($selected);
  247. print $warehousestatic->getNomUrl();
  248. } else {
  249. print "&nbsp;";
  250. }
  251. }
  252. }
  253. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
  254. /**
  255. * Output a combo box with list of units
  256. * pour l'instant on ne definit pas les unites dans la base
  257. *
  258. * @param string $name Name of HTML field
  259. * @param string $measuring_style Unit to show: weight, size, surface, volume
  260. * @param string $default Force unit
  261. * @param int $adddefault Add empty unit called "Default"
  262. * @return void
  263. */
  264. function select_measuring_units($name='measuring_units', $measuring_style='', $default='0', $adddefault=0)
  265. {
  266. //phpcs:enable
  267. print $this->load_measuring_units($name, $measuring_style, $default, $adddefault);
  268. }
  269. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.NotCamelCaps
  270. /**
  271. * Return a combo box with list of units
  272. * For the moment, units labels are defined in measuring_units_string
  273. *
  274. * @param string $name Name of HTML field
  275. * @param string $measuring_style Unit to show: weight, size, surface, volume
  276. * @param string $default Force unit
  277. * @param int $adddefault Add empty unit called "Default"
  278. * @return string
  279. */
  280. function load_measuring_units($name='measuring_units', $measuring_style='', $default='0', $adddefault=0)
  281. {
  282. //phpcs:enable
  283. global $langs,$conf,$mysoc;
  284. $langs->load("other");
  285. $return='';
  286. $measuring_units=array();
  287. if ($measuring_style == 'weight') $measuring_units=array(-6=>1,-3=>1,0=>1,3=>1,98=>1,99=>1);
  288. else if ($measuring_style == 'size') $measuring_units=array(-3=>1,-2=>1,-1=>1,0=>1,98=>1,99=>1);
  289. else if ($measuring_style == 'surface') $measuring_units=array(-6=>1,-4=>1,-2=>1,0=>1,98=>1,99=>1);
  290. else if ($measuring_style == 'volume') $measuring_units=array(-9=>1,-6=>1,-3=>1,0=>1,88=>1,89=>1,97=>1,99=>1,/* 98=>1 */); // Liter is not used as already available with dm3
  291. $return.= '<select class="flat" name="'.$name.'">';
  292. if ($adddefault) $return.= '<option value="0">'.$langs->trans("Default").'</option>';
  293. foreach ($measuring_units as $key => $value)
  294. {
  295. $return.= '<option value="'.$key.'"';
  296. if ($key == $default)
  297. {
  298. $return.= ' selected';
  299. }
  300. //$return.= '>'.$value.'</option>';
  301. $return.= '>'.measuring_units_string($key,$measuring_style).'</option>';
  302. }
  303. $return.= '</select>';
  304. return $return;
  305. }
  306. /**
  307. * Return list of lot numbers (stock from product_batch) with stock location and stock qty
  308. *
  309. * @param int $selected Id of preselected lot stock id ('' for no value, 'ifone'=select value if one value otherwise no value)
  310. * @param string $htmlname Name of html select html
  311. * @param string $filterstatus lot status filter, following comma separated filter options can be used
  312. * @param int $empty 1=Can be empty, 0 if not
  313. * @param int $disabled 1=Select is disabled
  314. * @param int $fk_product show lot numbers of product with id fk_product. All from objectLines if 0.
  315. * @param int $fk_entrepot filter lot numbers for warehouse with id fk_entrepot. All if 0.
  316. * @param array $objectLines Only cache lot numbers for products in lines of object. If no lines only for fk_product. If no fk_product, all.
  317. * @param string $empty_label Empty label if needed (only if $empty=1)
  318. * @param int $forcecombo 1=Force combo iso ajax select2
  319. * @param array $events Events to add to select2
  320. * @param string $morecss Add more css classes to HTML select
  321. *
  322. * @return string HTML select
  323. */
  324. function selectLotStock($selected='',$htmlname='batch_id',$filterstatus='',$empty=0,$disabled=0,$fk_product=0,$fk_entrepot=0,$objectLines = array(),$empty_label='', $forcecombo=0, $events=array(), $morecss='minwidth200')
  325. {
  326. global $langs;
  327. dol_syslog(get_class($this)."::selectLot $selected, $htmlname, $filterstatus, $empty, $disabled, $fk_product, $fk_entrepot, $empty_label, $showstock, $forcecombo, $morecss",LOG_DEBUG);
  328. $out='';
  329. $productIdArray = array();
  330. if (! is_array($objectLines) || ! count($objectLines))
  331. {
  332. if (! empty($fk_product)) $productIdArray[] = $fk_product;
  333. }
  334. else
  335. {
  336. foreach ($objectLines as $line) {
  337. if ($line->fk_product) $productIdArray[] = $line->fk_product;
  338. }
  339. }
  340. $nboflot = $this->loadLotStock($productIdArray);
  341. if ($conf->use_javascript_ajax && ! $forcecombo)
  342. {
  343. include_once DOL_DOCUMENT_ROOT . '/core/lib/ajax.lib.php';
  344. $comboenhancement = ajax_combobox($htmlname, $events);
  345. $out.= $comboenhancement;
  346. }
  347. $out.='<select class="flat'.($morecss?' '.$morecss:'').'"'.($disabled?' disabled':'').' id="'.$htmlname.'" name="'.($htmlname.($disabled?'_disabled':'')).'">';
  348. if ($empty) $out.='<option value="-1">'.($empty_label?$empty_label:'&nbsp;').'</option>';
  349. if (! empty($fk_product))
  350. {
  351. $productIdArray = array($fk_product); // only show lot stock for product
  352. }
  353. else
  354. {
  355. foreach($this->cache_lot as $key => $value)
  356. {
  357. $productIdArray[] = $key;
  358. }
  359. }
  360. foreach($productIdArray as $productId)
  361. {
  362. foreach($this->cache_lot[$productId] as $id => $arraytypes)
  363. {
  364. if (empty($fk_entrepot) || $fk_entrepot == $arraytypes['entrepot_id'])
  365. {
  366. $out.='<option value="'.$id.'"';
  367. if ($selected == $id || ($selected == 'ifone' && $nboflot == 1)) $out.=' selected';
  368. $out.='>';
  369. $out.=$arraytypes['entrepot_label'].' - ';
  370. $out.=$arraytypes['batch'];
  371. $out.=' ('.$langs->trans("Stock").':'.$arraytypes['qty'].')';
  372. $out.='</option>';
  373. }
  374. }
  375. }
  376. $out.='</select>';
  377. if ($disabled) $out.='<input type="hidden" name="'.$htmlname.'" value="'.(($selected>0)?$selected:'').'">';
  378. return $out;
  379. }
  380. /**
  381. * Load in cache array list of lot available in stock from a given list of products
  382. *
  383. * @param array $productIdArray array of product id's from who to get lot numbers. A
  384. *
  385. * @return int Nb of loaded lines, 0 if nothing loaded, <0 if KO
  386. */
  387. private function loadLotStock($productIdArray = array())
  388. {
  389. global $conf, $langs;
  390. $cacheLoaded = false;
  391. if (empty($productIdArray))
  392. {
  393. // only Load lot stock for given products
  394. $this->cache_lot = array();
  395. return 0;
  396. }
  397. if (count($productIdArray) && count($this->cache_lot))
  398. {
  399. // check cache already loaded for product id's
  400. foreach ($productIdArray as $productId)
  401. {
  402. $cacheLoaded = ! empty($this->cache_lot[$productId]) ? true : false;
  403. }
  404. }
  405. if ($cacheLoaded)
  406. {
  407. return count($this->cache_lot);
  408. }
  409. else
  410. {
  411. // clear cache
  412. $this->cache_lot = array();
  413. $productIdList = implode(',', $productIdArray);
  414. $sql = "SELECT pb.batch, pb.rowid, ps.fk_entrepot, pb.qty, e.ref as label, ps.fk_product";
  415. $sql.= " FROM ".MAIN_DB_PREFIX."product_batch as pb";
  416. $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."product_stock as ps on ps.rowid = pb.fk_product_stock";
  417. $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."entrepot as e on e.rowid = ps.fk_entrepot AND e.entity IN (".getEntity('stock').")";
  418. if (!empty($productIdList))
  419. {
  420. $sql.= " WHERE ps.fk_product IN (".$productIdList.")";
  421. }
  422. $sql.= " ORDER BY e.ref, pb.batch";
  423. dol_syslog(get_class($this).'::loadLotStock', LOG_DEBUG);
  424. $resql = $this->db->query($sql);
  425. if ($resql)
  426. {
  427. $num = $this->db->num_rows($resql);
  428. $i = 0;
  429. while ($i < $num)
  430. {
  431. $obj = $this->db->fetch_object($resql);
  432. $this->cache_lot[$obj->fk_product][$obj->rowid]['id'] =$obj->rowid;
  433. $this->cache_lot[$obj->fk_product][$obj->rowid]['batch']=$obj->batch;
  434. $this->cache_lot[$obj->fk_product][$obj->rowid]['entrepot_id']=$obj->fk_entrepot;
  435. $this->cache_lot[$obj->fk_product][$obj->rowid]['entrepot_label']=$obj->label;
  436. $this->cache_lot[$obj->fk_product][$obj->rowid]['qty'] = $obj->qty;
  437. $i++;
  438. }
  439. return $num;
  440. }
  441. else
  442. {
  443. dol_print_error($this->db);
  444. return -1;
  445. }
  446. }
  447. }
  448. }