diff --git a/htdocs/langs/en_US/products.lang b/htdocs/langs/en_US/products.lang index fd9a823c420..4f3545ae43a 100644 --- a/htdocs/langs/en_US/products.lang +++ b/htdocs/langs/en_US/products.lang @@ -380,3 +380,4 @@ ErrorProductCombinationNotFound=Product variant not found ActionAvailableOnVariantProductOnly=Action only available on the variant of product ProductsPricePerCustomer=Product prices per customers ProductSupplierExtraFields=Additional Attributes (Supplier Prices) +DeleteLinkedProduct=Delete the child product linked to the combination \ No newline at end of file diff --git a/htdocs/langs/fr_FR/products.lang b/htdocs/langs/fr_FR/products.lang index ad22750d1fe..d88b6cab7b9 100644 --- a/htdocs/langs/fr_FR/products.lang +++ b/htdocs/langs/fr_FR/products.lang @@ -375,4 +375,4 @@ ErrorDestinationProductNotFound=Produit destination non trouvé ErrorProductCombinationNotFound=Variante du produit non trouvé ActionAvailableOnVariantProductOnly=Action disponible uniquement sur la variante du produit ProductsPricePerCustomer=Prix produit par clients -ProductSupplierExtraFields=Attributs supplémentaires (Prix fournisseur) +ProductSupplierExtraFields=Attributs supplémentaires (Prix fournisseur) \ No newline at end of file diff --git a/htdocs/product/class/api_products.class.php b/htdocs/product/class/api_products.class.php index dc4d2e79481..d7d72e28b7c 100644 --- a/htdocs/product/class/api_products.class.php +++ b/htdocs/product/class/api_products.class.php @@ -630,6 +630,73 @@ class Products extends DolibarrApi ); } + /** + * Add/Update purchase prices for a product. + * + * @param int $id ID of Product + * @param float $qty Min quantity for which price is valid + * @param float $buyprice Purchase price for the quantity min + * @param string $price_base_type HT or TTC + * @param int $fourn_id Supplier ID + * @param int $availability Product availability + * @param string $ref_fourn Supplier ref + * @param float $tva_tx New VAT Rate (For example 8.5. Should not be a string) + * @param string $charges costs affering to product + * @param float $remise_percent Discount regarding qty (percent) + * @param float $remise Discount regarding qty (amount) + * @param int $newnpr Set NPR or not + * @param int $delivery_time_days Delay in days for delivery (max). May be '' if not defined. + * @param string $supplier_reputation Reputation with this product to the defined supplier (empty, FAVORITE, DONOTORDER) + * @param array $localtaxes_array Array with localtaxes info array('0'=>type1,'1'=>rate1,'2'=>type2,'3'=>rate2) (loaded by getLocalTaxesFromRate(vatrate, 0, ...) function). + * @param string $newdefaultvatcode Default vat code + * @param float $multicurrency_buyprice Purchase price for the quantity min in currency + * @param string $multicurrency_price_base_type HT or TTC in currency + * @param float $multicurrency_tx Rate currency + * @param string $multicurrency_code Currency code + * @param string $desc_fourn Custom description for product_fourn_price + * @param string $barcode Barcode + * @param int $fk_barcode_type Barcode type + * @return int + * + * @throws RestException 500 + * @throws RestException 401 + * + * @url POST {id}/purchase_prices + */ + public function addPurchasePrice($id, $qty, $buyprice, $price_base_type, $fourn_id, $availability, $ref_fourn, $tva_tx, $charges = 0, $remise_percent = 0, $remise = 0, $newnpr = 0, $delivery_time_days = 0, $supplier_reputation = '', $localtaxes_array = array(), $newdefaultvatcode = '', $multicurrency_buyprice = 0, $multicurrency_price_base_type = 'HT', $multicurrency_tx = 1, $multicurrency_code = '', $desc_fourn = '', $barcode = '', $fk_barcode_type = null) + { + if(! DolibarrApiAccess::$user->rights->produit->creer) { + throw new RestException(401); + } + + $result = $this->productsupplier->fetch($id); + if (!$result) { + throw new RestException(404, 'Product not found'); + } + + if (!DolibarrApi::_checkAccessToResource('product', $this->productsupplier->id)) { + throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login); + } + + $result = $this->productsupplier->add_fournisseur(DolibarrApiAccess::$user, $fourn_id, $ref_fourn, $qty); + if ($result < 0) { + throw new RestException(500, "Error adding supplier to product : ".$this->db->lasterror()); + } + + $fourn = new Fournisseur($this->db); + $result = $fourn->fetch($fourn_id); + if ($result <= 0) { + throw new RestException(404, 'Supplier not found'); + } + + $result = $this->productsupplier->update_buyprice($qty, $buyprice, DolibarrApiAccess::$user, $price_base_type, $fourn, $availability, $ref_fourn, $tva_tx, $charges, $remise_percent, $remise, $newnpr, $delivery_time_days, $supplier_reputation, $localtaxes_array, $newdefaultvatcode, $multicurrency_buyprice, $multicurrency_price_base_type, $multicurrency_tx, $multicurrency_code, $desc_fourn, $barcode, $fk_barcode_type); + + if ($result <= 0) { + throw new RestException(500, "Error updating buy price : ".$this->db->lasterror()); + } + return (int) $this->productsupplier->product_fourn_price_id; + } + /** * Delete purchase price for a product * @@ -1325,11 +1392,12 @@ class Products extends DolibarrApi * * "features" is a list of attributes pairs id_attribute=>id_value. Example: array(id_color=>id_Blue, id_size=>id_small, id_option=>id_val_a, ...) * - * @param int $id ID of Product - * @param float $weight_impact Weight impact of variant - * @param float $price_impact Price impact of variant - * @param bool $price_impact_is_percent Price impact in percent (true or false) - * @param array $features List of attributes pairs id_attribute->id_value. Example: array(id_color=>id_Blue, id_size=>id_small, id_option=>id_val_a, ...) + * @param int $id ID of Product + * @param float $weight_impact Weight impact of variant + * @param float $price_impact Price impact of variant + * @param bool $price_impact_is_percent Price impact in percent (true or false) + * @param array $features List of attributes pairs id_attribute->id_value. Example: array(id_color=>id_Blue, id_size=>id_small, id_option=>id_val_a, ...) + * @param bool|string $reference Customized reference of variant * @return int * * @throws RestException 500 @@ -1338,7 +1406,7 @@ class Products extends DolibarrApi * * @url POST {id}/variants */ - public function addVariant($id, $weight_impact, $price_impact, $price_impact_is_percent, $features) + public function addVariant($id, $weight_impact, $price_impact, $price_impact_is_percent, $features, $reference = false) { if (!DolibarrApiAccess::$user->rights->produit->creer) { throw new RestException(401); @@ -1368,17 +1436,13 @@ class Products extends DolibarrApi } $prodcomb = new ProductCombination($this->db); - if (!$prodcomb->fetchByProductCombination2ValuePairs($id, $features)) + + $result = $prodcomb->createProductCombination(DolibarrApiAccess::$user, $this->product, $features, array(), $price_impact_is_percent, $price_impact, $weight_impact, $reference); + if ($result > 0) { - $result = $prodcomb->createProductCombination(DolibarrApiAccess::$user, $this->product, $features, array(), $price_impact_is_percent, $price_impact, $weight_impact); - if ($result > 0) - { - return $result; - } else { - throw new RestException(500, "Error creating new product variant"); - } + return $result; } else { - return $prodcomb->id; + throw new RestException(500, "Error creating new product variant"); } } diff --git a/htdocs/variants/class/ProductCombination.class.php b/htdocs/variants/class/ProductCombination.class.php index 1c09aae62df..fc58d338220 100644 --- a/htdocs/variants/class/ProductCombination.class.php +++ b/htdocs/variants/class/ProductCombination.class.php @@ -502,9 +502,10 @@ WHERE c.fk_product_parent = ".(int) $productid." AND p.tosell = 1"; * @param bool $price_var_percent Is the price variation a relative variation? * @param bool|float $forced_pricevar If the price variation is forced * @param bool|float $forced_weightvar If the weight variation is forced + * @param bool|string $forced_refvar If the reference is forced * @return int <0 KO, >0 OK */ - public function createProductCombination(User $user, Product $product, array $combinations, array $variations, $price_var_percent = false, $forced_pricevar = false, $forced_weightvar = false) + public function createProductCombination(User $user, Product $product, array $combinations, array $variations, $price_var_percent = false, $forced_pricevar = false, $forced_weightvar = false, $forced_refvar = false) { global $db, $conf; @@ -513,7 +514,23 @@ WHERE c.fk_product_parent = ".(int) $productid." AND p.tosell = 1"; $db->begin(); - $newproduct = clone $product; + $forced_refvar = trim($forced_refvar); + + if (!empty($forced_refvar) && $forced_refvar != $product->ref) { + $existingProduct = new Product($db); + $result = $existingProduct->fetch('', $forced_refvar); + if ($result > 0) { + $newproduct = $existingProduct; + } else { + $existingProduct = false; + $newproduct = clone $product; + $newproduct->ref = $forced_refvar; + } + } else { + $forced_refvar = false; + $existingProduct = false; + $newproduct = clone $product; + } //Final weight impact $weight_impact = $forced_weightvar; @@ -536,7 +553,6 @@ WHERE c.fk_product_parent = ".(int) $productid." AND p.tosell = 1"; $newcomb = $existingCombination; } else { $newcomb->fk_product_parent = $product->id; - if ($newcomb->create($user) < 0) { // Create 1 entry into product_attribute_combination (1 entry for all combinations) $db->rollback(); return -1; @@ -573,10 +589,12 @@ WHERE c.fk_product_parent = ".(int) $productid." AND p.tosell = 1"; $price_impact += (float) price2num($variations[$currcombattr][$currcombval]['price']); } - if (isset($conf->global->PRODUIT_ATTRIBUTES_SEPARATOR)) { - $newproduct->ref .= $conf->global->PRODUIT_ATTRIBUTES_SEPARATOR . $prodattrval->ref; - } else { - $newproduct->ref .= '_'.$prodattrval->ref; + if ($forced_refvar === false) { + if (isset($conf->global->PRODUIT_ATTRIBUTES_SEPARATOR)) { + $newproduct->ref .= $conf->global->PRODUIT_ATTRIBUTES_SEPARATOR . $prodattrval->ref; + } else { + $newproduct->ref .= '_'.$prodattrval->ref; + } } //The first one should not contain a linebreak @@ -592,58 +610,65 @@ WHERE c.fk_product_parent = ".(int) $productid." AND p.tosell = 1"; $newproduct->weight += $weight_impact; - //To avoid wrong information in price history log - $newproduct->price = 0; - $newproduct->price_ttc = 0; - $newproduct->price_min = 0; - $newproduct->price_min_ttc = 0; - - // A new variant must use a new barcode (not same product) - $newproduct->barcode = -1; - // Now create the product //print 'Create prod '.$newproduct->ref.'
'."\n"; - $newprodid = $newproduct->create($user); - if ($newprodid < 0) - { - //In case the error is not related with an already existing product - if ($newproduct->error != 'ErrorProductAlreadyExists') { - $this->error[] = $newproduct->error; - $this->errors = $newproduct->errors; - $db->rollback(); - return -1; - } + if ($existingProduct === false) { + //To avoid wrong information in price history log + $newproduct->price = 0; + $newproduct->price_ttc = 0; + $newproduct->price_min = 0; + $newproduct->price_min_ttc = 0; - /** - * If there is an existing combination, then we update the prices and weight - * Otherwise, we try adding a random number to the ref - */ + // A new variant must use a new barcode (not same product) + $newproduct->barcode = -1; + $result = $newproduct->create($user); - if ($newcomb->fk_product_child) { - $res = $newproduct->fetch($existingCombination->fk_product_child); - } else { - $orig_prod_ref = $newproduct->ref; - $i = 1; + if ($result < 0) + { + //In case the error is not related with an already existing product + if ($newproduct->error != 'ErrorProductAlreadyExists') { + $this->error[] = $newproduct->error; + $this->errors = $newproduct->errors; + $db->rollback(); + return -1; + } - do { - $newproduct->ref = $orig_prod_ref.$i; - $res = $newproduct->create($user); + /** + * If there is an existing combination, then we update the prices and weight + * Otherwise, we try adding a random number to the ref + */ - if ($newproduct->error != 'ErrorProductAlreadyExists') { - $this->errors[] = $newproduct->error; - break; - } + if ($newcomb->fk_product_child) { + $res = $newproduct->fetch($existingCombination->fk_product_child); + } else { + $orig_prod_ref = $newproduct->ref; + $i = 1; - $i++; - } while ($res < 0); - } + do { + $newproduct->ref = $orig_prod_ref.$i; + $res = $newproduct->create($user); - if ($res < 0) { - $db->rollback(); - return -1; - } + if ($newproduct->error != 'ErrorProductAlreadyExists') { + $this->errors[] = $newproduct->error; + break; + } - $newproduct->weight += $weight_impact; + $i++; + } while ($res < 0); + } + + if ($res < 0) { + $db->rollback(); + return -1; + } + } + } else { + $result = $newproduct->update($newproduct->id, $user); + if ($result < 0) + { + $db->rollback(); + return -1; + } } $newcomb->fk_product_child = $newproduct->id; diff --git a/htdocs/variants/combinations.php b/htdocs/variants/combinations.php index 306586ec504..8c85577c68e 100644 --- a/htdocs/variants/combinations.php +++ b/htdocs/variants/combinations.php @@ -33,6 +33,7 @@ $ref = GETPOST('ref', 'alpha'); $weight_impact = GETPOST('weight_impact', 'alpha'); $price_impact = GETPOST('price_impact', 'alpha'); $price_impact_percent = (bool) GETPOST('price_impact_percent'); +$reference = GETPOST('reference', 'alpha'); $form = new Form($db); $action = GETPOST('action', 'alpha'); @@ -41,6 +42,7 @@ $show_files = GETPOST('show_files', 'int'); $confirm = GETPOST('confirm', 'alpha'); $toselect = GETPOST('toselect', 'array'); $cancel = GETPOST('cancel', 'alpha'); +$delete_product = GETPOST('delete_product', 'alpha'); // Security check $fieldvalue = (!empty($id) ? $id : $ref); @@ -106,6 +108,10 @@ if ($_POST) { } else { + $reference = trim($reference); + if (empty($reference)) { + $reference = false; + } $weight_impact = price2num($weight_impact); $price_impact = price2num($price_impact); $sanit_features = array(); @@ -141,7 +147,7 @@ if ($_POST) { if (!$prodcomb->fetchByProductCombination2ValuePairs($id, $sanit_features)) { - $result = $prodcomb->createProductCombination($user, $object, $sanit_features, array(), $price_impact_percent, $price_impact, $weight_impact); + $result = $prodcomb->createProductCombination($user, $object, $sanit_features, array(), $price_impact_percent, $price_impact, $weight_impact, $reference); if ($result > 0) { setEventMessages($langs->trans('RecordSaved'), null, 'mesgs'); @@ -242,7 +248,7 @@ if ($action === 'confirm_deletecombination') { if ($prodcomb->fetch($valueid) > 0) { $db->begin(); - if ($prodcomb->delete($user) > 0 && $prodstatic->fetch($prodcomb->fk_product_child) > 0 && $prodstatic->delete($user) > 0) { + if ($prodcomb->delete($user) > 0 && (empty($delete_product) || ($delete_product == 'on' && $prodstatic->fetch($prodcomb->fk_product_child) > 0 && $prodstatic->delete($user) > 0))) { $db->commit(); setEventMessages($langs->trans('RecordSaved'), null, 'mesgs'); header('Location: '.dol_buildpath('/variants/combinations.php?id='.$object->id, 2)); @@ -590,6 +596,10 @@ if (!empty($id) || !empty($ref)) + + + + @@ -629,7 +639,7 @@ if (!empty($id) || !empty($ref)) $langs->trans('Delete'), $langs->trans('ProductCombinationDeleteDialog', $prodstatic->ref), "confirm_deletecombination", - '', + array(array('label'=> $langs->trans('DeleteLinkedProduct'),'type'=> 'checkbox', 'name' => 'delete_product', 'value' => false)), 0, 1 );