PageRenderTime 45ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/htdocs/product/stock/class/mouvementstock.class.php

https://bitbucket.org/speedealing/speedealing
PHP | 361 lines | 210 code | 42 blank | 109 comment | 35 complexity | 05507781c2b693f52e39cbbf5bf391a4 MD5 | raw file
Possible License(s): LGPL-3.0, LGPL-2.1, GPL-3.0, MIT
  1. <?php
  2. /* Copyright (C) 2003-2006 Rodolphe Quiedeville <rodolphe@quiedeville.org>
  3. * Copyright (C) 2005-2009 Laurent Destailleur <eldy@users.sourceforge.net>
  4. * Copyright (C) 2011 Jean Heimburger <jean@tiaris.info>
  5. * Copyright (C) 2010-2011 Herve Prot <herve.prot@symeos.com>
  6. *
  7. * This program is free software; you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation; either version 3 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. /**
  21. * \file htdocs/product/stock/class/mouvementstock.class.php
  22. * \ingroup stock
  23. * \brief Fichier de la classe de gestion des mouvements de stocks
  24. */
  25. /**
  26. * Class to manage stock movements
  27. */
  28. class MouvementStock
  29. {
  30. var $error;
  31. var $db;
  32. /**
  33. * Constructor
  34. *
  35. * @param DoliDB $db Database handler
  36. */
  37. function __construct($db = '')
  38. {
  39. $this->db = $db;
  40. }
  41. /**
  42. * Add a movement of stock (in one direction only)
  43. *
  44. * @param User $user User object
  45. * @param int $fk_product Id of product
  46. * @param int $entrepot_id Id of warehouse
  47. * @param int $qty Qty of movement (can be <0 or >0)
  48. * @param int $type Direction of movement:
  49. * 0=input (stock increase after stock transfert), 1=output (stock decrease after stock transfer),
  50. * 2=output (stock decrease), 3=input (stock increase)
  51. * @param int $price Unit price HT of product
  52. * @param string $label Label of stock movement
  53. * @param string $datem Force date of movement
  54. * @return int <0 if KO, 0 if fk_product is null, >0 if OK
  55. */
  56. function _create($user, $fk_product, $entrepot_id, $qty, $type, $price=0, $label='', $datem='')
  57. {
  58. global $conf, $langs;
  59. $error = 0;
  60. dol_syslog(get_class($this)."::_create start userid=$user->id, fk_product=$fk_product, warehouse=$entrepot_id, qty=$qty, type=$type, price=$price label=$label");
  61. if (empty($fk_product)) return 0;
  62. $now=(! empty($datem) ? $datem : dol_now());
  63. $this->db->begin();
  64. $product = new Product($this->db);
  65. $result=$product->fetch($fk_product);
  66. if ($result < 0)
  67. {
  68. dol_print_error('',"Failed to fetch product");
  69. return -1;
  70. }
  71. $movestock=0;
  72. if ($product->type != 1 || ! empty($conf->global->STOCK_SUPPORTS_SERVICES)) $movestock=1;
  73. if ($movestock && $entrepot_id > 0) // Change stock for current product, change for subproduct is done after
  74. {
  75. $sql = "INSERT INTO ".MAIN_DB_PREFIX."stock_mouvement";
  76. $sql.= " (datem, fk_product, fk_entrepot, value, type_mouvement, fk_user_author, label, price)";
  77. $sql.= " VALUES ('".$this->db->idate($now)."', ".$fk_product.", ".$entrepot_id.", ".$qty.", ".$type.",";
  78. $sql.= " ".$user->id.",";
  79. $sql.= " '".$this->db->escape($label)."',";
  80. $sql.= " '".price2num($price)."')";
  81. dol_syslog(get_class($this)."::_create sql=".$sql, LOG_DEBUG);
  82. $resql = $this->db->query($sql);
  83. if ($resql)
  84. {
  85. $mvid = $this->db->last_insert_id(MAIN_DB_PREFIX."stock_mouvement");
  86. }
  87. else
  88. {
  89. $this->error=$this->db->lasterror();
  90. dol_syslog(get_class($this)."::_create ".$this->error, LOG_ERR);
  91. $error = -1;
  92. }
  93. // Define current values for qty and pmp
  94. $oldqty=$product->stock_reel;
  95. $oldqtywarehouse=0;
  96. $oldpmp=$product->pmp;
  97. $oldpmpwarehouse=0;
  98. // Test if there is already a record for couple (warehouse / product)
  99. $num = 0;
  100. if (! $error)
  101. {
  102. $sql = "SELECT rowid, reel, pmp FROM ".MAIN_DB_PREFIX."product_stock";
  103. $sql.= " WHERE fk_entrepot = ".$entrepot_id." AND fk_product = ".$fk_product;
  104. dol_syslog(get_class($this)."::_create sql=".$sql);
  105. $resql=$this->db->query($sql);
  106. if ($resql)
  107. {
  108. $obj = $this->db->fetch_object($resql);
  109. if ($obj)
  110. {
  111. $num = 1;
  112. $oldqtywarehouse = $obj->reel;
  113. $oldpmpwarehouse = $obj->pmp;
  114. }
  115. $this->db->free($resql);
  116. }
  117. else
  118. {
  119. $this->error=$this->db->lasterror();
  120. dol_syslog(get_class($this)."::_create echec update ".$this->error, LOG_ERR);
  121. $error = -2;
  122. }
  123. }
  124. // Calculate new PMP.
  125. if (! $error)
  126. {
  127. $newpmp=0;
  128. $newpmpwarehouse=0;
  129. // Note: PMP is calculated on stock input only (type = 0 or 3). If type == 0 or 3, qty should be > 0.
  130. // Note: Price should always be >0 or 0. PMP should be always >0 (calculated on input)
  131. if (($type == 0 || $type == 3) && $price > 0)
  132. {
  133. $oldqtytouse=($oldqty >= 0?$oldqty:0);
  134. // We make a test on oldpmp>0 to avoid to use normal rule on old data with no pmp field defined
  135. if ($oldpmp > 0) $newpmp=price2num((($oldqtytouse * $oldpmp) + ($qty * $price)) / ($oldqtytouse + $qty), 'MU');
  136. else $newpmp=$price;
  137. $oldqtywarehousetouse=($oldqtywarehouse >= 0?$oldqtywarehouse:0);
  138. if ($oldpmpwarehouse > 0) $newpmpwarehouse=price2num((($oldqtywarehousetouse * $oldpmpwarehouse) + ($qty * $price)) / ($oldqtywarehousetouse + $qty), 'MU');
  139. else $newpmpwarehouse=$price;
  140. //print "oldqtytouse=".$oldqtytouse." oldpmp=".$oldpmp." oldqtywarehousetouse=".$oldqtywarehousetouse." oldpmpwarehouse=".$oldpmpwarehouse." ";
  141. //print "qty=".$qty." newpmp=".$newpmp." newpmpwarehouse=".$newpmpwarehouse;
  142. //exit;
  143. }
  144. else
  145. {
  146. $newpmp = $oldpmp;
  147. $newpmpwarehouse = $oldpmpwarehouse;
  148. }
  149. }
  150. // Update denormalized value of stock in product_stock and product
  151. if (! $error)
  152. {
  153. if ($num > 0)
  154. {
  155. $sql = "UPDATE ".MAIN_DB_PREFIX."product_stock SET pmp = ".$newpmpwarehouse.", reel = reel + ".$qty;
  156. $sql.= " WHERE fk_entrepot = ".$entrepot_id." AND fk_product = ".$fk_product;
  157. }
  158. else
  159. {
  160. $sql = "INSERT INTO ".MAIN_DB_PREFIX."product_stock";
  161. $sql.= " (pmp, reel, fk_entrepot, fk_product) VALUES ";
  162. $sql.= " (".$newpmpwarehouse.", ".$qty.", ".$entrepot_id.", ".$fk_product.")";
  163. }
  164. dol_syslog(get_class($this)."::_create sql=".$sql);
  165. $resql=$this->db->query($sql);
  166. if (! $resql)
  167. {
  168. $this->error=$this->db->lasterror();
  169. dol_syslog(get_class($this)."::_create ".$this->error, LOG_ERR);
  170. $error = -3;
  171. }
  172. }
  173. if (! $error)
  174. {
  175. $sql = "UPDATE ".MAIN_DB_PREFIX."product SET pmp = ".$newpmp.", stock = ".$this->db->ifsql("stock IS NULL", 0, "stock") . " + ".$qty;
  176. $sql.= " WHERE rowid = ".$fk_product;
  177. // May be this request is better:
  178. // UPDATE llx_product p SET p.stock= (SELECT SUM(ps.reel) FROM llx_product_stock ps WHERE ps.fk_product = p.rowid);
  179. dol_syslog(get_class($this)."::_create sql=".$sql);
  180. $resql=$this->db->query($sql);
  181. if (! $resql)
  182. {
  183. $this->error=$this->db->lasterror();
  184. dol_syslog(get_class($this)."::_create ".$this->error, LOG_ERR);
  185. $error = -4;
  186. }
  187. }
  188. }
  189. // Add movement for sub products (recursive call)
  190. if (! $error && ! empty($conf->global->PRODUIT_SOUSPRODUITS))
  191. {
  192. $error = $this->_createSubProduct($user, $fk_product, $entrepot_id, $qty, $type, 0, $label); // pmp is not change for subproduct
  193. }
  194. // Composition module (this is an external module)
  195. /* Removed. This code must be provided by module on trigger STOCK_MOVEMENT
  196. if (! $error && $qty < 0 && ! empty($conf->global->MAIN_MODULE_COMPOSITION))
  197. {
  198. $error = $this->_createProductComposition($user, $fk_product, $entrepot_id, $qty, $type, 0, $label); // pmp is not change for subproduct
  199. }*/
  200. if ($movestock && ! $error)
  201. {
  202. // Appel des triggers
  203. include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php';
  204. $interface=new Interfaces($this->db);
  205. $this->product_id = $fk_product;
  206. $this->entrepot_id = $entrepot_id;
  207. $this->qty = $qty;
  208. $result=$interface->run_triggers('STOCK_MOVEMENT',$this,$user,$langs,$conf);
  209. if ($result < 0) { $error++; $this->errors=$interface->errors; }
  210. // Fin appel triggers
  211. }
  212. if (! $error)
  213. {
  214. $this->db->commit();
  215. return 1;
  216. }
  217. else
  218. {
  219. $this->db->rollback();
  220. dol_syslog(get_class($this)."::_create error code=".$error, LOG_ERR);
  221. return -6;
  222. }
  223. }
  224. /**
  225. * Create movement in database for all subproducts
  226. *
  227. * @param User $user Object user
  228. * @param int $idProduct Id product
  229. * @param int $entrepot_id Warehouse id
  230. * @param int $qty Quantity
  231. * @param int $type Type
  232. * @param int $price Price
  233. * @param string $label Label of movement
  234. * @return int <0 if KO, 0 if OK
  235. */
  236. function _createSubProduct($user, $idProduct, $entrepot_id, $qty, $type, $price=0, $label='')
  237. {
  238. $error = 0;
  239. $pids = array();
  240. $pqtys = array();
  241. $sql = "SELECT fk_product_pere, fk_product_fils, qty";
  242. $sql.= " FROM ".MAIN_DB_PREFIX."product_association";
  243. $sql.= " WHERE fk_product_pere = ".$idProduct;
  244. dol_syslog(get_class($this)."::_createSubProduct sql=".$sql, LOG_DEBUG);
  245. $resql=$this->db->query($sql);
  246. if ($resql)
  247. {
  248. $i=0;
  249. while ($obj=$this->db->fetch_object($resql))
  250. {
  251. $pids[$i]=$obj->fk_product_fils;
  252. $pqtys[$i]=$obj->qty;
  253. $i++;
  254. }
  255. $this->db->free($resql);
  256. }
  257. else
  258. {
  259. dol_syslog(get_class($this)."::_createSubProduct ".$this->error, LOG_ERR);
  260. $error = -2;
  261. }
  262. // Create movement for each subproduct
  263. foreach($pids as $key => $value)
  264. {
  265. $this->_create($user, $pids[$key], $entrepot_id, ($qty * $pqtys[$key]), $type, 0, $label);
  266. }
  267. return $error;
  268. }
  269. /**
  270. * Decrease stock for product and subproducts
  271. *
  272. * @param User $user Object user
  273. * @param int $fk_product Id product
  274. * @param int $entrepot_id Warehouse id
  275. * @param int $qty Quantity
  276. * @param int $price Price
  277. * @param string $label Label of stock movement
  278. * @param string $datem Force date of movement
  279. * @return int <0 if KO, >0 if OK
  280. */
  281. function livraison($user, $fk_product, $entrepot_id, $qty, $price=0, $label='', $datem='')
  282. {
  283. return $this->_create($user, $fk_product, $entrepot_id, (0 - $qty), 2, $price, $label, $datem);
  284. }
  285. /**
  286. * Increase stock for product and subproducts
  287. *
  288. * @param User $user Object user
  289. * @param int $fk_product Id product
  290. * @param int $entrepot_id Warehouse id
  291. * @param int $qty Quantity
  292. * @param int $price Price
  293. * @param string $label Label of stock movement
  294. * @return int <0 if KO, >0 if OK
  295. */
  296. function reception($user, $fk_product, $entrepot_id, $qty, $price=0, $label='')
  297. {
  298. return $this->_create($user, $fk_product, $entrepot_id, $qty, 3, $price, $label);
  299. }
  300. /**
  301. * Return nb of subproducts lines for a product
  302. *
  303. * @param int $id Id of product
  304. * @return int <0 if KO, nb of subproducts if OK
  305. */
  306. function nbOfSubProdcuts($id)
  307. {
  308. $nbSP=0;
  309. $resql = "SELECT count(*) as nb FROM ".MAIN_DB_PREFIX."product_association";
  310. $resql.= " WHERE fk_product_pere = ".$id;
  311. if ($this->db->query($resql))
  312. {
  313. $obj=$this->db->fetch_object($resql);
  314. $nbSP=$obj->nb;
  315. }
  316. return $nbSP;
  317. }
  318. }
  319. ?>