diff --git a/htdocs/admin/stock.php b/htdocs/admin/stock.php index 6b2c626da39b2808e1df97a8b3a8756b46c20605..0592b738fddfa7b8ad0ae264a997d253ffedec08 100644 --- a/htdocs/admin/stock.php +++ b/htdocs/admin/stock.php @@ -87,6 +87,9 @@ if($action) if($action == 'STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT') { $res = dolibarr_set_const($db, "STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT", GETPOST('STOCK_MUST_BE_ENOUGH_FOR_SHIPMENT','alpha'),'chaine',0,'',$conf->entity); } + if($action == 'INDEPENDANT_SUBPRODUCT_STOCK') { + $res = dolibarr_set_const($db, "INDEPENDANT_SUBPRODUCT_STOCK", GETPOST('INDEPENDANT_SUBPRODUCT_STOCK','alpha'),'chaine',0,'',$conf->entity); + } if (! $res > 0) $error++; @@ -339,9 +342,29 @@ print '</form>'; print "</td>\n"; print "</tr>\n"; print '<br>'; -print '</table>'; -print '<br>'; +/* I keep the option/feature, but hidden to end users for the moment. If feature is used by module, no need to have users see it. +If not used by a module, I still need to understand in which case user may need this now we can set rule on product page. +if ($conf->global->PRODUIT_SOUSPRODUITS) +{ + $var=!$var; + + print "<tr ".$bc[$var].">"; + print '<td width="60%">'.$langs->trans("IndependantSubProductStock").'</td>'; + + print '<td width="160" align="right">'; + print "<form method=\"post\" action=\"stock.php\">"; + print '<input type="hidden" name="token" value="'.$_SESSION['newtoken'].'">'; + print "<input type=\"hidden\" name=\"action\" value=\"INDEPENDANT_SUBPRODUCT_STOCK\">"; + print $form->selectyesno("INDEPENDANT_SUBPRODUCT_STOCK",$conf->global->INDEPENDANT_SUBPRODUCT_STOCK,1); + print '<input type="submit" class="button" value="'.$langs->trans("Modify").'">'; + print '</form>'; + print "</td>\n"; + print "</tr>\n"; +} +*/ + +print '</table>'; llxFooter(); diff --git a/htdocs/langs/en_US/products.lang b/htdocs/langs/en_US/products.lang index b896dc955e6585130f31c303345eca48c7ce65b0..3d52da2cd03d5cd76c3a082e7680b314e50aab8e 100755 --- a/htdocs/langs/en_US/products.lang +++ b/htdocs/langs/en_US/products.lang @@ -250,4 +250,7 @@ PriceExpressionEditorHelp3=In both product/service and supplier prices there are PriceExpressionEditorHelp4=In product/service price only: <b>#supplier_min_price#</b><br>In supplier prices only: <b>#supplier_quantity# and #supplier_tva_tx#</b> PriceMode=Price mode PriceNumeric=Number -DefaultPrice=Default price \ No newline at end of file +DefaultPrice=Default price +ComposedProductDecreaseStock=Decrease Stock for sub-product +ComposedProduct=Sub-product +MinSupplierPrice=Minimun supplier price diff --git a/htdocs/langs/en_US/stocks.lang b/htdocs/langs/en_US/stocks.lang index 10fcf353fd44c002355572bb574511363a80943e..7025176c9f1db0e74bae60e5d952470acb190c31 100644 --- a/htdocs/langs/en_US/stocks.lang +++ b/htdocs/langs/en_US/stocks.lang @@ -47,6 +47,7 @@ PMPValue=Weighted average price PMPValueShort=WAP EnhancedValueOfWarehouses=Warehouses value UserWarehouseAutoCreate=Create a warehouse automatically when creating a user +IndependantSubProductStock=Product stock and subproduct stock are independant QtyDispatched=Quantity dispatched QtyDispatchedShort=Qty dispatched QtyToDispatchShort=Qty to dispatch diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php index 3f3786016e5d0e84f5a2ae8bb72edfb512601a76..fe67938766d0514a5c7f6cd3294c58b1d41c5786 100755 --- a/htdocs/product/class/product.class.php +++ b/htdocs/product/class/product.class.php @@ -2306,6 +2306,40 @@ class Product extends CommonObject } } + /** + * Modify composed product + * + * @param int $id_pere Id of master product + * @param int $id_fils Id of linked product + * @param int $qty Quantity + * @param int $incdec increase/descrease stock or not + * * @return int < 0 if KO, > 0 if OK + */ + function update_sousproduit($id_pere, $id_fils,$qty, $incdec=1) + { + // Clean parameters + if (! is_numeric($id_pere)) $id_pere=0; + if (! is_numeric($id_fils)) $id_fils=0; + if (! is_numeric($incdec)) $incdec=1; + if (! is_numeric($qty)) $qty=1; + + $sql = 'UPDATE '.MAIN_DB_PREFIX.'product_association SET '; + $sql.= 'qty='.$qty; + $sql.= ',incdec='.$incdec; + $sql .= ' WHERE fk_product_pere='.$id_pere.' AND fk_product_fils='.$id_fils; + + if (!$this->db->query($sql)) + { + dol_print_error($this->db); + return -1; + } + else + { + return 1; + } + + } + /** * Retire le lien entre un sousproduit et un produit/service * @@ -2633,6 +2667,8 @@ class Product extends CommonObject $nb=(! empty($desc_pere[1]) ? $desc_pere[1] :''); $type=(! empty($desc_pere[2]) ? $desc_pere[2] :''); $label=(! empty($desc_pere[3]) ? $desc_pere[3] :''); + $incdec=!empty($desc_pere[4]) ? $desc_pere[4] : 0; + if ($multiply < 1) $multiply=1; //print "XXX We add id=".$id." - label=".$label." - nb=".$nb." - multiply=".$multiply." fullpath=".$compl_path.$label."\n"; @@ -2649,7 +2685,8 @@ class Product extends CommonObject 'fullpath'=>$compl_path.$label, // Label 'type'=>$type, // Nb of units that compose parent product 'desiredstock'=>$this->desiredstock, - 'level'=>$level + 'level'=>$level, + 'incdec'=>$incdec ); // Recursive call if there is childs to child @@ -2798,7 +2835,7 @@ class Product extends CommonObject */ function getChildsArbo($id) { - $sql = "SELECT p.rowid, p.label as label, pa.qty as qty, pa.fk_product_fils as id, p.fk_product_type"; + $sql = "SELECT p.rowid, p.label as label, pa.qty as qty, pa.fk_product_fils as id, p.fk_product_type, pa.incdec"; $sql.= " FROM ".MAIN_DB_PREFIX."product as p"; $sql.= ", ".MAIN_DB_PREFIX."product_association as pa"; $sql.= " WHERE p.rowid = pa.fk_product_fils"; @@ -2812,7 +2849,13 @@ class Product extends CommonObject $prods = array(); while ($rec = $this->db->fetch_array($res)) { - $prods[$rec['rowid']]= array(0=>$rec['id'],1=>$rec['qty'],2=>$rec['fk_product_type'],3=>$this->db->escape($rec['label'])); + $prods[$rec['rowid']]= array( + 0=>$rec['id'], + 1=>$rec['qty'], + 2=>$rec['fk_product_type'], + 3=>$this->db->escape($rec['label']), + 4=>$rec['incdec'] + ); //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty'],2=>$rec['fk_product_type']); //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty']); $listofchilds=$this->getChildsArbo($rec['id']); diff --git a/htdocs/product/composition/card.php b/htdocs/product/composition/card.php index 12553ada59ac7ddf32dd225e0ea8ddccb73377fb..9efa7772cb0b0be55f9c9d5a22f4d15c57cad702 100644 --- a/htdocs/product/composition/card.php +++ b/htdocs/product/composition/card.php @@ -109,6 +109,19 @@ $cancel <> $langs->trans("Cancel") && exit; } } +else if($action==='save_composed_product') { + + $TProduct = GETPOST('TProduct', 'array'); + if(!empty($TProduct)) { + + foreach ($TProduct as $id_product => $row) { + $product->update_sousproduit($id, $id_product,$row['qty'], isset($row['incdec']) ? 1 : 0 ); + } + + } + + +} if ($cancel == $langs->trans("Cancel")) { @@ -175,97 +188,6 @@ dol_fiche_head($head, 'subproduct', $titre, 0, $picto); if ($id > 0 || ! empty($ref)) { -/* if ($result) - { - if ($action <> 'edit' && $action <> 'search' && $action <> 're-edit') - { - // mode visu - - print '<table class="border" width="100%">'; - - print "<tr>"; - - $nblignes=6; - if ($product->isproduct() && ! empty($conf->stock->enabled)) $nblignes++; - if ($product->isservice()) $nblignes++; - - // Reference - print '<td width="25%">'.$langs->trans("Ref").'</td><td>'; - print $form->showrefnav($product,'ref','',1,'ref'); - print '</td></tr>'; - - // Libelle - print '<tr><td>'.$langs->trans("Label").'</td><td>'.$product->libelle.'</td>'; - print '</tr>'; - - // Number of subproducts - $prodsfather = $product->getFather(); // Parent Products - $product->get_sousproduits_arbo(); - $prods_arbo=$product->get_arbo_each_prod(); - $nbofsubproducts=count($prods_arbo); - print '<tr><td>'.$langs->trans("AssociatedProductsNumber").'</td><td>'; - print $form->textwithpicto($nbofsubproducts, $langs->trans('IfZeroItIsNotAVirtualProduct')); - print '</td>'; - - dol_fiche_end(); - - - // List of products into this virtual product - if (count($prods_arbo) > 0) - { - print '<tr><td colspan="2">'; - print '<b>'.$langs->trans("ProductAssociationList").'</b><br>'; - print '<table class="nobordernopadding">'; - foreach($prods_arbo as $value) - { - $productstatic->id=$value['id']; - $productstatic->type=$value['type']; - $productstatic->ref=$value['fullpath']; - if (! empty($conf->stock->enabled)) $productstatic->load_stock(); - //var_dump($value); - //print '<pre>'.$productstatic->ref.'</pre>'; - //print $productstatic->getNomUrl(1).'<br>'; - //print $value[0]; // This contains a tr line. - print '<tr>'; - //print '<td>'.$productstatic->getNomUrl(1,'composition').' ('.$value['nb'].($value['nb_total'] > $value['nb']?'->'.$value['nb_total']:'').')    </td>'; - print '<td>'.$productstatic->getNomUrl(1,'composition').' ('.$value['nb'].')    </td>'; - if (! empty($conf->stock->enabled)) print '<td>'.$langs->trans("Stock").' : <b>'.$productstatic->stock_reel.'</b></td>'; - print '</tr>'; - } - print '</table>'; - print '</td></tr>'; - } - - // Number of parent virtual products - print '<tr><td>'.$langs->trans("ParentProductsNumber").'</td><td>'; - print $form->textwithpicto(count($prodsfather), $langs->trans('IfZeroItIsNotUsedByVirtualProduct')); - print '</td>'; - - if (count($prodsfather) > 0) - { - print '<tr><td colspan="2">'; - print '<b>'.$langs->trans("ProductParentList").'</b><br>'; - print '<table class="nobordernopadding">'; - foreach($prodsfather as $value) - { - $idprod= $value["id"]; - $productstatic->id=$idprod;// $value["id"]; - $productstatic->type=$value["fk_product_type"]; - $productstatic->ref=$value['label']; - print '<tr>'; - print '<td>'.$productstatic->getNomUrl(1,'composition').'</td>';; - print '</tr>'; - } - print '</table>'; - print '</td></tr>'; - } - - print "</table>\n"; - - dol_fiche_end(); - } - } -*/ /* * Fiche en mode edition */ @@ -333,20 +255,43 @@ if ($id > 0 || ! empty($ref)) $atleastonenotdefined=0; print '<tr><td colspan="2">'; print $langs->trans("ProductAssociationList").'<br>'; - print '<table class="nobordernopadding centpercent">'; + + print '<form name="formComposedProduct" action="'.$_SERVER['PHP_SELF'].'" method="post" ">'; + print '<input type="hidden" name="action" value="save_composed_product" />'; + print '<input type="hidden" name="id" value="'.$id.'" />'; + + print '<table class="centpercent nobordernopadding">'; + + print '<tr class="liste_titre"><td>'.$langs->trans('ComposedProduct').'</td><td>'.$langs->trans('Qty').'</td><td>'.$langs->trans('ComposedProductDecreaseStock').'</td><td>'.$langs->trans('MinSupplierPrice').'</td><td>'.$langs->trans('Price').'</td><td>'.$langs->trans('Stock').'</td></tr>'; + foreach($prods_arbo as $value) { $productstatic->id=$value['id']; $productstatic->type=$value['type']; - //print '<pre>'.$productstatic->ref.'</pre>'; - //print $productstatic->getNomUrl(1).'<br>'; - //var_dump($value); - print '<tr>'; + + $class=($class=='impair')?'pair':'impair'; + + print '<tr class="'.$class.'">'; if ($value['level'] <= 1) { $notdefined=0; $productstatic->ref=$value['fullpath']; - print '<td>'.$productstatic->getNomUrl(1,'composition').' ('.$value['nb'].')</td>'; + $nb_of_subproduct = $value['nb']; + + print '<td>'.$productstatic->getNomUrl(1,'composition').'</td>'; + + if($user->rights->produit->creer || $user->rights->service->creer) { + print '<td><input type="text" value="'.$nb_of_subproduct.'" name="TProduct['.$productstatic->id.'][qty]" size="4" /></td>'; + print '<td><input type="checkbox" name="TProduct['.$productstatic->id.'][incdec]" value="1" '.($value['incdec']==1?'checked="checked"':'' ).' /></td>'; + + } + else{ + print '<td>'.$nb_of_subproduct.'</td>'; + print '<td>'.($value['incdec']==1?'x':'' ).'</td>'; + } + + + print '<td align="right">'; if ($product_fourn->find_min_price_product_fournisseur($productstatic->id) > 0) { @@ -367,26 +312,35 @@ if ($id > 0 || ! empty($ref)) { print ' '; } - print $productstatic->getNomUrl(1,'composition').' ('.$value['nb'].')</td>'; - print '<td><td>'; - print '<td><td>'; + print $productstatic->getNomUrl(1,'composition').'</td>'; + print '<td>'.$value['nb'].'</td>'; + print '<td> </td>'; + print '<td> <td>'; + print '<td> <td>'; if (! empty($conf->stock->enabled)) print '<td align="right"></td>'; // Real stock } print '</tr>'; } - print '<tr>'; - print '<td colspan="2">'.$langs->trans("TotalBuyingPriceMin").': '; + print '<tr class="liste_total">'; + print '<td colspan="3" align="right">'.$langs->trans("TotalBuyingPriceMin").': '; if ($atleastonenotdefined) print $langs->trans("Unknown").' ('.$langs->trans("SomeSubProductHaveNoPrices").')'; print '</td>'; - print '<td align="right">'.($atleastonenotdefined?'':price($total,'','',0,0,-1,$conf->currency)).'</td>'; + print '<td align="right" class="liste_total">'.($atleastonenotdefined?'':price($total,'','',0,0,-1,$conf->currency)).'</td>'; if (! empty($conf->stock->enabled)) print '<td class="liste_total" align="right"> </td>'; print '</tr>'; print '</table>'; + + if($user->rights->produit->creer || $user->rights->service->creer) { + print '<div class="tabsAction"><input type="submit" value="'.$langs->trans('Save').'" /></div>'; + } + + print '</form>'; + print '</td></tr>'; } // Number of parent virtual products - print '<tr><td>'.$langs->trans("ParentProductsNumber").'</td><td>'; + print '<tr class="pair"><td>'.$langs->trans("ParentProductsNumber").'</td><td>'; print $form->textwithpicto(count($prodsfather), $langs->trans('IfZeroItIsNotUsedByVirtualProduct')); print '</td>'; diff --git a/htdocs/product/stock/class/mouvementstock.class.php b/htdocs/product/stock/class/mouvementstock.class.php index f175d75db6fb29a65eadeb8cfbb927dcaa36a871..39dd2ff5f673bbbbea4bcf52d77d1638759380ef 100644 --- a/htdocs/product/stock/class/mouvementstock.class.php +++ b/htdocs/product/stock/class/mouvementstock.class.php @@ -292,7 +292,7 @@ class MouvementStock extends CommonObject } // Add movement for sub products (recursive call) - if (! $error && ! empty($conf->global->PRODUIT_SOUSPRODUITS)) + if (! $error && ! empty($conf->global->PRODUIT_SOUSPRODUITS) && empty($conf->global->INDEPENDANT_SUBPRODUCT_STOCK)) { $error = $this->_createSubProduct($user, $fk_product, $entrepot_id, $qty, $type, 0, $label, $inventorycode); // we use 0 as price, because pmp is not changed for subproduct } @@ -341,8 +341,7 @@ class MouvementStock extends CommonObject $sql = "SELECT fk_product_pere, fk_product_fils, qty"; $sql.= " FROM ".MAIN_DB_PREFIX."product_association"; $sql.= " WHERE fk_product_pere = ".$idProduct; - // TODO Select only subproduct with incdec tag - //$sql.= " AND incdec = 1"; + $sql.= " AND incdec = 1"; dol_syslog(get_class($this)."::_createSubProduct", LOG_DEBUG); $resql=$this->db->query($sql);