html.formproduct.class.php 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734
  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 <https://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. public $cache_warehouses = array();
  39. public $cache_lot = array();
  40. /**
  41. * Constructor
  42. *
  43. * @param DoliDB $db Database handler
  44. */
  45. public 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. * @param bool|int $stockMin [=false] Value of minimum stock to filter or false not not filter by minimum stock
  62. * @param string $orderBy [='e.ref'] Order by
  63. * @return int Nb of loaded lines, 0 if already loaded, <0 if KO
  64. * @throws Exception
  65. */
  66. public function loadWarehouses($fk_product = 0, $batch = '', $status = '', $sumStock = true, $exclude = array(), $stockMin = false, $orderBy = 'e.ref')
  67. {
  68. global $conf, $langs;
  69. if (empty($fk_product) && count($this->cache_warehouses)) {
  70. return 0; // Cache already loaded and we do not want a list with information specific to a product
  71. }
  72. $warehouseStatus = array();
  73. if (preg_match('/warehouseclosed/', $status)) {
  74. $warehouseStatus[] = Entrepot::STATUS_CLOSED;
  75. }
  76. if (preg_match('/warehouseopen/', $status)) {
  77. $warehouseStatus[] = Entrepot::STATUS_OPEN_ALL;
  78. }
  79. if (preg_match('/warehouseinternal/', $status)) {
  80. $warehouseStatus[] = Entrepot::STATUS_OPEN_INTERNAL;
  81. }
  82. $sql = "SELECT e.rowid, e.ref as label, e.description, e.fk_parent";
  83. if (!empty($fk_product) && $fk_product > 0) {
  84. if (!empty($batch)) {
  85. $sql .= ", pb.qty as stock";
  86. } else {
  87. $sql .= ", ps.reel as stock";
  88. }
  89. } elseif ($sumStock) {
  90. $sql .= ", sum(ps.reel) as stock";
  91. }
  92. $sql .= " FROM ".$this->db->prefix()."entrepot as e";
  93. $sql .= " LEFT JOIN ".$this->db->prefix()."product_stock as ps on ps.fk_entrepot = e.rowid";
  94. if (!empty($fk_product) && $fk_product > 0) {
  95. $sql .= " AND ps.fk_product = ".((int) $fk_product);
  96. if (!empty($batch)) {
  97. $sql .= " LEFT JOIN ".$this->db->prefix()."product_batch as pb on pb.fk_product_stock = ps.rowid AND pb.batch = '".$this->db->escape($batch)."'";
  98. }
  99. }
  100. $sql .= " WHERE e.entity IN (".getEntity('stock').")";
  101. if (count($warehouseStatus)) {
  102. $sql .= " AND e.statut IN (".$this->db->sanitize(implode(',', $warehouseStatus)).")";
  103. } else {
  104. $sql .= " AND e.statut = 1";
  105. }
  106. if (is_array($exclude) && !empty($exclude)) {
  107. $sql .= ' AND e.rowid NOT IN('.$this->db->sanitize(implode(',', $exclude)).')';
  108. }
  109. // minimum stock
  110. if ($stockMin !== false) {
  111. if (!empty($fk_product) && $fk_product > 0) {
  112. if (!empty($batch)) {
  113. $sql .= " AND pb.qty > ".((float) $stockMin);
  114. } else {
  115. $sql .= " AND ps.reel > ".((float) $stockMin);
  116. }
  117. }
  118. }
  119. if ($sumStock && empty($fk_product)) {
  120. $sql .= " GROUP BY e.rowid, e.ref, e.description, e.fk_parent";
  121. // minimum stock
  122. if ($stockMin !== false) {
  123. $sql .= " HAVING sum(ps.reel) > ".((float) $stockMin);
  124. }
  125. }
  126. $sql .= " ORDER BY ".$orderBy;
  127. dol_syslog(get_class($this).'::loadWarehouses', LOG_DEBUG);
  128. $resql = $this->db->query($sql);
  129. if ($resql) {
  130. $num = $this->db->num_rows($resql);
  131. $i = 0;
  132. while ($i < $num) {
  133. $obj = $this->db->fetch_object($resql);
  134. if ($sumStock) {
  135. $obj->stock = price2num($obj->stock, 5);
  136. }
  137. $this->cache_warehouses[$obj->rowid]['id'] = $obj->rowid;
  138. $this->cache_warehouses[$obj->rowid]['label'] = $obj->label;
  139. $this->cache_warehouses[$obj->rowid]['parent_id'] = $obj->fk_parent;
  140. $this->cache_warehouses[$obj->rowid]['description'] = $obj->description;
  141. $this->cache_warehouses[$obj->rowid]['stock'] = $obj->stock;
  142. $i++;
  143. }
  144. // Full label init
  145. foreach ($this->cache_warehouses as $obj_rowid => $tab) {
  146. $this->cache_warehouses[$obj_rowid]['full_label'] = $this->get_parent_path($tab);
  147. }
  148. return $num;
  149. } else {
  150. dol_print_error($this->db);
  151. return -1;
  152. }
  153. }
  154. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  155. /**
  156. * Return full path to current warehouse in $tab (recursive function)
  157. *
  158. * @param array $tab warehouse data in $this->cache_warehouses line
  159. * @param String $final_label full label with all parents, separated by ' >> ' (completed on each call)
  160. * @return String full label with all parents, separated by ' >> '
  161. */
  162. private function get_parent_path($tab, $final_label = '')
  163. {
  164. //phpcs:enable
  165. if (empty($final_label)) {
  166. $final_label = $tab['label'];
  167. }
  168. if (empty($tab['parent_id'])) {
  169. return $final_label;
  170. } else {
  171. if (!empty($this->cache_warehouses[$tab['parent_id']])) {
  172. $final_label = $this->cache_warehouses[$tab['parent_id']]['label'].' >> '.$final_label;
  173. return $this->get_parent_path($this->cache_warehouses[$tab['parent_id']], $final_label);
  174. }
  175. }
  176. return $final_label;
  177. }
  178. /**
  179. * Return list of warehouses
  180. *
  181. * @param string|int $selected Id of preselected warehouse ('' or '-1' for no value, 'ifone' and 'ifonenodefault' = select value if one value otherwise no value, '-2' to use the default value from setup)
  182. * @param string $htmlname Name of html select html
  183. * @param string $filterstatus warehouse status filter, following comma separated filter options can be used
  184. * 'warehouseopen' = select products from open warehouses,
  185. * 'warehouseclosed' = select products from closed warehouses,
  186. * 'warehouseinternal' = select products from warehouses for internal correct/transfer only
  187. * @param int $empty 1=Can be empty, 0 if not
  188. * @param int $disabled 1=Select is disabled
  189. * @param int $fk_product Add quantity of stock in label for product with id fk_product. Nothing if 0.
  190. * @param string $empty_label Empty label if needed (only if $empty=1)
  191. * @param int $showstock 1=Show stock count
  192. * @param int $forcecombo 1=Force combo iso ajax select2
  193. * @param array $events Events to add to select2
  194. * @param string $morecss Add more css classes to HTML select
  195. * @param array $exclude Warehouses ids to exclude
  196. * @param int $showfullpath 1=Show full path of name (parent ref into label), 0=Show only ref of current warehouse
  197. * @param bool|int $stockMin [=false] Value of minimum stock to filter or false not not filter by minimum stock
  198. * @param string $orderBy [='e.ref'] Order by
  199. * @return string HTML select
  200. *
  201. * @throws Exception
  202. */
  203. public function selectWarehouses($selected = '', $htmlname = 'idwarehouse', $filterstatus = '', $empty = 0, $disabled = 0, $fk_product = 0, $empty_label = '', $showstock = 0, $forcecombo = 0, $events = array(), $morecss = 'minwidth200', $exclude = array(), $showfullpath = 1, $stockMin = false, $orderBy = 'e.ref')
  204. {
  205. global $conf, $langs, $user, $hookmanager;
  206. dol_syslog(get_class($this)."::selectWarehouses $selected, $htmlname, $filterstatus, $empty, $disabled, $fk_product, $empty_label, $showstock, $forcecombo, $morecss", LOG_DEBUG);
  207. $out = '';
  208. if (empty($conf->global->ENTREPOT_EXTRA_STATUS)) {
  209. $filterstatus = '';
  210. }
  211. if (!empty($fk_product) && $fk_product > 0) {
  212. $this->cache_warehouses = array();
  213. }
  214. $this->loadWarehouses($fk_product, '', $filterstatus, true, $exclude, $stockMin, $orderBy);
  215. $nbofwarehouses = count($this->cache_warehouses);
  216. if ($conf->use_javascript_ajax && !$forcecombo) {
  217. include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
  218. $comboenhancement = ajax_combobox($htmlname, $events);
  219. $out .= $comboenhancement;
  220. }
  221. if (strpos($htmlname, 'search_') !== 0) {
  222. if (empty($user->fk_warehouse) || $user->fk_warehouse == -1) {
  223. if (($selected == '-2' || $selected == 'ifone') && !empty($conf->global->MAIN_DEFAULT_WAREHOUSE)) {
  224. $selected = $conf->global->MAIN_DEFAULT_WAREHOUSE;
  225. }
  226. } else {
  227. if (($selected == '-2' || $selected == 'ifone') && !empty($conf->global->MAIN_DEFAULT_WAREHOUSE_USER)) {
  228. $selected = $user->fk_warehouse;
  229. }
  230. }
  231. }
  232. $out .= '<select class="flat'.($morecss ? ' '.$morecss : '').'"'.($disabled ? ' disabled' : '').' id="'.$htmlname.'" name="'.($htmlname.($disabled ? '_disabled' : '')).'">';
  233. if ($empty) {
  234. $out .= '<option value="-1">'.($empty_label ? $empty_label : '&nbsp;').'</option>';
  235. }
  236. foreach ($this->cache_warehouses as $id => $arraytypes) {
  237. $label = '';
  238. if ($showfullpath) {
  239. $label .= $arraytypes['full_label'];
  240. } else {
  241. $label .= $arraytypes['label'];
  242. }
  243. if (($fk_product || ($showstock > 0)) && ($arraytypes['stock'] != 0 || ($showstock > 0))) {
  244. if ($arraytypes['stock'] <= 0) {
  245. $label .= ' <span class= \'text-warning\'>('.$langs->trans("Stock").':'.$arraytypes['stock'].')</span>';
  246. } else {
  247. $label .= ' <span class=\'opacitymedium\'>('.$langs->trans("Stock").':'.$arraytypes['stock'].')</span>';
  248. }
  249. }
  250. $out .= '<option value="'.$id.'"';
  251. if ($selected == $id || (preg_match('/^ifone/', $selected) && $nbofwarehouses == 1)) {
  252. $out .= ' selected';
  253. }
  254. $out .= ' data-html="'.dol_escape_htmltag($label).'"';
  255. $out .= '>';
  256. $out .= $label;
  257. $out .= '</option>';
  258. }
  259. $out .= '</select>';
  260. if ($disabled) {
  261. $out .= '<input type="hidden" name="'.$htmlname.'" value="'.(($selected > 0) ? $selected : '').'">';
  262. }
  263. $parameters = array(
  264. 'selected' => $selected,
  265. 'htmlname' => $htmlname,
  266. 'filterstatus' => $filterstatus,
  267. 'empty' => $empty,
  268. 'disabled ' => $disabled,
  269. 'fk_product' => $fk_product,
  270. 'empty_label' => $empty_label,
  271. 'showstock' => $showstock,
  272. 'forcecombo' => $forcecombo,
  273. 'events' => $events,
  274. 'morecss' => $morecss,
  275. 'exclude' => $exclude,
  276. 'showfullpath' => $showfullpath,
  277. 'stockMin' => $stockMin,
  278. 'orderBy' => $orderBy
  279. );
  280. $reshook = $hookmanager->executeHooks('selectWarehouses', $parameters, $this);
  281. if ($reshook > 0) {
  282. $out = $hookmanager->resPrint;
  283. } elseif ($reshook == 0) {
  284. $out .= $hookmanager->resPrint;
  285. }
  286. return $out;
  287. }
  288. /**
  289. * Display form to select warehouse
  290. *
  291. * @param string $page Page
  292. * @param int $selected Id of warehouse
  293. * @param string $htmlname Name of select html field
  294. * @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.
  295. * @return void
  296. */
  297. public function formSelectWarehouses($page, $selected = '', $htmlname = 'warehouse_id', $addempty = 0)
  298. {
  299. global $langs;
  300. if ($htmlname != "none") {
  301. print '<form method="POST" action="'.$page.'">';
  302. print '<input type="hidden" name="action" value="setwarehouse">';
  303. print '<input type="hidden" name="token" value="'.newToken().'">';
  304. print '<table class="nobordernopadding">';
  305. print '<tr><td>';
  306. print $this->selectWarehouses($selected, $htmlname, '', $addempty);
  307. print '</td>';
  308. print '<td class="left"><input type="submit" class="button smallpaddingimp" value="'.$langs->trans("Modify").'"></td>';
  309. print '</tr></table></form>';
  310. } else {
  311. if ($selected) {
  312. require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
  313. $warehousestatic = new Entrepot($this->db);
  314. $warehousestatic->fetch($selected);
  315. print $warehousestatic->getNomUrl();
  316. } else {
  317. print "&nbsp;";
  318. }
  319. }
  320. }
  321. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  322. /**
  323. * Output a combo box with list of units
  324. * pour l'instant on ne definit pas les unites dans la base
  325. *
  326. * @param string $name Name of HTML field
  327. * @param string $measuring_style Unit to show: weight, size, surface, volume, time
  328. * @param string $default Preselected value
  329. * @param int $adddefault Add empty unit called "Default"
  330. * @param int $mode 1=Use short label as value, 0=Use rowid
  331. * @return void
  332. * @deprecated
  333. */
  334. public function select_measuring_units($name = 'measuring_units', $measuring_style = '', $default = '0', $adddefault = 0, $mode = 0)
  335. {
  336. //phpcs:enable
  337. print $this->selectMeasuringUnits($name, $measuring_style, $default, $adddefault, $mode);
  338. }
  339. /**
  340. * Return a combo box with list of units
  341. * Units labels are defined in llx_c_units
  342. *
  343. * @param string $name Name of HTML field
  344. * @param string $measuring_style Unit to show: weight, size, surface, volume, time
  345. * @param string $default Preselected value
  346. * @param int|string $adddefault 1=Add empty unit called "Default", ''=Add empty value
  347. * @param int $mode 1=Use short label as value, 0=Use rowid, 2=Use scale (power)
  348. * @param string $morecss More CSS
  349. * @return string
  350. */
  351. public function selectMeasuringUnits($name = 'measuring_units', $measuring_style = '', $default = '0', $adddefault = 0, $mode = 0, $morecss = 'maxwidth125')
  352. {
  353. global $langs, $conf, $mysoc, $db;
  354. $langs->load("other");
  355. $return = '';
  356. // TODO Use a cache
  357. require_once DOL_DOCUMENT_ROOT.'/core/class/cunits.class.php';
  358. $measuringUnits = new CUnits($db);
  359. $filter = array();
  360. $filter['t.active'] = 1;
  361. if ($measuring_style) {
  362. $filter['t.unit_type'] = $measuring_style;
  363. }
  364. $result = $measuringUnits->fetchAll(
  365. '',
  366. '',
  367. 0,
  368. 0,
  369. $filter
  370. );
  371. if ($result < 0) {
  372. dol_print_error($db);
  373. return -1;
  374. } else {
  375. $return .= '<select class="flat'.($morecss ? ' '.$morecss : '').'" name="'.$name.'" id="'.$name.'">';
  376. if ($adddefault || $adddefault === '') {
  377. $return .= '<option value="0">'.($adddefault ? $langs->trans("Default") : '').'</option>';
  378. }
  379. foreach ($measuringUnits->records as $lines) {
  380. $return .= '<option value="';
  381. if ($mode == 1) {
  382. $return .= $lines->short_label;
  383. } elseif ($mode == 2) {
  384. $return .= $lines->scale;
  385. } else {
  386. $return .= $lines->id;
  387. }
  388. $return .= '"';
  389. if ($mode == 1 && $lines->short_label == $default) {
  390. $return .= ' selected';
  391. } elseif ($mode == 2 && $lines->scale == $default) {
  392. $return .= ' selected';
  393. } elseif ($mode == 0 && $lines->id == $default) {
  394. $return .= ' selected';
  395. }
  396. $return .= '>';
  397. if ($measuring_style == 'time') {
  398. $return .= $langs->trans(ucfirst($lines->label));
  399. } else {
  400. $return .= $langs->trans($lines->label);
  401. }
  402. $return .= '</option>';
  403. }
  404. $return .= '</select>';
  405. }
  406. $return .= ajax_combobox($name);
  407. return $return;
  408. }
  409. /**
  410. * Return a combo box with list of units
  411. * NAture of product labels are defined in llx_c_product_nature
  412. *
  413. * @param string $name Name of HTML field
  414. * @param string $selected Preselected value
  415. * @param int $mode 1=Use label as value, 0=Use code
  416. * @param int $showempty 1=show empty value, 0= no
  417. * @return string
  418. */
  419. public function selectProductNature($name = 'finished', $selected = '', $mode = 0, $showempty = 1)
  420. {
  421. global $langs, $db;
  422. $langs->load('products');
  423. $return = '';
  424. // TODO Use a cache
  425. require_once DOL_DOCUMENT_ROOT.'/core/class/cproductnature.class.php';
  426. $productNature = new CProductNature($db);
  427. $filter = array();
  428. $filter['t.active'] = 1;
  429. $result = $productNature->fetchAll('', '', 0, 0, $filter);
  430. if ($result < 0) {
  431. dol_print_error($db);
  432. return -1;
  433. } else {
  434. $return .= '<select class="flat" name="'.$name.'" id="'.$name.'">';
  435. if ($showempty || ($selected == '' || $selected == '-1')) {
  436. $return .= '<option value="-1"';
  437. if ($selected == '' || $selected == '-1') {
  438. $return .= ' selected';
  439. }
  440. $return .= '></option>';
  441. }
  442. if (!empty($productNature->records) && is_array($productNature->records)) {
  443. foreach ($productNature->records as $lines) {
  444. $return .= '<option value="';
  445. if ($mode == 1) {
  446. $return .= $lines->label;
  447. } else {
  448. $return .= $lines->code;
  449. }
  450. $return .= '"';
  451. if ($mode == 1 && $lines->label == $selected) {
  452. $return .= ' selected';
  453. } elseif ($lines->code == $selected) {
  454. $return .= ' selected';
  455. }
  456. $return .= '>';
  457. $return .= $langs->trans($lines->label);
  458. $return .= '</option>';
  459. }
  460. }
  461. $return .= '</select>';
  462. }
  463. $return .= ajax_combobox($name);
  464. return $return;
  465. }
  466. /**
  467. * Return list of lot numbers (stock from product_batch) with stock location and stock qty
  468. *
  469. * @param int $selected Id of preselected lot stock id ('' for no value, 'ifone'=select value if one value otherwise no value)
  470. * @param string $htmlname Name of html select html
  471. * @param string $filterstatus lot status filter, following comma separated filter options can be used
  472. * @param int $empty 1=Can be empty, 0 if not
  473. * @param int $disabled 1=Select is disabled
  474. * @param int $fk_product show lot numbers of product with id fk_product. All from objectLines if 0.
  475. * @param int $fk_entrepot filter lot numbers for warehouse with id fk_entrepot. All if 0.
  476. * @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.
  477. * @param string $empty_label Empty label if needed (only if $empty=1)
  478. * @param int $forcecombo 1=Force combo iso ajax select2
  479. * @param array $events Events to add to select2
  480. * @param string $morecss Add more css classes to HTML select
  481. *
  482. * @return string HTML select
  483. */
  484. public 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')
  485. {
  486. global $conf, $langs;
  487. dol_syslog(get_class($this)."::selectLotStock $selected, $htmlname, $filterstatus, $empty, $disabled, $fk_product, $fk_entrepot, $empty_label, $forcecombo, $morecss", LOG_DEBUG);
  488. $out = '';
  489. $productIdArray = array();
  490. if (!is_array($objectLines) || !count($objectLines)) {
  491. if (!empty($fk_product) && $fk_product > 0) {
  492. $productIdArray[] = (int) $fk_product;
  493. }
  494. } else {
  495. foreach ($objectLines as $line) {
  496. if ($line->fk_product) {
  497. $productIdArray[] = $line->fk_product;
  498. }
  499. }
  500. }
  501. $nboflot = $this->loadLotStock($productIdArray);
  502. if ($conf->use_javascript_ajax && !$forcecombo) {
  503. include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
  504. $comboenhancement = ajax_combobox($htmlname, $events);
  505. $out .= $comboenhancement;
  506. }
  507. $out .= '<select class="flat'.($morecss ? ' '.$morecss : '').'"'.($disabled ? ' disabled' : '').' id="'.$htmlname.'" name="'.($htmlname.($disabled ? '_disabled' : '')).'">';
  508. if ($empty) {
  509. $out .= '<option value="-1">'.($empty_label ? $empty_label : '&nbsp;').'</option>';
  510. }
  511. if (!empty($fk_product) && $fk_product > 0) {
  512. $productIdArray = array((int) $fk_product); // only show lot stock for product
  513. } else {
  514. foreach ($this->cache_lot as $key => $value) {
  515. $productIdArray[] = $key;
  516. }
  517. }
  518. foreach ($productIdArray as $productId) {
  519. foreach ($this->cache_lot[$productId] as $id => $arraytypes) {
  520. if (empty($fk_entrepot) || $fk_entrepot == $arraytypes['entrepot_id']) {
  521. $label = $arraytypes['entrepot_label'].' - ';
  522. $label .= $arraytypes['batch'];
  523. if ($arraytypes['qty'] <= 0) {
  524. $label .= ' <span class=\'text-warning\'>('.$langs->trans("Stock").' '.$arraytypes['qty'].')</span>';
  525. } else {
  526. $label .= ' <span class=\'opacitymedium\'>('.$langs->trans("Stock").' '.$arraytypes['qty'].')</span>';
  527. }
  528. $out .= '<option value="'.$id.'"';
  529. if ($selected == $id || ($selected == 'ifone' && $nboflot == 1)) {
  530. $out .= ' selected';
  531. }
  532. $out .= ' data-html="'.dol_escape_htmltag($label).'"';
  533. $out .= '>';
  534. $out .= $label;
  535. $out .= '</option>';
  536. }
  537. }
  538. }
  539. $out .= '</select>';
  540. if ($disabled) {
  541. $out .= '<input type="hidden" name="'.$htmlname.'" value="'.(($selected > 0) ? $selected : '').'">';
  542. }
  543. return $out;
  544. }
  545. /**
  546. * Return list of lot numbers (stock from product_batch) for product and warehouse.
  547. *
  548. * @param string $htmlname Name of key that is inside attribute "list" of an input text field.
  549. * @param int $empty 1=Can be empty, 0 if not
  550. * @param int $fk_product show lot numbers of product with id fk_product. All from objectLines if 0.
  551. * @param int $fk_entrepot filter lot numbers for warehouse with id fk_entrepot. All if 0.
  552. * @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.
  553. * @return string HTML datalist
  554. */
  555. public function selectLotDataList($htmlname = 'batch_id', $empty = 0, $fk_product = 0, $fk_entrepot = 0, $objectLines = array())
  556. {
  557. global $conf, $langs;
  558. dol_syslog(get_class($this)."::selectLotDataList $htmlname, $empty, $fk_product, $fk_entrepot,$objectLines", LOG_DEBUG);
  559. $out = '';
  560. $productIdArray = array();
  561. if (!is_array($objectLines) || !count($objectLines)) {
  562. if (!empty($fk_product) && $fk_product > 0) {
  563. $productIdArray[] = (int) $fk_product;
  564. }
  565. } else {
  566. foreach ($objectLines as $line) {
  567. if ($line->fk_product) {
  568. $productIdArray[] = $line->fk_product;
  569. }
  570. }
  571. }
  572. $nboflot = $this->loadLotStock($productIdArray);
  573. $out .= '<datalist id="'.$htmlname.'" >';
  574. if (!empty($fk_product) && $fk_product > 0) {
  575. $productIdArray = array((int) $fk_product); // only show lot stock for product
  576. } else {
  577. foreach ($this->cache_lot as $key => $value) {
  578. $productIdArray[] = $key;
  579. }
  580. }
  581. foreach ($productIdArray as $productId) {
  582. if (array_key_exists($productId, $this->cache_lot)) {
  583. foreach ($this->cache_lot[$productId] as $id => $arraytypes) {
  584. if (empty($fk_entrepot) || $fk_entrepot == $arraytypes['entrepot_id']) {
  585. $label = $arraytypes['entrepot_label'] . ' - ';
  586. $label .= $arraytypes['batch'];
  587. $out .= '<option>' . $arraytypes['batch'] . '</option>';
  588. }
  589. }
  590. }
  591. }
  592. $out .= '</datalist>';
  593. return $out;
  594. }
  595. /**
  596. * Load in cache array list of lot available in stock from a given list of products
  597. *
  598. * @param array $productIdArray array of product id's from who to get lot numbers. A
  599. *
  600. * @return int Nb of loaded lines, 0 if nothing loaded, <0 if KO
  601. */
  602. private function loadLotStock($productIdArray = array())
  603. {
  604. global $conf, $langs;
  605. $cacheLoaded = false;
  606. if (empty($productIdArray)) {
  607. // only Load lot stock for given products
  608. $this->cache_lot = array();
  609. return 0;
  610. }
  611. if (count($productIdArray) && count($this->cache_lot)) {
  612. // check cache already loaded for product id's
  613. foreach ($productIdArray as $productId) {
  614. $cacheLoaded = !empty($this->cache_lot[$productId]) ? true : false;
  615. }
  616. }
  617. if ($cacheLoaded) {
  618. return count($this->cache_lot);
  619. } else {
  620. // clear cache
  621. $this->cache_lot = array();
  622. $productIdList = implode(',', $productIdArray);
  623. $sql = "SELECT pb.batch, pb.rowid, ps.fk_entrepot, pb.qty, e.ref as label, ps.fk_product";
  624. $sql .= " FROM ".$this->db->prefix()."product_batch as pb";
  625. $sql .= " LEFT JOIN ".$this->db->prefix()."product_stock as ps on ps.rowid = pb.fk_product_stock";
  626. $sql .= " LEFT JOIN ".$this->db->prefix()."entrepot as e on e.rowid = ps.fk_entrepot AND e.entity IN (".getEntity('stock').")";
  627. if (!empty($productIdList)) {
  628. $sql .= " WHERE ps.fk_product IN (".$this->db->sanitize($productIdList).")";
  629. }
  630. $sql .= " ORDER BY e.ref, pb.batch";
  631. dol_syslog(get_class($this).'::loadLotStock', LOG_DEBUG);
  632. $resql = $this->db->query($sql);
  633. if ($resql) {
  634. $num = $this->db->num_rows($resql);
  635. $i = 0;
  636. while ($i < $num) {
  637. $obj = $this->db->fetch_object($resql);
  638. $this->cache_lot[$obj->fk_product][$obj->rowid]['id'] = $obj->rowid;
  639. $this->cache_lot[$obj->fk_product][$obj->rowid]['batch'] = $obj->batch;
  640. $this->cache_lot[$obj->fk_product][$obj->rowid]['entrepot_id'] = $obj->fk_entrepot;
  641. $this->cache_lot[$obj->fk_product][$obj->rowid]['entrepot_label'] = $obj->label;
  642. $this->cache_lot[$obj->fk_product][$obj->rowid]['qty'] = $obj->qty;
  643. $i++;
  644. }
  645. return $num;
  646. } else {
  647. dol_print_error($this->db);
  648. return -1;
  649. }
  650. }
  651. }
  652. }