html.formproduct.class.php 32 KB

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