Browse Source

Merge pull request #20034 from atm-quentin/NEW_Needs_net_tab_on_bom

New needs net tab on bom
Laurent Destailleur 3 năm trước cách đây
mục cha
commit
9c6858379f

+ 328 - 0
htdocs/bom/bom_net_needs.php

@@ -0,0 +1,328 @@
+<?php
+/* Copyright (C) 2017-2020  Laurent Destailleur     <eldy@users.sourceforge.net>
+ * Copyright (C) 2019       Frédéric France         <frederic.france@netlogic.fr>
+ *
+ * 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
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ *   	\file       htdocs/bom/bom_net_needs.php
+ *		\ingroup    bom
+ *		\brief      Page to create/edit/view bom
+ */
+
+// Load Dolibarr environment
+require '../main.inc.php';
+require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php';
+require_once DOL_DOCUMENT_ROOT.'/core/class/html.formfile.class.php';
+require_once DOL_DOCUMENT_ROOT.'/bom/class/bom.class.php';
+require_once DOL_DOCUMENT_ROOT.'/bom/lib/bom.lib.php';
+
+// Load translation files required by the page
+$langs->loadLangs(array("mrp", "other"));
+
+// Get parameters
+$id = GETPOST('id', 'int');
+$ref        = GETPOST('ref', 'alpha');
+$action = GETPOST('action', 'aZ09');
+$confirm    = GETPOST('confirm', 'alpha');
+$cancel = GETPOST('cancel', 'aZ09');
+$contextpage = GETPOST('contextpage', 'aZ') ?GETPOST('contextpage', 'aZ') : 'bomnet_needs'; // To manage different context of search
+$backtopage = GETPOST('backtopage', 'alpha');
+
+
+
+// Initialize technical objects
+$object = new BOM($db);
+$extrafields = new ExtraFields($db);
+$diroutputmassaction = $conf->bom->dir_output.'/temp/massgeneration/'.$user->id;
+$hookmanager->initHooks(array('bomnetneeds')); // Note that conf->hooks_modules contains array
+// Fetch optionals attributes and labels
+$extrafields->fetch_name_optionals_label($object->table_element);
+$search_array_options = $extrafields->getOptionalsFromPost($object->table_element, '', 'search_');
+
+// Initialize array of search criterias
+$search_all = GETPOST("search_all", 'alpha');
+$search = array();
+foreach ($object->fields as $key => $val) {
+	if (GETPOST('search_'.$key, 'alpha')) {
+		$search[$key] = GETPOST('search_'.$key, 'alpha');
+	}
+}
+
+if (empty($action) && empty($id) && empty($ref)) {
+	$action = 'view';
+}
+
+// Load object
+include DOL_DOCUMENT_ROOT.'/core/actions_fetchobject.inc.php'; // Must be include, not include_once.
+if ($object->id > 0) {
+	$object->calculateCosts();
+}
+
+
+
+// Security check - Protection if external user
+//if ($user->socid > 0) accessforbidden();
+//if ($user->socid > 0) $socid = $user->socid;
+$isdraft = (($object->status == $object::STATUS_DRAFT) ? 1 : 0);
+$result = restrictedArea($user, 'bom', $object->id, 'bom_bom', '', '', 'rowid', $isdraft);
+
+$permissionnote = $user->rights->bom->write; // Used by the include of actions_setnotes.inc.php
+$permissiondellink = $user->rights->bom->write; // Used by the include of actions_dellink.inc.php
+$permissiontoadd = $user->rights->bom->write; // Used by the include of actions_addupdatedelete.inc.php and actions_lineupdown.inc.php
+$permissiontodelete = $user->rights->bom->delete || ($permissiontoadd && isset($object->status) && $object->status == $object::STATUS_DRAFT);
+$upload_dir = $conf->bom->multidir_output[isset($object->entity) ? $object->entity : 1];
+
+
+/*
+ * Actions
+ */
+
+$parameters = array();
+$reshook = $hookmanager->executeHooks('doActions', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
+if ($reshook < 0) {
+	setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
+}
+
+if (empty($reshook)) {
+	$error = 0;
+
+	$backurlforlist = DOL_URL_ROOT.'/bom/bom_list.php';
+
+	if (empty($backtopage) || ($cancel && empty($id))) {
+		if (empty($backtopage) || ($cancel && strpos($backtopage, '__ID__'))) {
+			if (empty($id) && (($action != 'add' && $action != 'create') || $cancel)) {
+				$backtopage = $backurlforlist;
+			} else {
+				$backtopage = DOL_URL_ROOT.'/bom/bom_net_needs.php?id='.($id > 0 ? $id : '__ID__');
+			}
+		}
+	}
+	if ($action == 'treeview') $object->getNetNeedsTree($TChildBom, 1);
+	else $object->getNetNeeds($TChildBom, 1);
+}
+
+
+/*
+ * View
+ */
+
+$form = new Form($db);
+$formfile = new FormFile($db);
+
+
+$title = $langs->trans('BOM');
+$help_url ='EN:Module_BOM';
+llxHeader('', $title, $help_url);
+
+
+
+// Part to show record
+if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'create'))) {
+	$head = bomPrepareHead($object);
+	print dol_get_fiche_head($head, 'net_needs', $langs->trans("BillOfMaterials"), -1, 'bom');
+
+	$formconfirm = '';
+
+	// Call Hook formConfirm
+	$parameters = array('formConfirm' => $formconfirm, 'lineid' => $lineid);
+	$reshook = $hookmanager->executeHooks('formConfirm', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
+	if (empty($reshook)) {
+		$formconfirm .= $hookmanager->resPrint;
+	} elseif ($reshook > 0) {
+		$formconfirm = $hookmanager->resPrint;
+	}
+
+	// Print form confirm
+	print $formconfirm;
+
+
+	// Object card
+	// ------------------------------------------------------------
+	$linkback = '<a href="'.DOL_URL_ROOT.'/bom/bom_list.php?restore_lastsearch_values=1'.(!empty($socid) ? '&socid='.$socid : '').'">'.$langs->trans("BackToList").'</a>';
+
+	$morehtmlref = '<div class="refidno">';
+
+	$morehtmlref .= '</div>';
+
+
+	dol_banner_tab($object, 'ref', $linkback, 1, 'ref', 'ref', $morehtmlref);
+
+
+	print '<div class="fichecenter">';
+	print '<div class="fichehalfleft">';
+	print '<div class="underbanner clearboth"></div>';
+	print '<table class="border centpercent tableforfield">'."\n";
+
+	// Common attributes
+	$keyforbreak = 'duration';
+	include DOL_DOCUMENT_ROOT.'/core/tpl/commonfields_view.tpl.php';
+
+	print '<tr><td>'.$form->textwithpicto($langs->trans("TotalCost"), $langs->trans("BOMTotalCost")).'</td><td>'.price($object->total_cost).'</td></tr>';
+	print '<tr><td>'.$langs->trans("UnitCost").'</td><td>'.price($object->unit_cost).'</td></tr>';
+
+	// Other attributes
+	include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_view.tpl.php';
+
+	print '</table>';
+	print '</div>';
+	print '</div>';
+
+	print '<div class="clearboth"></div>';
+
+	print dol_get_fiche_end();
+
+	$viewlink = dolGetButtonTitle($langs->trans('GroupByProduct'), '', 'fa fa-list-alt imgforviewmode', $_SERVER['PHP_SELF'].'?id='.$object->id.'&token='.newToken(), '', 1, array('morecss' => 'reposition '.($action !== 'treeview' ? 'btnTitleSelected':'')));
+	$viewlink .= dolGetButtonTitle($langs->trans('TreeStructure'), '', 'fa fa-stream imgforviewmode', $_SERVER['PHP_SELF'].'?id='.$object->id.'&action=treeview&token='.newToken(), '', 1, array('morecss' => 'reposition marginleftonly '.($action == 'treeview' ? 'btnTitleSelected':'')));
+
+	print load_fiche_titre($langs->trans("BillOfMaterials"), $viewlink, 'cubes');
+
+	/*
+	 * Lines
+	 */
+	$text_stock_options = $langs->trans("RealStockDesc").'<br>';
+	$text_stock_options .= $langs->trans("RealStockWillAutomaticallyWhen").'<br>';
+	$text_stock_options .= (! empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT) || ! empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT_CLOSE) ? '- '.$langs->trans("DeStockOnShipment").'<br>' : '');
+	$text_stock_options .= (! empty($conf->global->STOCK_CALCULATE_ON_VALIDATE_ORDER) ? '- '.$langs->trans("DeStockOnValidateOrder").'<br>' : '');
+	$text_stock_options .= (! empty($conf->global->STOCK_CALCULATE_ON_BILL) ? '- '.$langs->trans("DeStockOnBill").'<br>' : '');
+	$text_stock_options .= (! empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_BILL) ? '- '.$langs->trans("ReStockOnBill").'<br>' : '');
+	$text_stock_options .= (! empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_VALIDATE_ORDER) ? '- '.$langs->trans("ReStockOnValidateOrder").'<br>' : '');
+	$text_stock_options .= (! empty($conf->global->STOCK_CALCULATE_ON_SUPPLIER_DISPATCH_ORDER) ? '- '.$langs->trans("ReStockOnDispatchOrder").'<br>' : '');
+	$text_stock_options .= (!empty($conf->global->STOCK_CALCULATE_ON_RECEPTION) || !empty($conf->global->STOCK_CALCULATE_ON_RECEPTION_CLOSE) ? '- '.$langs->trans("StockOnReception").'<br>' : '');
+
+	print '<table id="tablelines" class="noborder noshadow" width="100%">';
+	print "<thead>\n";
+	print '<tr class="liste_titre nodrag nodrop">';
+	print '<td class="linecoldescription">'.$langs->trans('Product');
+	if (! empty($conf->global->BOM_SUB_BOM)  && $action == 'treeview') {
+		print ' &nbsp; <a id="show_all" href="#">'.img_picto('', 'folder-open', 'class="paddingright"').$langs->trans("ExpandAll").'</a>&nbsp;&nbsp;';
+		print '<a id="hide_all" href="#">'.img_picto('', 'folder', 'class="paddingright"').$langs->trans("UndoExpandAll").'</a>&nbsp;';
+	}
+	print '</td>';
+	print '<td class="linecolqty">'.$langs->trans('Quantity').'</td>';
+	print '<td class="linecolstock">'.$form->textwithpicto($langs->trans("PhysicalStock"), $text_stock_options, 1).'</td>';
+	print '<td class="linecoltheoricalstock">'.$form->textwithpicto($langs->trans("VirtualStock"), $langs->trans("VirtualStockDesc")).'</td>';
+	print  '</tr>';
+	if (! empty($TChildBom)) {
+		if ($action == 'treeview') {
+			foreach ($TChildBom as $fk_bom => $TProduct) {
+				$repeatChar = '&emsp;';
+				if (! empty($TProduct['bom'])) {
+					if ($TProduct['parentid'] != $object->id) print '<tr class="sub_bom_lines oddeven" parentid="'.$TProduct['parentid'].'">';
+					else print '<tr class="oddeven">';
+					print '<td class="linecoldescription">'.str_repeat($repeatChar, $TProduct['level']).$TProduct['bom']->getNomUrl(1);
+					print ' <a class="collapse_bom" id="collapse-'.$fk_bom.'" href="#">';
+					print img_picto('', 'folder-open');
+					print '</a>';
+					print  '</td>';
+					print '<td class="linecolqty">'.$TProduct['qty'].'</td>';
+					print '<td class="linecolstock"></td>';
+					print '<td class="linecoltheoricalstock"></td>';
+					print '</tr>';
+				}
+				if (! empty($TProduct['product'])) {
+					foreach ($TProduct['product'] as $fk_product => $TInfos) {
+						$prod = new Product($db);
+						$prod->fetch($fk_product);
+						$prod->load_virtual_stock();
+						if (empty($prod->stock_reel)) $prod->stock_reel = 0;
+						if ($fk_bom != $object->id) print '<tr class="sub_bom_lines oddeven" parentid="'.$fk_bom.'">';
+						else print '<tr class="oddeven">';
+						print '<td class="linecoldescription">'.str_repeat($repeatChar, $TInfos['level']).$prod->getNomUrl(1).'</td>';
+						print '<td class="linecolqty">'.$TInfos['qty'].'</td>';
+						print '<td class="linecolstock">'.$prod->stock_reel.'</td>';
+						print '<td class="linecoltheoricalstock">'.$prod->stock_theorique.'</td>';
+						print '</tr>';
+					}
+				}
+			}
+		} else {
+			foreach ($TChildBom as $fk_product => $qty) {
+				$prod = new Product($db);
+				$prod->fetch($fk_product);
+				$prod->load_virtual_stock();
+				if (empty($prod->stock_reel)) $prod->stock_reel = 0;
+				print '<tr class="oddeven">';
+				print '<td class="linecoldescription">'.$prod->getNomUrl(1).'</td>';
+				print '<td class="linecolqty">'.$qty.'</td>';
+				print '<td class="linecolstock">'.$prod->stock_reel.'</td>';
+				print '<td class="linecoltheoricalstock">'.$prod->stock_theorique.'</td>';
+				print '</tr>';
+			}
+		}
+	}
+	print '</thead>';
+	print '</table>';
+
+
+
+	/*
+	 * ButAction
+	 */
+	print '<div class="tabsAction">'."\n";
+	$parameters = array();
+	$reshook = $hookmanager->executeHooks('addMoreActionsButtons', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
+	if ($reshook < 0) {
+		setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
+	}
+	print '</div>';
+
+
+	?>
+
+		<script type="text/javascript" language="javascript">
+			$(document).ready(function() {
+				// When clicking on collapse
+				$(".collapse_bom").click(function() {
+					console.log("We click on collapse");
+					var id_bom_line = $(this).attr('id').replace('collapse-', '');
+					console.log($(this).html().indexOf('folder-open'));
+					if($(this).html().indexOf('folder-open') <= 0) {
+						$('[parentid="'+ id_bom_line +'"]').show();
+						$(this).html('<?php echo dol_escape_js(img_picto('', 'folder-open')); ?>');
+					}
+					else {
+						$('[parentid="'+ id_bom_line +'"]').hide();
+						$(this).html('<?php echo dol_escape_js(img_picto('', 'folder')); ?>');
+					}
+
+					return false;
+				});
+
+				// To Show all the sub bom lines
+				$("#show_all").click(function() {
+					console.log("We click on show all");
+					$("[class^=sub_bom_lines]").show();
+					$("[class^=collapse_bom]").html('<?php echo dol_escape_js(img_picto('', 'folder-open')); ?>');
+					return false;
+				});
+
+				// To Hide all the sub bom lines
+				$("#hide_all").click(function() {
+					console.log("We click on hide all");
+					$("[class^=sub_bom_lines]").hide();
+					$("[class^=collapse_bom]").html('<?php echo dol_escape_js(img_picto('', 'folder')); ?>');
+					return false;
+				});
+
+			});
+		</script>
+
+		<?php
+}
+
+// End of page
+llxFooter();
+$db->close();

