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))