diff --git a/htdocs/langs/en_US/products.lang b/htdocs/langs/en_US/products.lang index 90436801571..8390eb5439d 100644 --- a/htdocs/langs/en_US/products.lang +++ b/htdocs/langs/en_US/products.lang @@ -330,6 +330,7 @@ NbOfDifferentValues=No. of different values NbProducts=No. of products ParentProduct=Parent product HideChildProducts=Hide variant products +ShowChildProducts=Show variant products ConfirmCloneProductCombinations=Would you like to copy all the product variants to the other parent product with the given reference? CloneDestinationReference=Destination product reference ErrorCopyProductCombinations=There was an error while copying the product variants diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php index 78b07ddf7cb..d4f09bafa31 100644 --- a/htdocs/product/class/product.class.php +++ b/htdocs/product/class/product.class.php @@ -4,7 +4,7 @@ * Copyright (C) 2005-2015 Regis Houssin * Copyright (C) 2006 Andre Cianfarani * Copyright (C) 2007-2011 Jean Heimburger - * Copyright (C) 2010-2013 Juanjo Menent + * Copyright (C) 2010-2018 Juanjo Menent * Copyright (C) 2012 Cedric Salvador * Copyright (C) 2013-2014 Cedric GROSS * Copyright (C) 2013-2016 Marcos García @@ -3496,6 +3496,26 @@ 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/list.php b/htdocs/product/list.php index 28f08035198..36bf5a24ef9 100644 --- a/htdocs/product/list.php +++ b/htdocs/product/list.php @@ -3,7 +3,7 @@ * Copyright (C) 2004-2018 Laurent Destailleur * Copyright (C) 2005-2012 Regis Houssin * Copyright (C) 2012-2016 Marcos García - * Copyright (C) 2013-2016 Juanjo Menent + * Copyright (C) 2013-2018 Juanjo Menent * Copyright (C) 2013-2015 Raphaël Doursenaud * Copyright (C) 2013 Jean Heimburger * Copyright (C) 2013 Cédric Salvador @@ -53,7 +53,7 @@ $sall=trim((GETPOST('search_all', 'alphanohtml')!='')?GETPOST('search_all', 'alp $search_ref=GETPOST("search_ref"); $search_barcode=GETPOST("search_barcode"); $search_label=GETPOST("search_label"); -$search_type = GETPOST("search_type",'int'); +$search_type = GETPOST("search_type", 'int'); $search_sale = GETPOST("search_sale"); $search_categ = GETPOST("search_categ",'int'); $search_tosell = GETPOST("search_tosell", 'int'); @@ -66,11 +66,11 @@ $search_accountancy_code_buy = GETPOST("search_accountancy_code_buy",'alpha'); $optioncss = GETPOST('optioncss','alpha'); $type=GETPOST("type","int"); -//Show/hide child products. Hidden by default -if (!$_POST) { - $search_hidechildproducts = 'on'; +//Show/hide child products +if (!empty($conf->variants->enabled) && ! empty($conf->global->PRODUIT_ATTRIBUTES_HIDECHILD)) { + $show_childproducts = GETPOST('search_show_childproducts'); } else { - $search_hidechildproducts = GETPOST('search_hidechildproducts'); + $show_childproducts = ''; } $diroutputmassaction=$conf->product->dir_output . '/temp/massgeneration/'.$user->id; @@ -219,6 +219,8 @@ if (empty($reshook)) $search_tobuy=""; $search_tobatch=''; //$search_type=''; // There is 2 types of list: a list of product and a list of services. No list with both. So when we clear search criteria, we must keep the filter on type. + + $show_childproducts = ''; $search_accountancy_code_sell=''; $search_accountancy_code_buy=''; $search_array_options=array(); @@ -265,7 +267,7 @@ $sql.= ' p.fk_product_type, p.duration, p.tosell, p.tobuy, p.seuil_stock_alerte, $sql.= ' p.tobatch, p.accountancy_code_sell, p.accountancy_code_sell_intra, p.accountancy_code_sell_export, p.accountancy_code_buy,'; $sql.= ' p.datec as date_creation, p.tms as date_update, p.pmp,'; $sql.= ' MIN(pfp.unitprice) as minsellprice'; -if (!empty($conf->variants->enabled) && $search_hidechildproducts && ($search_type === 0)) { +if (!empty($conf->variants->enabled) && (!empty($conf->global->PRODUIT_ATTRIBUTES_HIDECHILD) && ! $show_childproducts )) { $sql .= ', pac.rowid prod_comb_id'; } // Add fields from extrafields @@ -282,10 +284,12 @@ if (! empty($search_categ) || ! empty($catid)) $sql.= ' LEFT JOIN '.MAIN_DB_PREF $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."product_fournisseur_price as pfp ON p.rowid = pfp.fk_product"; // multilang if (! empty($conf->global->MAIN_MULTILANGS)) $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."product_lang as pl ON pl.fk_product = p.rowid AND pl.lang = '".$langs->getDefaultLang() ."'"; -if (!empty($conf->variants->enabled) && $search_hidechildproducts && ($search_type === 0)) { + +if (!empty($conf->variants->enabled) && (!empty($conf->global->PRODUIT_ATTRIBUTES_HIDECHILD) && ! $show_childproducts )) { $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product_attribute_combination pac ON pac.fk_product_child = p.rowid"; } + $sql.= ' WHERE p.entity IN ('.getEntity('product').')'; if ($sall) $sql .= natural_search(array_keys($fieldstosearchall), $sall); // if the type is not 1, we show all products (type = 0,2,3) @@ -294,6 +298,11 @@ if (dol_strlen($search_type) && $search_type != '-1') if ($search_type == 1) $sql.= " AND p.fk_product_type = 1"; else $sql.= " AND p.fk_product_type <> 1"; } + +if (!empty($conf->variants->enabled) && (!empty($conf->global->PRODUIT_ATTRIBUTES_HIDECHILD) && ! $show_childproducts )) { + $sql .= " AND pac.rowid IS NULL"; +} + if ($search_ref) $sql .= natural_search('p.ref', $search_ref); if ($search_label) $sql .= natural_search('p.label', $search_label); if ($search_barcode) $sql .= natural_search('p.barcode', $search_barcode); @@ -308,7 +317,7 @@ if ($fourn_id > 0) $sql.= " AND pfp.fk_soc = ".$fourn_id; if ($search_tobatch != '' && $search_tobatch >= 0) $sql.= " AND p.tobatch = ".$db->escape($search_tobatch); if ($search_accountancy_code_sell) $sql.= natural_search('p.accountancy_code_sell', $search_accountancy_code_sell); if ($search_accountancy_code_buy) $sql.= natural_search('p.accountancy_code_buy', $search_accountancy_code_buy); -if (!empty($conf->variants->enabled) && $search_hidechildproducts && ($search_type === 0)) $sql .= " AND pac.rowid IS NULL"; + // Add where from extra fields include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_sql.tpl.php'; // Add where from hooks @@ -318,7 +327,10 @@ $sql.=$hookmanager->resPrint; $sql.= " GROUP BY p.rowid, p.ref, p.label, p.barcode, p.price, p.price_ttc, p.price_base_type,"; $sql.= " p.fk_product_type, p.duration, p.tosell, p.tobuy, p.seuil_stock_alerte, p.desiredstock,"; $sql.= ' p.datec, p.tms, p.entity, p.tobatch, p.accountancy_code_sell, p.accountancy_code_sell_intra, p.accountancy_code_sell_export, p.accountancy_code_buy, p.pmp'; -if (!empty($conf->variants->enabled) && $search_hidechildproducts && ($search_type === 0)) $sql .= ', pac.rowid'; + +if (!empty($conf->variants->enabled) && (!empty($conf->global->PRODUIT_ATTRIBUTES_HIDECHILD) && ! $show_childproducts )) { + $sql .= ', pac.rowid'; +} // Add fields from extrafields if (! empty($extrafields->attributes[$object->table_element]['label'])) { foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $val) $sql.=($extrafields->attributes[$object->table_element]['type'][$key] != 'separate' ? ", ef.".$key : ''); @@ -392,6 +404,7 @@ if ($resql) if ($search_tobuy != '') $param.="&search_tobuy=".urlencode($search_tobuy); if ($fourn_id > 0) $param.=($fourn_id?"&fourn_id=".$fourn_id:""); if ($seach_categ) $param.=($search_categ?"&search_categ=".urlencode($search_categ):""); + if ($show_childproducts) $param.=($show_childproducts?"&search_show_childproducts=".urlencode($show_childproducts):""); if ($type != '') $param.='&type='.urlencode($type); if ($search_type != '') $param.='&search_type='.urlencode($search_type); if ($optioncss != '') $param.='&optioncss='.urlencode($optioncss); @@ -467,10 +480,10 @@ if ($resql) } //Show/hide child products. Hidden by default - if (!empty($conf->variants->enabled) && $search_type === 0) { + if (!empty($conf->variants->enabled) && !empty($conf->global->PRODUIT_ATTRIBUTES_HIDECHILD )) { $moreforfilter.='
'; - $moreforfilter.= ''; - $moreforfilter.= ' '; + $moreforfilter.= ''; + $moreforfilter.= ' '; $moreforfilter.='
'; } diff --git a/htdocs/product/stock/product.php b/htdocs/product/stock/product.php index 324e14047d4..dc291eb0fdc 100644 --- a/htdocs/product/stock/product.php +++ b/htdocs/product/stock/product.php @@ -5,7 +5,7 @@ * Copyright (C) 2005 Simon TOSSER * Copyright (C) 2005-2009 Regis Houssin * Copyright (C) 2013 Cédric Salvador - * Copyright (C) 2013-2015 Juanjo Menent + * Copyright (C) 2013-2018 Juanjo Menent * Copyright (C) 2014-2015 Cédric Gross * Copyright (C) 2015 Marcos García * Copyright (C) 2018 Frédéric France @@ -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 '
'; print ''; - if ($conf->productbatch->enabled) - { - print ''; - } + if (! $variants) { - // PMP - print ''; - print ''; - print ''; + if ($conf->productbatch->enabled) { + print ''; + } - // Minimum Price - print ''; - print ''; + // PMP + print ''; + print ''; + print ''; - if (empty($conf->global->PRODUIT_MULTIPRICES)) - { - // Price - print ''; + print ''; - // Price minimum - print ''; + + // Price minimum + print ''; } else { - print price($object->price_min) . ' ' . $langs->trans($object->price_base_type); + // Price + print ''; + + // Price minimum + print ''; } - print ''; - } - else - { - // Price - print ''; - // Price minimum - 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 ''; + 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 "'; + 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 '"; } - - // Stock alert threshold - 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 ''; - - // 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 ''; - 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 "'; - 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 '"; - print "
'.$langs->trans("ManageLotSerial").''; - print $object->getLibStatut(0,2); - print '
'.$langs->trans("AverageUnitPricePMP").''; - if ($object->pmp > 0) print price($object->pmp).' '.$langs->trans("HT"); - print '
' . $langs->trans("ManageLotSerial") . ''; + print $object->getLibStatut(0, 2); + print '
'.$langs->trans("BuyingPriceMin").''; - $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 '
' . $langs->trans("AverageUnitPricePMP") . ''; + if ($object->pmp > 0) print price($object->pmp) . ' ' . $langs->trans("HT"); + 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") . ''; + $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 '
' . $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 '
' . $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 '
' . $langs->trans("SellingPrice") . ''; + print $langs->trans("Variable"); + print '
' . $langs->trans("MinPrice") . ''; + print $langs->trans("Variable"); + 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 '
' . $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 '
'; + print $form->textwithpicto($langs->trans("PhysicalStock"), $text_stock_options, 1); + 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 $form->textwithpicto($langs->trans("VirtualStock"), $langs->trans("VirtualStockDesc")); + 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 '
' . $langs->trans("LastMovement") . ''; + if ($lastmovementdate) { + print dol_print_date($lastmovementdate, 'dayhour') . ' '; + print '(' . $langs->trans("FullList") . ')'; + } else { + print '' . $langs->trans("None") . ''; + } + 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 '
'.$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 '
'; - print $form->textwithpicto($langs->trans("PhysicalStock"), $text_stock_options, 1); - 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 $form->textwithpicto($langs->trans("VirtualStock"), $langs->trans("VirtualStockDesc")); - 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 '
'.$langs->trans("LastMovement").''; - if ($lastmovementdate) - { - print dol_print_date($lastmovementdate,'dayhour').' '; - print '('.$langs->trans("FullList").')'; - } - else - { - print ''.$langs->trans("None").''; - } - print "
"; print ''; @@ -770,229 +766,305 @@ if (empty($reshook)) { print "
\n"; - if ($user->rights->stock->mouvement->creer) - { - print ''.$langs->trans("CorrectStock").''; - } + if (! $variants) { - //if (($user->rights->stock->mouvement->creer) && ! $object->hasbatch()) - if ($user->rights->stock->mouvement->creer) - { - print ''.$langs->trans("TransferStock").''; + if ($user->rights->stock->mouvement->creer) { + print '' . $langs->trans("CorrectStock") . ''; + } + + //if (($user->rights->stock->mouvement->creer) && ! $object->hasbatch()) + if ($user->rights->stock->mouvement->creer) { + print '' . $langs->trans("TransferStock") . ''; + } } - print '
'; } } -/* - * Stock detail (by warehouse). May go down into batch details. - */ +if (! $variants) { + /* + * Stock detail (by warehouse). May go down into batch details. + */ -print '
'; -print ''; -print ''; -print ''; -print ''; -print ''; -print ''; -print ''; -print ''; -print ''; -if ((! empty($conf->productbatch->enabled)) && $object->hasbatch()) -{ - print ''; - print ''; - print ''; - print ''; - print ''; - print ''; - print ''; - print ''; - print ''; - print ''; -} - -$sql = "SELECT e.rowid, e.ref as label, e.lieu, ps.reel, ps.rowid as product_stock_id, p.pmp"; -$sql.= " FROM ".MAIN_DB_PREFIX."entrepot as e,"; -$sql.= " ".MAIN_DB_PREFIX."product_stock as ps"; -$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = ps.fk_product"; -$sql.= " WHERE ps.reel != 0"; -$sql.= " AND ps.fk_entrepot = e.rowid"; -$sql.= " AND e.entity IN (".getEntity('stock').")"; -$sql.= " AND ps.fk_product = ".$object->id; -$sql.= " ORDER BY e.ref"; - -$entrepotstatic=new Entrepot($db); -$product_lot_static=new Productlot($db); - -$total=0; -$totalvalue=$totalvaluesell=0; - -$resql=$db->query($sql); -if ($resql) -{ - $num = $db->num_rows($resql); - $total=$totalwithpmp; - $i=0; $var=false; - while ($i < $num) - { - $obj = $db->fetch_object($resql); - $entrepotstatic->id=$obj->rowid; - $entrepotstatic->libelle=$obj->label; - $entrepotstatic->lieu=$obj->lieu; - $stock_real = price2num($obj->reel, 'MS'); - print ''; - print ''; - print ''; - // PMP - print ''; - // Value purchase - print ''; - // Sell price - print ''; - // Value sell - print ''; - else print $langs->trans("Variable"); - print ''; ; - $total += $obj->reel; - if (price2num($object->pmp)) $totalwithpmp += $obj->reel; - $totalvalue = $totalvalue + ($object->pmp*$obj->reel); - $totalvaluesell = $totalvaluesell + ($object->price*$obj->reel); - // Batch Detail - if ((! empty($conf->productbatch->enabled)) && $object->hasbatch()) - { - $details=Productbatch::findAll($db, $obj->product_stock_id, 0, $object->id); - if ($details<0) dol_print_error($db); - foreach ($details as $pdluo) - { - $product_lot_static->id = $pdluo->lotid; - $product_lot_static->batch = $pdluo->batch; - $product_lot_static->eatby = $pdluo->eatby; - $product_lot_static->sellby = $pdluo->sellby; - - if ($action == 'editline' && GETPOST('lineid','int') == $pdluo->id) - { //Current line edit - print "\n".''; - print ''; - } - else - { - print "\n".''; - print ''; - print ''; - print ''; - print ''; - print ''; - } - } - } - $i++; - - } -} -else dol_print_error($db); - -print ''; -print ''; -print ''; -// Value purchase -print ''; -print ''; -// Value to sell -print ''; -print ""; -print "
'.$langs->trans("Warehouse").''.$langs->trans("NumberOfUnit").''.$langs->trans("AverageUnitPricePMPShort").''.$langs->trans("EstimatedStockValueShort").''.$langs->trans("SellPriceMin").''.$langs->trans("EstimatedStockValueSellShort").'
'.$langs->trans("batch_number").''.$langs->trans("EatByDate").''.$langs->trans("SellByDate").'
'.$entrepotstatic->getNomUrl(1).''.$stock_real.($stock_real < 0 ?' '.img_warning():'').''.(price2num($object->pmp)?price2num($object->pmp,'MU'):'').''.(price2num($object->pmp)?price(price2num($object->pmp*$obj->reel,'MT')):'').''; - if (empty($conf->global->PRODUIT_MULTIPRICES)) print price(price2num($object->price,'MU'),1); - else print $langs->trans("Variable"); - print ''; - if (empty($conf->global->PRODUIT_MULTIPRICES)) print price(price2num($object->price*$obj->reel,'MT'),1).'
'; - print '
'; - print ''; - print ''; - print ''; - print ''; - print ''; - print ''; - print '
'; - print $form->selectDate($pdluo->eatby,'eatby','','',1,'',1,0); - print ''; - print $form->selectDate($pdluo->sellby,'sellby','','',1,'',1,0); - print ''.$pdluo->qty.($pdluo->qty<0?' '.img_warning():'').''; - print '
'; - print '
'; - print '
'; - print img_picto($langs->trans("Tranfer"),'uparrow','class="hideonsmartphone"').' '; - print 'id.'">'.$langs->trans("TransferStock").''; - // Disabled, because edition of stock content must use the "Correct stock menu". - // Do not use this, or data will be wrong (bad tracking of movement label, inventory code, ... - //print 'id.'#'.$pdluo->id.'">'; - //print img_edit().''; - print $product_lot_static->getNomUrl(1); - print ''. dol_print_date($pdluo->eatby,'day') .''. dol_print_date($pdluo->sellby,'day') .''.$pdluo->qty.($pdluo->qty<0?' '.img_warning():'').'
'.$langs->trans("Total").':'.price2num($total, 'MS').''; -print ($totalwithpmp?price(price2num($totalvalue/$totalwithpmp,'MU')):' '); // This value may have rounding errors -print ''; -print $totalvalue?price(price2num($totalvalue,'MT'),1):' '; -print ''; -if (empty($conf->global->PRODUIT_MULTIPRICES)) print ($total?price($totalvaluesell/$total,1):' '); -else print $langs->trans("Variable"); -print ''; -if (empty($conf->global->PRODUIT_MULTIPRICES)) print price(price2num($totalvaluesell,'MT'),1); -else print $langs->trans("Variable"); -print '
"; -print '
'; - -if (!empty($conf->global->STOCK_ALLOW_ADD_LIMIT_STOCK_BY_WAREHOUSE)) -{ - print '

'; - print_titre($langs->trans('AddNewProductStockWarehouse')); - - if (!empty($user->rights->produit->creer)){ - print '
'; - print ''; - print ''; - } + print '
'; print ''; - if (!empty($user->rights->produit->creer)){ - print ''; - print ''; - print ''; - print ''; - print ''; - }else{ - print ''; - print ''; - print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + if ((!empty($conf->productbatch->enabled)) && $object->hasbatch()) { + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; print ''; } - $pse = new ProductStockEntrepot($db); - $lines = $pse->fetchAll($id); + $sql = "SELECT e.rowid, e.ref as label, e.lieu, ps.reel, ps.rowid as product_stock_id, p.pmp"; + $sql .= " FROM " . MAIN_DB_PREFIX . "entrepot as e,"; + $sql .= " " . MAIN_DB_PREFIX . "product_stock as ps"; + $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "product as p ON p.rowid = ps.fk_product"; + $sql .= " WHERE ps.reel != 0"; + $sql .= " AND ps.fk_entrepot = e.rowid"; + $sql .= " AND e.entity IN (" . getEntity('stock') . ")"; + $sql .= " AND ps.fk_product = " . $object->id; + $sql .= " ORDER BY e.ref"; - if (!empty($lines)) - { - $var=false; - foreach($lines as $line) - { - $ent = new Entrepot($db); - $ent->fetch($line['fk_entrepot']); - print ''; - print ''; - print ''; - if (!empty($user->rights->produit->creer)){ - print ''; + $entrepotstatic = new Entrepot($db); + $product_lot_static = new Productlot($db); + + $total = 0; + $totalvalue = $totalvaluesell = 0; + + $resql = $db->query($sql); + if ($resql) { + $num = $db->num_rows($resql); + $total = $totalwithpmp; + $i = 0; + $var = false; + while ($i < $num) { + $obj = $db->fetch_object($resql); + $entrepotstatic->id = $obj->rowid; + $entrepotstatic->libelle = $obj->label; + $entrepotstatic->lieu = $obj->lieu; + $stock_real = price2num($obj->reel, 'MS'); + print ''; + print ''; + print ''; + // PMP + print ''; + // Value purchase + print ''; + // Sell price + print ''; + // Value sell + print ''; + else print $langs->trans("Variable"); + print '';; + $total += $obj->reel; + if (price2num($object->pmp)) $totalwithpmp += $obj->reel; + $totalvalue = $totalvalue + ($object->pmp * $obj->reel); + $totalvaluesell = $totalvaluesell + ($object->price * $obj->reel); + // Batch Detail + if ((!empty($conf->productbatch->enabled)) && $object->hasbatch()) { + $details = Productbatch::findAll($db, $obj->product_stock_id, 0, $object->id); + if ($details < 0) dol_print_error($db); + foreach ($details as $pdluo) { + $product_lot_static->id = $pdluo->lotid; + $product_lot_static->batch = $pdluo->batch; + $product_lot_static->eatby = $pdluo->eatby; + $product_lot_static->sellby = $pdluo->sellby; + + if ($action == 'editline' && GETPOST('lineid', 'int') == $pdluo->id) { //Current line edit + print "\n" . ''; + print ''; + } else { + print "\n" . ''; + print ''; + print ''; + print ''; + print ''; + print ''; + } + } } + $i++; + + } + } else dol_print_error($db); + + print ''; + print ''; + print ''; +// Value purchase + print ''; + print ''; +// Value to sell + print ''; + print ""; + print "
'.$formproduct->selectWarehouses('', 'fk_entrepot').'
'.$langs->trans("Warehouse").''.$langs->trans("StockLimit").''.$langs->trans("DesiredStock").'
' . $langs->trans("Warehouse") . '' . $langs->trans("NumberOfUnit") . '' . $langs->trans("AverageUnitPricePMPShort") . '' . $langs->trans("EstimatedStockValueShort") . '' . $langs->trans("SellPriceMin") . '' . $langs->trans("EstimatedStockValueSellShort") . '
' . $langs->trans("batch_number") . '' . $langs->trans("EatByDate") . '' . $langs->trans("SellByDate") . '
'.$ent->getNomUrl(3).''.$line['seuil_stock_alerte'].''.$line['desiredstock'].''.img_delete().'
' . $entrepotstatic->getNomUrl(1) . '' . $stock_real . ($stock_real < 0 ? ' ' . img_warning() : '') . '' . (price2num($object->pmp) ? price2num($object->pmp, 'MU') : '') . '' . (price2num($object->pmp) ? price(price2num($object->pmp * $obj->reel, 'MT')) : '') . ''; + if (empty($conf->global->PRODUIT_MULTIPRICES)) print price(price2num($object->price, 'MU'), 1); + else print $langs->trans("Variable"); + print ''; + if (empty($conf->global->PRODUIT_MULTIPRICES)) print price(price2num($object->price * $obj->reel, 'MT'), 1) . '
'; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print ''; + print '
'; + print $form->selectDate($pdluo->eatby, 'eatby', '', '', 1, '', 1, 0); + print ''; + print $form->selectDate($pdluo->sellby, 'sellby', '', '', 1, '', 1, 0); + print '' . $pdluo->qty . ($pdluo->qty < 0 ? ' ' . img_warning() : '') . ''; + print '
'; + print ''; + print '
'; + print img_picto($langs->trans("Tranfer"), 'uparrow', 'class="hideonsmartphone"') . ' '; + print 'id . '">' . $langs->trans("TransferStock") . ''; + // Disabled, because edition of stock content must use the "Correct stock menu". + // Do not use this, or data will be wrong (bad tracking of movement label, inventory code, ... + //print 'id.'#'.$pdluo->id.'">'; + //print img_edit().''; + print $product_lot_static->getNomUrl(1); + print '' . dol_print_date($pdluo->eatby, 'day') . '' . dol_print_date($pdluo->sellby, 'day') . '' . $pdluo->qty . ($pdluo->qty < 0 ? ' ' . img_warning() : '') . '
' . $langs->trans("Total") . ':' . price2num($total, 'MS') . ''; + print ($totalwithpmp ? price(price2num($totalvalue / $totalwithpmp, 'MU')) : ' '); // This value may have rounding errors + print ''; + print $totalvalue ? price(price2num($totalvalue, 'MT'), 1) : ' '; + print ''; + if (empty($conf->global->PRODUIT_MULTIPRICES)) print ($total ? price($totalvaluesell / $total, 1) : ' '); + else print $langs->trans("Variable"); + print ''; + if (empty($conf->global->PRODUIT_MULTIPRICES)) print price(price2num($totalvaluesell, 'MT'), 1); + else print $langs->trans("Variable"); + print '
"; + print '
'; + + if (!empty($conf->global->STOCK_ALLOW_ADD_LIMIT_STOCK_BY_WAREHOUSE)) { + print '

'; + print_titre($langs->trans('AddNewProductStockWarehouse')); + + if (!empty($user->rights->produit->creer)) { + print '
'; + print ''; + print ''; + } + print ''; + if (!empty($user->rights->produit->creer)) { + print ''; + print ''; + print ''; + print ''; + print ''; + } else { + print ''; + print ''; + print ''; print ''; } - } - print "
' . $formproduct->selectWarehouses('', 'fk_entrepot') . '
' . $langs->trans("Warehouse") . '' . $langs->trans("StockLimit") . '' . $langs->trans("DesiredStock") . '
"; + $pse = new ProductStockEntrepot($db); + $lines = $pse->fetchAll($id); - if (!empty($user->rights->produit->creer)){ - print '
'; + if (!empty($lines)) { + $var = false; + foreach ($lines as $line) { + $ent = new Entrepot($db); + $ent->fetch($line['fk_entrepot']); + print '' . $ent->getNomUrl(3) . ''; + print '' . $line['seuil_stock_alerte'] . ''; + print '' . $line['desiredstock'] . ''; + if (!empty($user->rights->produit->creer)) { + print '' . img_delete() . ''; + } + print ''; + } + } + + print ""; + + if (!empty($user->rights->produit->creer)) { + print ''; + } } +} else { + // List of variants + + $prodstatic = new Product($db); + $prodcomb = new ProductCombination($db); + $comb2val = new ProductCombination2ValuePair($db); + $productCombinations = $prodcomb->fetchAllByFkProductParent($object->id); + + print '
'; + print ''; + print ''; + print ''; + print ''; + + // load variants + $title = $langs->trans("ProductCombinations"); + + print_barre_liste($title, 0, $_SERVER["PHP_SELF"], '', $sortfield, $sortorder, '', 0); + + print '
'; + ?> + + + + + + + + + + fetch($currcomb->fk_product_child); + $prodstatic->load_stock(); + $stock_total+=$prodstatic->stock_reel; + ?> + + + + + + + + + + '; + print ''; + print ''; + print ''; + } + else + { + print ''; + } + ?> +
trans('Product') ?>trans('Combination') ?>trans('OnSell') ?>trans('OnBuy') ?>trans('Stock') ?>
getNomUrl(1) ?> + fetchByFkCombination($currcomb->id); + $iMax = count($productCombination2ValuePairs); + + for ($i = 0; $i < $iMax; $i++) { + echo dol_htmlentities($productCombination2ValuePairs[$i]); + + if ($i !== ($iMax - 1)) { + echo ', '; + } + } ?> + getLibStatut(2, 0) ?>getLibStatut(2, 1) ?>stock_reel ?> + +
'.$langs->trans("Total").''.$stock_total.'
'.$langs->trans("None").'
+ + '; + + print ''; } // End of page diff --git a/htdocs/variants/class/ProductCombination.class.php b/htdocs/variants/class/ProductCombination.class.php index 58eb2178da4..8908bfa4d2d 100644 --- a/htdocs/variants/class/ProductCombination.class.php +++ b/htdocs/variants/class/ProductCombination.class.php @@ -1,6 +1,7 @@ + * Copyright (C) 2018 Juanjo Menent * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -634,7 +635,7 @@ WHERE c.fk_product_parent = ".(int) $productid." AND p.tosell = 1"; } $db->commit(); - return 1; + return $newproduct->id; } /**