diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php
index 050d756ad82..3257f9bbde5 100644
--- a/htdocs/product/class/product.class.php
+++ b/htdocs/product/class/product.class.php
@@ -3493,6 +3493,25 @@ class Product extends CommonObject
return $nb;
}
+ /**
+ * Return if a product has variants or not
+ *
+ * @return int Number of variants
+ */
+ function hasVariants() {
+ $nb = 0;
+ $sql = "SELECT count(rowid) as nb FROM ".MAIN_DB_PREFIX."product_attribute_combination WHERE fk_product_parent = ".$this->id;
+ $sql.= " AND entity IN (".getEntity('product').")";
+
+ $resql = $this->db->query($sql);
+ if ($resql) {
+ $obj = $this->db->fetch_object($resql);
+ if ($obj) $nb = $obj->nb;
+ }
+
+ return $nb;
+ }
+
/**
* Return all parent products for current product (first level only)
*
diff --git a/htdocs/product/stock/product.php b/htdocs/product/stock/product.php
index 324e14047d4..51871736a3d 100644
--- a/htdocs/product/stock/product.php
+++ b/htdocs/product/stock/product.php
@@ -45,6 +45,13 @@ if (! empty($conf->projet->enabled))
require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
}
+if (! empty($conf->variants->enabled)) {
+ require_once DOL_DOCUMENT_ROOT . '/variants/class/ProductAttribute.class.php';
+ require_once DOL_DOCUMENT_ROOT . '/variants/class/ProductAttributeValue.class.php';
+ require_once DOL_DOCUMENT_ROOT . '/variants/class/ProductCombination.class.php';
+ require_once DOL_DOCUMENT_ROOT . '/variants/class/ProductCombination2ValuePair.class.php';
+}
+
// Load translation files required by the page
$langs->loadlangs(array('products', 'orders', 'bills', 'stocks', 'sendings'));
if (! empty($conf->productbatch->enabled)) $langs->load("productbatch");
@@ -518,6 +525,8 @@ if ($id > 0 || $ref)
$object = new Product($db);
$result = $object->fetch($id,$ref);
+ $variants = $object->hasVariants();
+
$object->load_stock();
$title = $langs->trans('ProductServiceCard');
@@ -558,174 +567,161 @@ if ($id > 0 || $ref)
print '
';
- if ($conf->productbatch->enabled)
- {
- print '| '.$langs->trans("ManageLotSerial").' | ';
- print $object->getLibStatut(0,2);
- print ' |
';
- }
+ if (! $variants) {
- // PMP
- print '| '.$langs->trans("AverageUnitPricePMP").' | ';
- print '';
- if ($object->pmp > 0) print price($object->pmp).' '.$langs->trans("HT");
- print ' | ';
- print '
';
+ if ($conf->productbatch->enabled) {
+ print '| ' . $langs->trans("ManageLotSerial") . ' | ';
+ print $object->getLibStatut(0, 2);
+ print ' |
';
+ }
- // Minimum Price
- print '| '.$langs->trans("BuyingPriceMin").' | ';
- print '';
- $product_fourn = new ProductFournisseur($db);
- if ($product_fourn->find_min_price_product_fournisseur($object->id) > 0)
- {
- if ($product_fourn->product_fourn_price_id > 0) print $product_fourn->display_price_product_fournisseur();
- else print $langs->trans("NotDefined");
- }
- print ' |
';
+ // PMP
+ print '| ' . $langs->trans("AverageUnitPricePMP") . ' | ';
+ print '';
+ if ($object->pmp > 0) print price($object->pmp) . ' ' . $langs->trans("HT");
+ print ' | ';
+ print '
';
- if (empty($conf->global->PRODUIT_MULTIPRICES))
- {
- // Price
- print '| ' . $langs->trans("SellingPrice") . ' | ';
- if ($object->price_base_type == 'TTC') {
- print price($object->price_ttc) . ' ' . $langs->trans($object->price_base_type);
- } else {
- print price($object->price) . ' ' . $langs->trans($object->price_base_type);
+ // Minimum Price
+ print ' |
| ' . $langs->trans("BuyingPriceMin") . ' | ';
+ print '';
+ $product_fourn = new ProductFournisseur($db);
+ if ($product_fourn->find_min_price_product_fournisseur($object->id) > 0) {
+ if ($product_fourn->product_fourn_price_id > 0) print $product_fourn->display_price_product_fournisseur();
+ else print $langs->trans("NotDefined");
}
print ' |
';
- // Price minimum
- print '| ' . $langs->trans("MinPrice") . ' | ';
- if ($object->price_base_type == 'TTC') {
- print price($object->price_min_ttc) . ' ' . $langs->trans($object->price_base_type);
+ if (empty($conf->global->PRODUIT_MULTIPRICES)) {
+ // Price
+ print ' |
| ' . $langs->trans("SellingPrice") . ' | ';
+ if ($object->price_base_type == 'TTC') {
+ print price($object->price_ttc) . ' ' . $langs->trans($object->price_base_type);
+ } else {
+ print price($object->price) . ' ' . $langs->trans($object->price_base_type);
+ }
+ print ' |
';
+
+ // Price minimum
+ print '| ' . $langs->trans("MinPrice") . ' | ';
+ if ($object->price_base_type == 'TTC') {
+ print price($object->price_min_ttc) . ' ' . $langs->trans($object->price_base_type);
+ } else {
+ print price($object->price_min) . ' ' . $langs->trans($object->price_base_type);
+ }
+ print ' |
';
} else {
- print price($object->price_min) . ' ' . $langs->trans($object->price_base_type);
+ // Price
+ print '| ' . $langs->trans("SellingPrice") . ' | ';
+ print $langs->trans("Variable");
+ print ' |
';
+
+ // Price minimum
+ print '| ' . $langs->trans("MinPrice") . ' | ';
+ print $langs->trans("Variable");
+ print ' |
';
}
- print '';
- }
- else
- {
- // Price
- print '| ' . $langs->trans("SellingPrice") . ' | ';
- print $langs->trans("Variable");
+
+ // Stock alert threshold
+ print ' |
| ' . $form->editfieldkey($form->textwithpicto($langs->trans("StockLimit"), $langs->trans("StockLimitDesc"), 1), 'seuil_stock_alerte', $object->seuil_stock_alerte, $object, $user->rights->produit->creer) . ' | ';
+ print $form->editfieldval("StockLimit", 'seuil_stock_alerte', $object->seuil_stock_alerte, $object, $user->rights->produit->creer, 'string');
print ' |
';
- // Price minimum
- print '| ' . $langs->trans("MinPrice") . ' | ';
- print $langs->trans("Variable");
+ // Hook formObject
+ $parameters = array();
+ $reshook = $hookmanager->executeHooks('formObjectOptions', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
+ print $hookmanager->resPrint;
+
+ // Desired stock
+ print ' |
| ' . $form->editfieldkey($form->textwithpicto($langs->trans("DesiredStock"), $langs->trans("DesiredStockDesc"), 1), 'desiredstock', $object->desiredstock, $object, $user->rights->produit->creer);
+ print ' | ';
+ print $form->editfieldval("DesiredStock", 'desiredstock', $object->desiredstock, $object, $user->rights->produit->creer, 'string');
print ' |
';
+
+ // Real stock
+ $text_stock_options = $langs->trans("RealStockDesc") . '
';
+ $text_stock_options .= $langs->trans("RealStockWillAutomaticallyWhen") . '
';
+ $text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT) || !empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE) ? $langs->trans("DeStockOnShipment") . '
' : '');
+ $text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_VALIDATE_ORDER) ? $langs->trans("DeStockOnValidateOrder") . '
' : '');
+ $text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_BILL) ? $langs->trans("DeStockOnBill") . '
' : '');
+ $text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_BILL) ? $langs->trans("ReStockOnBill") . '
' : '');
+ $text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER) ? $langs->trans("ReStockOnValidateOrder") . '
' : '');
+ $text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER) ? $langs->trans("ReStockOnDispatchOrder") . '
' : '');
+ print '| ';
+ print $form->textwithpicto($langs->trans("PhysicalStock"), $text_stock_options, 1);
+ print ' | ';
+ print '' . price2num($object->stock_reel, 'MS');
+ if ($object->seuil_stock_alerte != '' && ($object->stock_reel < $object->seuil_stock_alerte)) print ' ' . img_warning($langs->trans("StockLowerThanLimit", $object->seuil_stock_alerte));
+ print ' | ';
+ print '
';
+
+ $stocktheo = price2num($object->stock_theorique, 'MS');
+
+ $found = 0;
+ $helpondiff = '' . $langs->trans("StockDiffPhysicTeoric") . ':
';
+ // Number of customer orders running
+ if (!empty($conf->commande->enabled)) {
+ if ($found) $helpondiff .= '
'; else $found = 1;
+ $helpondiff .= $langs->trans("ProductQtyInCustomersOrdersRunning") . ': ' . $object->stats_commande['qty'];
+ $result = $object->load_stats_commande(0, '0', 1);
+ if ($result < 0) dol_print_error($db, $object->error);
+ $helpondiff .= ' (' . $langs->trans("ProductQtyInDraft") . ': ' . $object->stats_commande['qty'] . ')';
+ }
+
+ // Number of product from customer order already sent (partial shipping)
+ if (!empty($conf->expedition->enabled)) {
+ if ($found) $helpondiff .= '
'; else $found = 1;
+ $result = $object->load_stats_sending(0, '2', 1);
+ $helpondiff .= $langs->trans("ProductQtyInShipmentAlreadySent") . ': ' . $object->stats_expedition['qty'];
+ }
+
+ // Number of supplier order running
+ if (!empty($conf->fournisseur->enabled)) {
+ if ($found) $helpondiff .= '
'; else $found = 1;
+ $result = $object->load_stats_commande_fournisseur(0, '3,4', 1);
+ $helpondiff .= $langs->trans("ProductQtyInSuppliersOrdersRunning") . ': ' . $object->stats_commande_fournisseur['qty'];
+ $result = $object->load_stats_commande_fournisseur(0, '0,1,2', 1);
+ if ($result < 0) dol_print_error($db, $object->error);
+ $helpondiff .= ' (' . $langs->trans("ProductQtyInDraftOrWaitingApproved") . ': ' . $object->stats_commande_fournisseur['qty'] . ')';
+ }
+
+ // Number of product from supplier order already received (partial receipt)
+ if (!empty($conf->fournisseur->enabled)) {
+ if ($found) $helpondiff .= '
'; else $found = 1;
+ $helpondiff .= $langs->trans("ProductQtyInSuppliersShipmentAlreadyRecevied") . ': ' . $object->stats_reception['qty'];
+ }
+
+ // Calculating a theorical value
+ print '| ';
+ print $form->textwithpicto($langs->trans("VirtualStock"), $langs->trans("VirtualStockDesc"));
+ print ' | ';
+ print "";
+ //print (empty($stocktheo)?0:$stocktheo);
+ print $form->textwithpicto((empty($stocktheo) ? 0 : $stocktheo), $helpondiff);
+ if ($object->seuil_stock_alerte != '' && ($object->stock_theorique < $object->seuil_stock_alerte)) print ' ' . img_warning($langs->trans("StockLowerThanLimit", $object->seuil_stock_alerte));
+ print ' | ';
+ print '
';
+
+ // Last movement
+ $sql = "SELECT max(m.datem) as datem";
+ $sql .= " FROM " . MAIN_DB_PREFIX . "stock_mouvement as m";
+ $sql .= " WHERE m.fk_product = '" . $object->id . "'";
+ $resqlbis = $db->query($sql);
+ if ($resqlbis) {
+ $obj = $db->fetch_object($resqlbis);
+ $lastmovementdate = $db->jdate($obj->datem);
+ } else {
+ dol_print_error($db);
+ }
+ print '| ' . $langs->trans("LastMovement") . ' | ';
+ if ($lastmovementdate) {
+ print dol_print_date($lastmovementdate, 'dayhour') . ' ';
+ print '(' . $langs->trans("FullList") . ')';
+ } else {
+ print '' . $langs->trans("None") . '';
+ }
+ print " |
";
}
-
- // Stock alert threshold
- print '| '.$form->editfieldkey($form->textwithpicto($langs->trans("StockLimit"), $langs->trans("StockLimitDesc"), 1),'seuil_stock_alerte',$object->seuil_stock_alerte,$object,$user->rights->produit->creer).' | ';
- print $form->editfieldval("StockLimit",'seuil_stock_alerte',$object->seuil_stock_alerte,$object,$user->rights->produit->creer,'string');
- print ' |
';
-
- // Hook formObject
- $parameters=array();
- $reshook=$hookmanager->executeHooks('formObjectOptions',$parameters,$object,$action); // Note that $action and $object may have been modified by hook
- print $hookmanager->resPrint;
-
- // Desired stock
- print '| '.$form->editfieldkey($form->textwithpicto($langs->trans("DesiredStock"), $langs->trans("DesiredStockDesc"), 1),'desiredstock',$object->desiredstock,$object,$user->rights->produit->creer);
- print ' | ';
- print $form->editfieldval("DesiredStock",'desiredstock',$object->desiredstock,$object,$user->rights->produit->creer,'string');
- print ' |
';
-
- // Real stock
- $text_stock_options = $langs->trans("RealStockDesc").'
';
- $text_stock_options.= $langs->trans("RealStockWillAutomaticallyWhen").'
';
- $text_stock_options.= (! empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT) || ! empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE)?$langs->trans("DeStockOnShipment").'
':'');
- $text_stock_options.= (! empty($conf->global->STOCK_CALCULATE_ON_VALIDATE_ORDER)?$langs->trans("DeStockOnValidateOrder").'
':'');
- $text_stock_options.= (! empty($conf->global->STOCK_CALCULATE_ON_BILL)?$langs->trans("DeStockOnBill").'
':'');
- $text_stock_options.= (! empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_BILL)?$langs->trans("ReStockOnBill").'
':'');
- $text_stock_options.= (! empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER)?$langs->trans("ReStockOnValidateOrder").'
':'');
- $text_stock_options.= (! empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER)?$langs->trans("ReStockOnDispatchOrder").'
':'');
- print '| ';
- print $form->textwithpicto($langs->trans("PhysicalStock"), $text_stock_options, 1);
- print ' | ';
- print ''.price2num($object->stock_reel, 'MS');
- if ($object->seuil_stock_alerte != '' && ($object->stock_reel < $object->seuil_stock_alerte)) print ' '.img_warning($langs->trans("StockLowerThanLimit", $object->seuil_stock_alerte));
- print ' | ';
- print '
';
-
- $stocktheo = price2num($object->stock_theorique, 'MS');
-
- $found=0;
- $helpondiff=''.$langs->trans("StockDiffPhysicTeoric").':
';
- // Number of customer orders running
- if (! empty($conf->commande->enabled))
- {
- if ($found) $helpondiff.='
'; else $found=1;
- $helpondiff.=$langs->trans("ProductQtyInCustomersOrdersRunning").': '.$object->stats_commande['qty'];
- $result=$object->load_stats_commande(0,'0', 1);
- if ($result < 0) dol_print_error($db,$object->error);
- $helpondiff.=' ('.$langs->trans("ProductQtyInDraft").': '.$object->stats_commande['qty'].')';
- }
-
- // Number of product from customer order already sent (partial shipping)
- if (! empty($conf->expedition->enabled))
- {
- if ($found) $helpondiff.='
'; else $found=1;
- $result=$object->load_stats_sending(0,'2', 1);
- $helpondiff.=$langs->trans("ProductQtyInShipmentAlreadySent").': '.$object->stats_expedition['qty'];
- }
-
- // Number of supplier order running
- if (! empty($conf->fournisseur->enabled))
- {
- if ($found) $helpondiff.='
'; else $found=1;
- $result=$object->load_stats_commande_fournisseur(0,'3,4', 1);
- $helpondiff.=$langs->trans("ProductQtyInSuppliersOrdersRunning").': '.$object->stats_commande_fournisseur['qty'];
- $result=$object->load_stats_commande_fournisseur(0,'0,1,2', 1);
- if ($result < 0) dol_print_error($db,$object->error);
- $helpondiff.=' ('.$langs->trans("ProductQtyInDraftOrWaitingApproved").': '.$object->stats_commande_fournisseur['qty'].')';
- }
-
- // Number of product from supplier order already received (partial receipt)
- if (! empty($conf->fournisseur->enabled))
- {
- if ($found) $helpondiff.='
'; else $found=1;
- $helpondiff.=$langs->trans("ProductQtyInSuppliersShipmentAlreadyRecevied").': '.$object->stats_reception['qty'];
- }
-
- // Calculating a theorical value
- print '| ';
- print $form->textwithpicto($langs->trans("VirtualStock"), $langs->trans("VirtualStockDesc"));
- print ' | ';
- print "";
- //print (empty($stocktheo)?0:$stocktheo);
- print $form->textwithpicto((empty($stocktheo)?0:$stocktheo), $helpondiff);
- if ($object->seuil_stock_alerte != '' && ($object->stock_theorique < $object->seuil_stock_alerte)) print ' '.img_warning($langs->trans("StockLowerThanLimit", $object->seuil_stock_alerte));
- print ' | ';
- print '
';
-
- // Last movement
- $sql = "SELECT max(m.datem) as datem";
- $sql.= " FROM ".MAIN_DB_PREFIX."stock_mouvement as m";
- $sql.= " WHERE m.fk_product = '".$object->id."'";
- $resqlbis = $db->query($sql);
- if ($resqlbis)
- {
- $obj = $db->fetch_object($resqlbis);
- $lastmovementdate=$db->jdate($obj->datem);
- }
- else
- {
- dol_print_error($db);
- }
- print '| '.$langs->trans("LastMovement").' | ';
- if ($lastmovementdate)
- {
- print dol_print_date($lastmovementdate,'dayhour').' ';
- print '('.$langs->trans("FullList").')';
- }
- else
- {
- print ''.$langs->trans("None").'';
- }
- print " |
";
-
print "
";
print '';
@@ -770,229 +766,305 @@ if (empty($reshook))
{
print "