print $formproduct->selectWarehouses($id_sw, 'id_sw', '', 1); print '</td>'; // Out warehouse print '<td>'; print $formproduct->selectWarehouses($id_tw, 'id_tw', '', 1); print '</td>'; // Qty print '<td align="center"><input type="text" size="4" class="flat" name="qty" value="' . $qty . '"></td>'; // Button to add line if (!$listofdata) { print '<td align="right"><input type="submit" class="button" name="addline" value="' . dol_escape_htmltag($titletoadd) . '"></td>'; } print '</tr>'; foreach ($listofdata as $key => $val) { $var = !$var; $productstatic->fetch_opt($val['id_product']); $warehousestatics->fetch($val['id_sw']); $warehousestatict->fetch($val['id_tw']); print '<tr ' . $bc[$var] . '>'; print '<td>' . $productstatic->getNomUrl(1) . '</td>'; print '<td>'; $oldref = $productstatic->ref; $productstatic->ref = $productstatic->label; print $productstatic->getNomUrl(1); $productstatic->ref = $oldref; print '</td>'; print '<td>'; print $warehousestatics->getNomUrl(1); print '</td>'; print '<td>'; print $warehousestatict->getNomUrl(1);
/** * Add a movement of stock (in one direction only) * * @param User $user User object * @param int $fk_product Id of product * @param int $entrepot_id Id of warehouse * @param int $qty Qty of movement (can be <0 or >0) * @param int $type Direction of movement: * 0=input (stock increase after stock transfert), 1=output (stock decrease after stock transfer), * 2=output (stock decrease), 3=input (stock increase) * @param int $price Unit price HT of product, used to calculate average weighted price (PMP in french). If 0, average weighted price is not changed. * @param string $label Label of stock movement * @param string $datem Force date of movement * @param date $eatby eat-by date * @param date $sellby sell-by date * @param string $batch batch number * @param boolean $skip_sellby If set to true, stock mouvement is done without impacting batch record * @return int <0 if KO, 0 if fk_product is null, >0 if OK */ function _create($user, $fk_product, $entrepot_id, $qty, $type, $price = 0, $label = '', $datem = '', $eatby = '', $sellby = '', $batch = '', $skip_sellby = false) { global $conf, $langs; require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php'; $error = 0; 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}"); // Clean parameters if (empty($price)) { $price = 0; } if (empty($fk_product)) { return 0; } $now = !empty($datem) ? $datem : dol_now(); $this->db->begin(); $product = new Product($this->db); $result = $product->fetch_opt($fk_product); if ($result < 0) { dol_print_error('', "Failed to fetch product"); return -1; } $product->load_stock(); // Define if we must make the stock change (If product type is a service or if stock is used also for services) $movestock = 0; if ($product->type != 1 || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) { $movestock = 1; } if ($movestock && $entrepot_id > 0) { if (!empty($this->origin)) { $origintype = $this->origin->element; $fk_origin = $this->origin->id; } else { $origintype = ''; $fk_origin = 0; } $sql = "INSERT INTO " . MAIN_DB_PREFIX . "stock_mouvement"; $sql .= " (datem, fk_product, fk_entrepot, value, type_mouvement, fk_user_author, label, price, fk_origin, origintype)"; $sql .= " VALUES ('" . $this->db->idate($now) . "', " . $fk_product . ", " . $entrepot_id . ", " . $qty . ", " . $type . ","; $sql .= " " . $user->id . ","; $sql .= " '" . $this->db->escape($label) . "',"; $sql .= " '" . price2num($price) . "',"; $sql .= " '" . $fk_origin . "',"; $sql .= " '" . $origintype . "'"; $sql .= ")"; dol_syslog(get_class($this) . "::_create sql=" . $sql, LOG_DEBUG); $resql = $this->db->query($sql); if ($resql) { $mvid = $this->db->last_insert_id(MAIN_DB_PREFIX . "stock_mouvement"); } else { $this->error = $this->db->lasterror(); dol_syslog(get_class($this) . "::_create " . $this->error, LOG_ERR); $error = -1; } // Define current values for qty and pmp $oldqty = $product->stock_reel; $oldqtywarehouse = 0; $oldpmp = $product->pmp; $oldpmpwarehouse = 0; // Test if there is already a record for couple (warehouse / product) $num = 0; if (!$error) { $sql = "SELECT rowid, reel, pmp FROM " . MAIN_DB_PREFIX . "product_stock"; $sql .= " WHERE fk_entrepot = " . $entrepot_id . " AND fk_product = " . $fk_product; dol_syslog(get_class($this) . "::_create sql=" . $sql); $resql = $this->db->query($sql); if ($resql) { $obj = $this->db->fetch_object($resql); if ($obj) { $num = 1; $oldqtywarehouse = $obj->reel; $oldpmpwarehouse = $obj->pmp; $fk_product_stock = $obj->rowid; } $this->db->free($resql); } else { $this->error = $this->db->lasterror(); dol_syslog(get_class($this) . "::_create echec update " . $this->error, LOG_ERR); $error = -2; } } // Calculate new PMP. if (!$error) { $newpmp = 0; $newpmpwarehouse = 0; // Note: PMP is calculated on stock input only (type = 0 or 3). If type == 0 or 3, qty should be > 0. // Note: Price should always be >0 or 0. PMP should be always >0 (calculated on input) if (($type == 0 || $type == 3) && $price > 0) { $oldqtytouse = $oldqty >= 0 ? $oldqty : 0; // We make a test on oldpmp>0 to avoid to use normal rule on old data with no pmp field defined if ($oldpmp > 0) { $newpmp = price2num(($oldqtytouse * $oldpmp + $qty * $price) / ($oldqtytouse + $qty), 'MU'); } else { $newpmp = $price; } $oldqtywarehousetouse = $oldqtywarehouse >= 0 ? $oldqtywarehouse : 0; if ($oldpmpwarehouse > 0) { $newpmpwarehouse = price2num(($oldqtywarehousetouse * $oldpmpwarehouse + $qty * $price) / ($oldqtywarehousetouse + $qty), 'MU'); } else { $newpmpwarehouse = $price; } //print "oldqtytouse=".$oldqtytouse." oldpmp=".$oldpmp." oldqtywarehousetouse=".$oldqtywarehousetouse." oldpmpwarehouse=".$oldpmpwarehouse." "; //print "qty=".$qty." newpmp=".$newpmp." newpmpwarehouse=".$newpmpwarehouse; //exit; } else { $newpmp = $oldpmp; $newpmpwarehouse = $oldpmpwarehouse; } } // Update denormalized value of stock in product_stock and product if (!$error) { if ($num > 0) { $sql = "UPDATE " . MAIN_DB_PREFIX . "product_stock SET pmp = " . $newpmpwarehouse . ", reel = reel + " . $qty; $sql .= " WHERE fk_entrepot = " . $entrepot_id . " AND fk_product = " . $fk_product; } else { $sql = "INSERT INTO " . MAIN_DB_PREFIX . "product_stock"; $sql .= " (pmp, reel, fk_entrepot, fk_product) VALUES "; $sql .= " (" . $newpmpwarehouse . ", " . $qty . ", " . $entrepot_id . ", " . $fk_product . ")"; } dol_syslog(get_class($this) . "::_create sql=" . $sql); $resql = $this->db->query($sql); if (!$resql) { $this->error = $this->db->lasterror(); dol_syslog(get_class($this) . "::_create " . $this->error, LOG_ERR); $error = -3; } else { if (empty($fk_product_stock)) { $fk_product_stock = $this->db->last_insert_id(MAIN_DB_PREFIX . "product_stock"); } } } // Update detail stock for sell-by date if ($product->hasbatch() && !$error && !$skip_sellby) { $param_batch = array('fk_product_stock' => $fk_product_stock, 'eatby' => $eatby, 'sellby' => $sellby, 'batchnumber' => $batch); $result = $this->_create_batch($param_batch, $qty); if ($result < 0) { $error++; } } if (!$error) { $sql = "UPDATE " . MAIN_DB_PREFIX . "product SET pmp = " . $newpmp . ", stock = " . $this->db->ifsql("stock IS NULL", 0, "stock") . " + " . $qty; $sql .= " WHERE rowid = " . $fk_product; // May be this request is better: // UPDATE llx_product p SET p.stock= (SELECT SUM(ps.reel) FROM llx_product_stock ps WHERE ps.fk_product = p.rowid); dol_syslog(get_class($this) . "::_create sql=" . $sql); $resql = $this->db->query($sql); if (!$resql) { $this->error = $this->db->lasterror(); dol_syslog(get_class($this) . "::_create " . $this->error, LOG_ERR); $error = -4; } } } // Add movement for sub products (recursive call) if (!$error && !empty($conf->global->PRODUIT_SOUSPRODUITS)) { $error = $this->_createSubProduct($user, $fk_product, $entrepot_id, $qty, $type, 0, $label); // we use 0 as price, because pmp is not changed for subproduct } if ($movestock && !$error) { // Appel des triggers include_once DOL_DOCUMENT_ROOT . '/core/class/interfaces.class.php'; $interface = new Interfaces($this->db); $this->product_id = $fk_product; $this->entrepot_id = $entrepot_id; $this->qty = $qty; $result = $interface->run_triggers('STOCK_MOVEMENT', $this, $user, $langs, $conf); if ($result < 0) { $error++; $this->errors = $interface->errors; } // Fin appel triggers } if (!$error) { $this->db->commit(); return $mvid; } else { $this->db->rollback(); dol_syslog(get_class($this) . "::_create error code=" . $error, LOG_ERR); return -6; } }