+ 54 - 0
htdocs/bom/class/bom.class.php

@@ -1085,6 +1085,7 @@ class BOM extends CommonObject
 					$res = $bom_child->fetch($line->fk_bom_child);
 					if ($res>0) {
 						$bom_child->calculateCosts();
+						$line->childBom[] = $bom_child;
 						$this->total_cost += $bom_child->total_cost  * $line->qty;
 					} else {
 						$this->error = $bom_child->error;
@@ -1101,6 +1102,54 @@ class BOM extends CommonObject
 			}
 		}
 	}
+
+	/**
+	 * Get Net needs by product
+	 *
+	 * @param array $TNetNeeds Array of ChildBom and infos linked to
+	 * @param int   $qty       qty needed
+	 * @return void
+	 */
+	public function getNetNeeds(&$TNetNeeds = array(), $qty = 0)
+	{
+		if (! empty($this->lines)) {
+			foreach ($this->lines as $line) {
+				if (! empty($line->childBom)) {
+					foreach ($line->childBom as $childBom) $childBom->getNetNeeds($TNetNeeds, $line->qty*$qty);
+				} else {
+					$TNetNeeds[$line->fk_product] += $line->qty*$qty;
+				}
+			}
+		}
+	}
+
+	/**
+	 * Get Net needs Tree by product or bom
+	 *
+	 * @param array $TNetNeeds Array of ChildBom and infos linked to
+	 * @param int   $qty       qty needed
+	 * @param int   $level     level of recursivity
+	 * @return void
+	 */
+	public function getNetNeedsTree(&$TNetNeeds = array(), $qty = 0, $level = 0)
+	{
+		if (! empty($this->lines)) {
+			foreach ($this->lines as $line) {
+				if (! empty($line->childBom)) {
+					foreach ($line->childBom as $childBom) {
+						$TNetNeeds[$childBom->id]['bom'] = $childBom;
+						$TNetNeeds[$childBom->id]['parentid'] = $this->id;
+						$TNetNeeds[$childBom->id]['qty'] = $line->qty*$qty;
+						$TNetNeeds[$childBom->id]['level'] = $level;
+						$childBom->getNetNeedsTree($TNetNeeds, $line->qty*$qty, $level+1);
+					}
+				} else {
+					$TNetNeeds[$this->id]['product'][$line->fk_product]['qty'] += $line->qty * $qty;
+					$TNetNeeds[$this->id]['product'][$line->fk_product]['level'] = $level;
+				}
+			}
+		}
+	}
 }
 
 
