html.formproduct.class.php 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937
  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 (only warehouse with stock > stockMin are loaded) 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|array $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 (only warehouse with stock > stockMin are loaded) or false not not filter by minimum stock
  248. * @param string $orderBy [='e.ref'] Order by
  249. * @param int $multiselect 1=Allow multiselect
  250. * @return string HTML select
  251. *
  252. * @throws Exception
  253. */
  254. 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', $multiselect = 0)
  255. {
  256. global $conf, $langs, $user, $hookmanager;
  257. dol_syslog(get_class($this)."::selectWarehouses $selected, $htmlname, $filterstatus, $empty, $disabled, $fk_product, $empty_label, $showstock, $forcecombo, $morecss", LOG_DEBUG);
  258. $out = '';
  259. if (!getDolGlobalString('ENTREPOT_EXTRA_STATUS')) {
  260. $filterstatus = '';
  261. }
  262. if (!empty($fk_product) && $fk_product > 0) {
  263. $this->cache_warehouses = array();
  264. }
  265. $this->loadWarehouses($fk_product, '', $filterstatus, true, $exclude, $stockMin, $orderBy);
  266. $nbofwarehouses = count($this->cache_warehouses);
  267. if ($conf->use_javascript_ajax && !$forcecombo) {
  268. include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
  269. $comboenhancement = ajax_combobox($htmlname, $events);
  270. $out .= $comboenhancement;
  271. }
  272. if (strpos($htmlname, 'search_') !== 0) {
  273. if (empty($user->fk_warehouse) || $user->fk_warehouse == -1) {
  274. if (is_scalar($selected) && ($selected == '-2' || $selected == 'ifone') && getDolGlobalString('MAIN_DEFAULT_WAREHOUSE')) {
  275. $selected = $conf->global->MAIN_DEFAULT_WAREHOUSE;
  276. }
  277. } else {
  278. if (is_scalar($selected) && ($selected == '-2' || $selected == 'ifone') && getDolGlobalString('MAIN_DEFAULT_WAREHOUSE_USER')) {
  279. $selected = $user->fk_warehouse;
  280. }
  281. }
  282. }
  283. $out .= '<select '.($multiselect ? 'multiple ' : '').'class="flat'.($morecss ? ' '.$morecss : '').'"'.($disabled ? ' disabled' : '');
  284. $out .= ' id="'.$htmlname.'" name="'.($htmlname.($multiselect ? '[]' : '').($disabled ? '_disabled' : '')).'"';
  285. //$out .= ' placeholder="todo"'; // placeholder for select2 must be added by setting the id+placeholder js param when calling select2
  286. $out .= '>';
  287. if ($empty) {
  288. $out .= '<option value="-1">'.($empty_label ? $empty_label : '&nbsp;').'</option>';
  289. }
  290. foreach ($this->cache_warehouses as $id => $arraytypes) {
  291. $label = '';
  292. if ($showfullpath) {
  293. $label .= $arraytypes['full_label'];
  294. } else {
  295. $label .= $arraytypes['label'];
  296. }
  297. if (($fk_product || ($showstock > 0)) && ($arraytypes['stock'] != 0 || ($showstock > 0))) {
  298. if ($arraytypes['stock'] <= 0) {
  299. $label .= ' <span class="text-warning">('.$langs->trans("Stock").':'.$arraytypes['stock'].')</span>';
  300. } else {
  301. $label .= ' <span class="opacitymedium">('.$langs->trans("Stock").':'.$arraytypes['stock'].')</span>';
  302. }
  303. }
  304. $out .= '<option value="'.$id.'"';
  305. if (is_array($selected)) {
  306. if (in_array($id, $selected)) {
  307. $out .= ' selected';
  308. }
  309. } else {
  310. if ($selected == $id || (preg_match('/^ifone/', $selected) && $nbofwarehouses == 1)) {
  311. $out .= ' selected';
  312. }
  313. }
  314. $out .= ' data-html="'.dol_escape_htmltag($label).'"';
  315. $out .= '>';
  316. $out .= $label;
  317. $out .= '</option>';
  318. }
  319. $out .= '</select>';
  320. if ($disabled) {
  321. $out .= '<input type="hidden" name="'.$htmlname.'" value="'.(($selected > 0) ? $selected : '').'">';
  322. }
  323. $parameters = array(
  324. 'selected' => $selected,
  325. 'htmlname' => $htmlname,
  326. 'filterstatus' => $filterstatus,
  327. 'empty' => $empty,
  328. 'disabled ' => $disabled,
  329. 'fk_product' => $fk_product,
  330. 'empty_label' => $empty_label,
  331. 'showstock' => $showstock,
  332. 'forcecombo' => $forcecombo,
  333. 'events' => $events,
  334. 'morecss' => $morecss,
  335. 'exclude' => $exclude,
  336. 'showfullpath' => $showfullpath,
  337. 'stockMin' => $stockMin,
  338. 'orderBy' => $orderBy
  339. );
  340. $reshook = $hookmanager->executeHooks('selectWarehouses', $parameters, $this);
  341. if ($reshook > 0) {
  342. $out = $hookmanager->resPrint;
  343. } elseif ($reshook == 0) {
  344. $out .= $hookmanager->resPrint;
  345. }
  346. return $out;
  347. }
  348. /**
  349. * Return list of workstations
  350. *
  351. * @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)
  352. * @param string $htmlname Name of html select html
  353. * @param int $empty 1=Can be empty, 0 if not
  354. * @param int $disabled 1=Select is disabled
  355. * @param int $fk_product Add quantity of stock in label for product with id fk_product. Nothing if 0.
  356. * @param string $empty_label Empty label if needed (only if $empty=1)
  357. * @param int $forcecombo 1=Force combo iso ajax select2
  358. * @param array $events Events to add to select2
  359. * @param string $morecss Add more css classes to HTML select
  360. * @param array $exclude Warehouses ids to exclude
  361. * @param int $showfullpath 1=Show full path of name (parent ref into label), 0=Show only ref of current warehouse
  362. * @param string $orderBy [='e.ref'] Order by
  363. * @return string HTML select
  364. *
  365. * @throws Exception
  366. */
  367. 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')
  368. {
  369. global $conf, $langs, $user, $hookmanager;
  370. dol_syslog(get_class($this)."::selectWorkstations $selected, $htmlname, $empty, $disabled, $fk_product, $empty_label, $forcecombo, $morecss", LOG_DEBUG);
  371. $filterstatus='';
  372. $out = '';
  373. if (!empty($fk_product) && $fk_product > 0) {
  374. $this->cache_workstations = array();
  375. }
  376. $this->loadWorkstations($fk_product);
  377. $nbofworkstations = count($this->cache_workstations);
  378. if ($conf->use_javascript_ajax && !$forcecombo) {
  379. include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
  380. $comboenhancement = ajax_combobox($htmlname, $events);
  381. $out .= $comboenhancement;
  382. }
  383. if (strpos($htmlname, 'search_') !== 0) {
  384. if (empty($user->fk_workstation) || $user->fk_workstation == -1) {
  385. if (($selected == '-2' || $selected == 'ifone') && getDolGlobalString('MAIN_DEFAULT_WORKSTATION')) {
  386. $selected = $conf->global->MAIN_DEFAULT_WORKSTATION;
  387. }
  388. } else {
  389. if (($selected == '-2' || $selected == 'ifone') && getDolGlobalString('MAIN_DEFAULT_WORKSTATION')) {
  390. $selected = $user->fk_workstation;
  391. }
  392. }
  393. }
  394. $out .= '<select class="flat'.($morecss ? ' '.$morecss : '').'"'.($disabled ? ' disabled' : '').' id="'.$htmlname.'" name="'.($htmlname.($disabled ? '_disabled' : '')).'">';
  395. if ($empty) {
  396. $out .= '<option value="-1">'.($empty_label ? $empty_label : '&nbsp;').'</option>';
  397. }
  398. foreach ($this->cache_workstations as $id => $arraytypes) {
  399. $label = $arraytypes['label'];
  400. $out .= '<option value="'.$id.'"';
  401. if ($selected == $id || (preg_match('/^ifone/', $selected) && $nbofworkstations == 1)) {
  402. $out .= ' selected';
  403. }
  404. $out .= ' data-html="'.dol_escape_htmltag($label).'"';
  405. $out .= '>';
  406. $out .= $label;
  407. $out .= '</option>';
  408. }
  409. $out .= '</select>';
  410. if ($disabled) {
  411. $out .= '<input type="hidden" name="'.$htmlname.'" value="'.(($selected > 0) ? $selected : '').'">';
  412. }
  413. $parameters = array(
  414. 'selected' => $selected,
  415. 'htmlname' => $htmlname,
  416. 'filterstatus' => $filterstatus,
  417. 'empty' => $empty,
  418. 'disabled ' => $disabled,
  419. 'fk_product' => $fk_product,
  420. 'empty_label' => $empty_label,
  421. 'forcecombo' => $forcecombo,
  422. 'events' => $events,
  423. 'morecss' => $morecss,
  424. 'exclude' => $exclude,
  425. 'showfullpath' => $showfullpath,
  426. 'orderBy' => $orderBy
  427. );
  428. $reshook = $hookmanager->executeHooks('selectWorkstations', $parameters, $this);
  429. if ($reshook > 0) {
  430. $out = $hookmanager->resPrint;
  431. } elseif ($reshook == 0) {
  432. $out .= $hookmanager->resPrint;
  433. }
  434. return $out;
  435. }
  436. /**
  437. * Display form to select warehouse
  438. *
  439. * @param string $page Page
  440. * @param int $selected Id of warehouse
  441. * @param string $htmlname Name of select html field
  442. * @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.
  443. * @return void
  444. */
  445. public function formSelectWarehouses($page, $selected = '', $htmlname = 'warehouse_id', $addempty = 0)
  446. {
  447. global $langs;
  448. if ($htmlname != "none") {
  449. print '<form method="POST" action="'.$page.'">';
  450. print '<input type="hidden" name="action" value="setwarehouse">';
  451. print '<input type="hidden" name="token" value="'.newToken().'">';
  452. print '<table class="nobordernopadding">';
  453. print '<tr><td>';
  454. print $this->selectWarehouses($selected, $htmlname, '', $addempty);
  455. print '</td>';
  456. print '<td class="left"><input type="submit" class="button smallpaddingimp" value="'.$langs->trans("Modify").'"></td>';
  457. print '</tr></table></form>';
  458. } else {
  459. if ($selected) {
  460. require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
  461. $warehousestatic = new Entrepot($this->db);
  462. $warehousestatic->fetch($selected);
  463. print $warehousestatic->getNomUrl();
  464. } else {
  465. print "&nbsp;";
  466. }
  467. }
  468. }
  469. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  470. /**
  471. * Output a combo box with list of units
  472. * pour l'instant on ne definit pas les unites dans la base
  473. *
  474. * @param string $name Name of HTML field
  475. * @param string $measuring_style Unit to show: weight, size, surface, volume, time
  476. * @param string $default Preselected value
  477. * @param int $adddefault Add empty unit called "Default"
  478. * @param int $mode 1=Use short label as value, 0=Use rowid
  479. * @return void
  480. * @deprecated
  481. */
  482. public function select_measuring_units($name = 'measuring_units', $measuring_style = '', $default = '0', $adddefault = 0, $mode = 0)
  483. {
  484. //phpcs:enable
  485. print $this->selectMeasuringUnits($name, $measuring_style, $default, $adddefault, $mode);
  486. }
  487. /**
  488. * Return a combo box with list of units
  489. * Units labels are defined in llx_c_units
  490. *
  491. * @param string $name Name of HTML field
  492. * @param string $measuring_style Unit to show: weight, size, surface, volume, time
  493. * @param string $default Preselected value
  494. * @param int|string $adddefault 1=Add empty unit called "Default", ''=Add empty value
  495. * @param int $mode 1=Use short label as value, 0=Use rowid, 2=Use scale (power)
  496. * @param string $morecss More CSS
  497. * @return string
  498. */
  499. public function selectMeasuringUnits($name = 'measuring_units', $measuring_style = '', $default = '0', $adddefault = 0, $mode = 0, $morecss = 'maxwidth125')
  500. {
  501. global $langs, $conf, $mysoc, $db;
  502. $langs->load("other");
  503. $return = '';
  504. // TODO Use a cache
  505. require_once DOL_DOCUMENT_ROOT.'/core/class/cunits.class.php';
  506. $measuringUnits = new CUnits($db);
  507. $filter = array();
  508. $filter['t.active'] = 1;
  509. if ($measuring_style) {
  510. $filter['t.unit_type'] = $measuring_style;
  511. }
  512. $result = $measuringUnits->fetchAll(
  513. '',
  514. '',
  515. 0,
  516. 0,
  517. $filter
  518. );
  519. if ($result < 0) {
  520. dol_print_error($db);
  521. return -1;
  522. } else {
  523. $return .= '<select class="flat'.($morecss ? ' '.$morecss : '').'" name="'.$name.'" id="'.$name.'">';
  524. if ($adddefault || $adddefault === '') {
  525. $return .= '<option value="0">'.($adddefault ? $langs->trans("Default") : '').'</option>';
  526. }
  527. foreach ($measuringUnits->records as $lines) {
  528. $return .= '<option value="';
  529. if ($mode == 1) {
  530. $return .= $lines->short_label;
  531. } elseif ($mode == 2) {
  532. $return .= $lines->scale;
  533. } else {
  534. $return .= $lines->id;
  535. }
  536. $return .= '"';
  537. if ($mode == 1 && $lines->short_label == $default) {
  538. $return .= ' selected';
  539. } elseif ($mode == 2 && $lines->scale == $default) {
  540. $return .= ' selected';
  541. } elseif ($mode == 0 && $lines->id == $default) {
  542. $return .= ' selected';
  543. }
  544. $return .= '>';
  545. if ($measuring_style == 'time') {
  546. $return .= $langs->trans(ucfirst($lines->label));
  547. } else {
  548. $return .= $langs->trans($lines->label);
  549. }
  550. $return .= '</option>';
  551. }
  552. $return .= '</select>';
  553. }
  554. $return .= ajax_combobox($name);
  555. return $return;
  556. }
  557. /**
  558. * Return a combo box with list of units
  559. * NAture of product labels are defined in llx_c_product_nature
  560. *
  561. * @param string $name Name of HTML field
  562. * @param string $selected Preselected value
  563. * @param int $mode 1=Use label as value, 0=Use code
  564. * @param int $showempty 1=show empty value, 0= no
  565. * @return string
  566. */
  567. public function selectProductNature($name = 'finished', $selected = '', $mode = 0, $showempty = 1)
  568. {
  569. global $langs, $db;
  570. $langs->load('products');
  571. $return = '';
  572. // TODO Use a cache
  573. require_once DOL_DOCUMENT_ROOT.'/core/class/cproductnature.class.php';
  574. $productNature = new CProductNature($db);
  575. $filter = array();
  576. $filter['t.active'] = 1;
  577. $result = $productNature->fetchAll('', '', 0, 0, $filter);
  578. if ($result < 0) {
  579. dol_print_error($db);
  580. return -1;
  581. } else {
  582. $return .= '<select class="flat" name="'.$name.'" id="'.$name.'">';
  583. if ($showempty || ($selected == '' || $selected == '-1')) {
  584. $return .= '<option value="-1"';
  585. if ($selected == '' || $selected == '-1') {
  586. $return .= ' selected';
  587. }
  588. $return .= '></option>';
  589. }
  590. if (!empty($productNature->records) && is_array($productNature->records)) {
  591. foreach ($productNature->records as $lines) {
  592. $return .= '<option value="';
  593. if ($mode == 1) {
  594. $return .= $lines->label;
  595. } else {
  596. $return .= $lines->code;
  597. }
  598. $return .= '"';
  599. if ($mode == 1 && $lines->label == $selected) {
  600. $return .= ' selected';
  601. } elseif ($lines->code == $selected) {
  602. $return .= ' selected';
  603. }
  604. $return .= '>';
  605. $return .= $langs->trans($lines->label);
  606. $return .= '</option>';
  607. }
  608. }
  609. $return .= '</select>';
  610. }
  611. $return .= ajax_combobox($name);
  612. return $return;
  613. }
  614. /**
  615. * Return list of lot numbers (stock from product_batch) with stock location and stock qty
  616. *
  617. * @param int $selected Id of preselected lot stock id ('' for no value, 'ifone'=select value if one value otherwise no value)
  618. * @param string $htmlname Name of html select html
  619. * @param string $filterstatus lot status filter, following comma separated filter options can be used
  620. * @param int $empty 1=Can be empty, 0 if not
  621. * @param int $disabled 1=Select is disabled
  622. * @param int $fk_product show lot numbers of product with id fk_product. All from objectLines if 0.
  623. * @param int $fk_entrepot filter lot numbers for warehouse with id fk_entrepot. All if 0.
  624. * @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.
  625. * @param string $empty_label Empty label if needed (only if $empty=1)
  626. * @param int $forcecombo 1=Force combo iso ajax select2
  627. * @param array $events Events to add to select2
  628. * @param string $morecss Add more css classes to HTML select
  629. *
  630. * @return string HTML select
  631. */
  632. 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')
  633. {
  634. global $conf, $langs;
  635. dol_syslog(get_class($this)."::selectLotStock $selected, $htmlname, $filterstatus, $empty, $disabled, $fk_product, $fk_entrepot, $empty_label, $forcecombo, $morecss", LOG_DEBUG);
  636. $out = '';
  637. $productIdArray = array();
  638. if (!is_array($objectLines) || !count($objectLines)) {
  639. if (!empty($fk_product) && $fk_product > 0) {
  640. $productIdArray[] = (int) $fk_product;
  641. }
  642. } else {
  643. foreach ($objectLines as $line) {
  644. if ($line->fk_product) {
  645. $productIdArray[] = $line->fk_product;
  646. }
  647. }
  648. }
  649. $nboflot = $this->loadLotStock($productIdArray);
  650. if ($conf->use_javascript_ajax && !$forcecombo) {
  651. include_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php';
  652. $comboenhancement = ajax_combobox($htmlname, $events);
  653. $out .= $comboenhancement;
  654. }
  655. $out .= '<select class="flat'.($morecss ? ' '.$morecss : '').'"'.($disabled ? ' disabled' : '').' id="'.$htmlname.'" name="'.($htmlname.($disabled ? '_disabled' : '')).'">';
  656. if ($empty) {
  657. $out .= '<option value="-1">'.($empty_label ? $empty_label : '&nbsp;').'</option>';
  658. }
  659. if (!empty($fk_product) && $fk_product > 0) {
  660. $productIdArray = array((int) $fk_product); // only show lot stock for product
  661. } else {
  662. foreach ($this->cache_lot as $key => $value) {
  663. $productIdArray[] = $key;
  664. }
  665. }
  666. foreach ($productIdArray as $productId) {
  667. foreach ($this->cache_lot[$productId] as $id => $arraytypes) {
  668. if (empty($fk_entrepot) || $fk_entrepot == $arraytypes['entrepot_id']) {
  669. $label = $arraytypes['entrepot_label'].' - ';
  670. $label .= $arraytypes['batch'];
  671. if ($arraytypes['qty'] <= 0) {
  672. $label .= ' <span class=\'text-warning\'>('.$langs->trans("Stock").' '.$arraytypes['qty'].')</span>';
  673. } else {
  674. $label .= ' <span class=\'opacitymedium\'>('.$langs->trans("Stock").' '.$arraytypes['qty'].')</span>';
  675. }
  676. $out .= '<option value="'.$id.'"';
  677. if ($selected == $id || ($selected == 'ifone' && $nboflot == 1)) {
  678. $out .= ' selected';
  679. }
  680. $out .= ' data-html="'.dol_escape_htmltag($label).'"';
  681. $out .= '>';
  682. $out .= $label;
  683. $out .= '</option>';
  684. }
  685. }
  686. }
  687. $out .= '</select>';
  688. if ($disabled) {
  689. $out .= '<input type="hidden" name="'.$htmlname.'" value="'.(($selected > 0) ? $selected : '').'">';
  690. }
  691. return $out;
  692. }
  693. /**
  694. * Return list of lot numbers (stock from product_batch) for product and warehouse.
  695. *
  696. * @param string $htmlname Name of key that is inside attribute "list" of an input text field.
  697. * @param int $empty 1=Can be empty, 0 if not
  698. * @param int $fk_product show lot numbers of product with id fk_product. All from objectLines if 0.
  699. * @param int $fk_entrepot filter lot numbers for warehouse with id fk_entrepot. All if 0.
  700. * @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.
  701. * @return string HTML datalist
  702. */
  703. public function selectLotDataList($htmlname = 'batch_id', $empty = 0, $fk_product = 0, $fk_entrepot = 0, $objectLines = array())
  704. {
  705. global $langs, $hookmanager;
  706. dol_syslog(get_class($this)."::selectLotDataList $htmlname, $empty, $fk_product, $fk_entrepot", LOG_DEBUG);
  707. $out = '';
  708. $productIdArray = array();
  709. if (!is_array($objectLines) || !count($objectLines)) {
  710. if (!empty($fk_product) && $fk_product > 0) {
  711. $productIdArray[] = (int) $fk_product;
  712. }
  713. } else {
  714. foreach ($objectLines as $line) {
  715. if ($line->fk_product) {
  716. $productIdArray[] = $line->fk_product;
  717. }
  718. }
  719. }
  720. $nboflot = $this->loadLotStock($productIdArray);
  721. if (!empty($fk_product) && $fk_product > 0) {
  722. $productIdArray = array((int) $fk_product); // only show lot stock for product
  723. } else {
  724. foreach ($this->cache_lot as $key => $value) {
  725. $productIdArray[] = $key;
  726. }
  727. }
  728. if (empty($hookmanager)) {
  729. include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
  730. $hookmanager = new HookManager($this->db);
  731. }
  732. $hookmanager->initHooks(array('productdao'));
  733. $parameters = array('productIdArray' => $productIdArray, 'htmlname' => $htmlname);
  734. $reshook = $hookmanager->executeHooks('selectLotDataList', $parameters, $this);
  735. if ($reshook < 0) {
  736. return $hookmanager->error;
  737. } elseif ($reshook > 0) {
  738. return $hookmanager->resPrint;
  739. } else {
  740. $out .= $hookmanager->resPrint;
  741. }
  742. $out .= '<datalist id="'.$htmlname.'" >';
  743. foreach ($productIdArray as $productId) {
  744. if (array_key_exists($productId, $this->cache_lot)) {
  745. foreach ($this->cache_lot[$productId] as $id => $arraytypes) {
  746. if (empty($fk_entrepot) || $fk_entrepot == $arraytypes['entrepot_id']) {
  747. $label = $arraytypes['entrepot_label'] . ' - ';
  748. $label .= $arraytypes['batch'];
  749. $out .= '<option data-warehouse="'.dol_escape_htmltag($label).'" value="' . $arraytypes['batch'] . '">(' . $langs->trans('Stock Total') . ': ' . $arraytypes['qty'] . ')</option>';
  750. }
  751. }
  752. }
  753. }
  754. $out .= '</datalist>';
  755. return $out;
  756. }
  757. /**
  758. * Load in cache array list of lot available in stock from a given list of products
  759. *
  760. * @param array $productIdArray array of product id's from who to get lot numbers. A
  761. *
  762. * @return int Nb of loaded lines, 0 if nothing loaded, <0 if KO
  763. */
  764. private function loadLotStock($productIdArray = array())
  765. {
  766. global $conf, $langs;
  767. $cacheLoaded = false;
  768. if (empty($productIdArray)) {
  769. // only Load lot stock for given products
  770. $this->cache_lot = array();
  771. return 0;
  772. }
  773. if (count($productIdArray) && count($this->cache_lot)) {
  774. // check cache already loaded for product id's
  775. foreach ($productIdArray as $productId) {
  776. $cacheLoaded = !empty($this->cache_lot[$productId]) ? true : false;
  777. }
  778. }
  779. if ($cacheLoaded) {
  780. return count($this->cache_lot);
  781. } else {
  782. // clear cache
  783. $this->cache_lot = array();
  784. $productIdList = implode(',', $productIdArray);
  785. $batch_count = 0;
  786. global $hookmanager;
  787. if (empty($hookmanager)) {
  788. include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
  789. $hookmanager = new HookManager($this->db);
  790. }
  791. $hookmanager->initHooks(array('productdao'));
  792. $parameters = array('productIdList' => $productIdList);
  793. $reshook = $hookmanager->executeHooks('loadLotStock', $parameters, $this);
  794. if ($reshook < 0) {
  795. $this->error = $hookmanager->error;
  796. return -1;
  797. }
  798. if (!empty($hookmanager->resArray['batch_list']) && is_array($hookmanager->resArray['batch_list'])) {
  799. $this->cache_lot = $hookmanager->resArray['batch_list'];
  800. $batch_count = (int) $hookmanager->resArray['batch_count'];
  801. }
  802. if ($reshook > 0) {
  803. return $batch_count;
  804. }
  805. $sql = "SELECT pb.batch, pb.rowid, ps.fk_entrepot, pb.qty, e.ref as label, ps.fk_product";
  806. $sql .= " FROM ".$this->db->prefix()."product_batch as pb";
  807. $sql .= " LEFT JOIN ".$this->db->prefix()."product_stock as ps on ps.rowid = pb.fk_product_stock";
  808. $sql .= " LEFT JOIN ".$this->db->prefix()."entrepot as e on e.rowid = ps.fk_entrepot AND e.entity IN (".getEntity('stock').")";
  809. if (!empty($productIdList)) {
  810. $sql .= " WHERE ps.fk_product IN (".$this->db->sanitize($productIdList).")";
  811. }
  812. $sql .= " ORDER BY e.ref, pb.batch";
  813. dol_syslog(get_class($this).'::loadLotStock', LOG_DEBUG);
  814. $resql = $this->db->query($sql);
  815. if ($resql) {
  816. $num = $this->db->num_rows($resql);
  817. $i = 0;
  818. while ($i < $num) {
  819. $obj = $this->db->fetch_object($resql);
  820. $this->cache_lot[$obj->fk_product][$obj->rowid]['id'] = $obj->rowid;
  821. $this->cache_lot[$obj->fk_product][$obj->rowid]['batch'] = $obj->batch;
  822. $this->cache_lot[$obj->fk_product][$obj->rowid]['entrepot_id'] = $obj->fk_entrepot;
  823. $this->cache_lot[$obj->fk_product][$obj->rowid]['entrepot_label'] = $obj->label;
  824. $this->cache_lot[$obj->fk_product][$obj->rowid]['qty'] = $obj->qty;
  825. $i++;
  826. }
  827. return $batch_count + $num;
  828. } else {
  829. dol_print_error($this->db);
  830. return -1;
  831. }
  832. }
  833. }
  834. }