@@ -1227,6 +1276,11 @@ class BOMLine extends CommonObjectLine
 	public $unit_cost = 0;
 
 
+	/**
+	 * @var Bom     array of Bom in line
+	 */
+	public $childBom = array();
+
 	/**
 	 * Constructor
 	 *

+ 5 - 0
htdocs/bom/lib/bom.lib.php

@@ -84,6 +84,11 @@ function bomPrepareHead($object)
 	$head[$h][2] = 'card';
 	$h++;
 
+	$head[$h][0] = DOL_URL_ROOT."/bom/bom_net_needs.php?id=".$object->id;
+	$head[$h][1] = $langs->trans("BOMNetNeeds");
+	$head[$h][2] = 'net_needs';
+	$h++;
+
 	if (isset($object->fields['note_public']) || isset($object->fields['note_private'])) {
 		$nbNote = 0;
 		if (!empty($object->note_private)) {

+ 3 - 0
htdocs/langs/en_US/mrp.lang

@@ -107,3 +107,6 @@ THMEstimatedHelp=This rate makes it possible to define a forecast cost of the it
 BOM=Bill Of Materials
 CollapseBOMHelp=You can define the default display of the details of the nomenclature in the configuration of the BOM module
 MOAndLines=Manufacturing Orders and lines
+BOMNetNeeds=Net Needs
+TreeStructure=Tree structure
+GroupByProduct=Group by product