dolibarr  16.0.1
inventory.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (C) 2019 Laurent Destailleur <eldy@users.sourceforge.net>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program. If not, see <https://www.gnu.org/licenses/>.
16  */
17 
24 require '../../main.inc.php';
25 include_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php';
26 include_once DOL_DOCUMENT_ROOT.'/product/class/html.formproduct.class.php';
27 include_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
28 include_once DOL_DOCUMENT_ROOT.'/product/inventory/class/inventory.class.php';
29 include_once DOL_DOCUMENT_ROOT.'/product/inventory/lib/inventory.lib.php';
30 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
31 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/productlot.class.php';
32 
33 // Load translation files required by the page
34 $langs->loadLangs(array("stocks", "other", "productbatch"));
35 
36 // Get parameters
37 $id = GETPOST('id', 'int');
38 $ref = GETPOST('ref', 'alpha');
39 $action = GETPOST('action', 'aZ09');
40 $confirm = GETPOST('confirm', 'alpha');
41 $cancel = GETPOST('cancel', 'aZ09');
42 $contextpage = GETPOST('contextpage', 'aZ') ?GETPOST('contextpage', 'aZ') : 'inventorycard'; // To manage different context of search
43 $backtopage = GETPOST('backtopage', 'alpha');
44 
45 $fk_warehouse = GETPOST('fk_warehouse', 'int');
46 $fk_product = GETPOST('fk_product', 'int');
47 $lineid = GETPOST('lineid', 'int');
48 $batch = GETPOST('batch', 'alphanohtml');
49 $totalExpectedValuation = 0;
50 $totalRealValuation = 0;
51 if (empty($conf->global->MAIN_USE_ADVANCED_PERMS)) {
52  $result = restrictedArea($user, 'stock', $id);
53 } else {
54  $result = restrictedArea($user, 'stock', $id, '', 'inventory_advance');
55 }
56 
57 // Initialize technical objects
58 $object = new Inventory($db);
59 $extrafields = new ExtraFields($db);
60 $diroutputmassaction = $conf->stock->dir_output.'/temp/massgeneration/'.$user->id;
61 $hookmanager->initHooks(array('inventorycard')); // Note that conf->hooks_modules contains array
62 
63 // Fetch optionals attributes and labels
64 $extrafields->fetch_name_optionals_label($object->table_element);
65 
66 $search_array_options = $extrafields->getOptionalsFromPost($object->table_element, '', 'search_');
67 
68 // Initialize array of search criterias
69 $search_all = GETPOST("search_all", 'alpha');
70 $search = array();
71 foreach ($object->fields as $key => $val) {
72  if (GETPOST('search_'.$key, 'alpha')) {
73  $search[$key] = GETPOST('search_'.$key, 'alpha');
74  }
75 }
76 
77 if (empty($action) && empty($id) && empty($ref)) {
78  $action = 'view';
79 }
80 
81 // Load object
82 include DOL_DOCUMENT_ROOT.'/core/actions_fetchobject.inc.php'; // Must be include, not include_once.
83 
84 // Security check - Protection if external user
85 //if ($user->socid > 0) accessforbidden();
86 //if ($user->socid > 0) $socid = $user->socid;
87 //$result = restrictedArea($user, 'mymodule', $id);
88 
89 if (empty($conf->global->MAIN_USE_ADVANCED_PERMS)) {
90  $permissiontoadd = $user->rights->stock->creer;
91  $permissiontodelete = $user->rights->stock->supprimer;
92 } else {
93  $permissiontoadd = $user->rights->stock->inventory_advance->write;
94  $permissiontodelete = $user->rights->stock->inventory_advance->write;
95 }
96 
97 $now = dol_now();
98 
99 
100 /*
101  * Actions
102  */
103 
104 if ($cancel) {
105  $action = '';
106 }
107 
108 
109 $parameters = array();
110 $reshook = $hookmanager->executeHooks('doActions', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
111 if ($reshook < 0) {
112  setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
113 }
114 
115 if (empty($reshook)) {
116  $error = 0;
117 
118  if ($action == 'cancel_record' && $permissiontoadd) {
119  $object->setCanceled($user);
120  }
121 
122  // Close inventory by recording the stock movements
123  if ($action == 'update' && !empty($user->rights->stock->mouvement->creer)) {
124  $stockmovment = new MouvementStock($db);
125  $stockmovment->setOrigin($object->element, $object->id);
126 
127  $cacheOfProducts = array();
128 
129  $db->begin();
130 
131  $sql = 'SELECT id.rowid, id.datec as date_creation, id.tms as date_modification, id.fk_inventory, id.fk_warehouse,';
132  $sql .= ' id.fk_product, id.batch, id.qty_stock, id.qty_view, id.qty_regulated, id.pmp_real';
133  $sql .= ' FROM '.MAIN_DB_PREFIX.'inventorydet as id';
134  $sql .= ' WHERE id.fk_inventory = '.((int) $object->id);
135 
136  $resql = $db->query($sql);
137  if ($resql) {
138  $num = $db->num_rows($resql);
139  $i = 0;
140  $totalarray = array();
141  while ($i < $num) {
142  $line = $db->fetch_object($resql);
143 
144  $qty_stock = $line->qty_stock;
145  $qty_view = $line->qty_view; // The quantity viewed by inventorier, the qty we target
146 
147 
148  // Load real stock we have now.
149  if (isset($cacheOfProducts[$line->fk_product])) {
150  $product_static = $cacheOfProducts[$line->fk_product];
151  } else {
152  $product_static = new Product($db);
153  $result = $product_static->fetch($line->fk_product, '', '', '', 1, 1, 1);
154 
155  //$option = 'nobatch';
156  $option .= ',novirtual';
157  $product_static->load_stock($option); // Load stock_reel + stock_warehouse.
158 
159  $cacheOfProducts[$product_static->id] = $product_static;
160  }
161 
162  // Get the real quantity in stock now, but before the stock move for inventory.
163  $realqtynow = $product_static->stock_warehouse[$line->fk_warehouse]->real;
164  if ($conf->productbatch->enabled && $product_static->hasbatch()) {
165  $realqtynow = $product_static->stock_warehouse[$line->fk_warehouse]->detail_batch[$line->batch]->qty;
166  }
167 
168 
169  if (!is_null($qty_view)) {
170  $stock_movement_qty = price2num($qty_view - $realqtynow, 'MS');
171  if ($stock_movement_qty != 0) {
172  if ($stock_movement_qty < 0) {
173  $movement_type = 1;
174  } else {
175  $movement_type = 0;
176  }
177 
178  $datemovement = '';
179  //$inventorycode = 'INV'.$object->id;
180  $inventorycode = 'INV-'.$object->ref;
181  $price = 0;
182  if (!empty($line->pmp_real) && !empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) $price = $line->pmp_real;
183 
184  $idstockmove = $stockmovment->_create($user, $line->fk_product, $line->fk_warehouse, $stock_movement_qty, $movement_type, $price, $langs->trans('LabelOfInventoryMovemement', $object->ref), $inventorycode, $datemovement, '', '', $line->batch);
185  if ($idstockmove < 0) {
186  $error++;
187  setEventMessages($stockmovment->error, $stockmovment->errors, 'errors');
188  break;
189  }
190 
191  // Update line with id of stock movement (and the start quantity if it has changed this last recording)
192  $sqlupdate = "UPDATE ".MAIN_DB_PREFIX."inventorydet";
193  $sqlupdate .= " SET fk_movement = ".((int) $idstockmove);
194  if ($qty_stock != $realqtynow) {
195  $sqlupdate .= ", qty_stock = ".((float) $realqtynow);
196  }
197  $sqlupdate .= " WHERE rowid = ".((int) $line->rowid);
198  $resqlupdate = $db->query($sqlupdate);
199  if (! $resqlupdate) {
200  $error++;
201  setEventMessages($db->lasterror(), null, 'errors');
202  break;
203  }
204  }
205 
206  if (!empty($line->pmp_real) && !empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
207  $sqlpmp = 'UPDATE '.MAIN_DB_PREFIX.'product SET pmp = '.((float) $line->pmp_real).' WHERE rowid = '.((int) $line->fk_product);
208  $resqlpmp = $db->query($sqlpmp);
209  if (! $resqlpmp) {
210  $error++;
211  setEventMessages($db->lasterror(), null, 'errors');
212  break;
213  }
214  if (!empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED)) {
215  $sqlpmp = 'UPDATE '.MAIN_DB_PREFIX.'product_perentity SET pmp = '.((float) $line->pmp_real).' WHERE fk_product = '.((int) $line->fk_product).' AND entity='.$conf->entity;
216  $resqlpmp = $db->query($sqlpmp);
217  if (! $resqlpmp) {
218  $error++;
219  setEventMessages($db->lasterror(), null, 'errors');
220  break;
221  }
222  }
223  }
224  }
225  $i++;
226  }
227 
228  if (!$error) {
229  $object->setRecorded($user);
230  }
231  } else {
232  setEventMessages($db->lasterror, null, 'errors');
233  $error++;
234  }
235 
236  if (! $error) {
237  $db->commit();
238  } else {
239  $db->rollback();
240  }
241  }
242 
243  // Save quantity found during inventory (when we click on Save button on inventory page)
244  if ($action =='updateinventorylines' && $permissiontoadd) {
245  $sql = 'SELECT id.rowid, id.datec as date_creation, id.tms as date_modification, id.fk_inventory, id.fk_warehouse,';
246  $sql .= ' id.fk_product, id.batch, id.qty_stock, id.qty_view, id.qty_regulated';
247  $sql .= ' FROM '.MAIN_DB_PREFIX.'inventorydet as id';
248  $sql .= ' WHERE id.fk_inventory = '.((int) $object->id);
249 
250  $db->begin();
251 
252  $resql = $db->query($sql);
253  if ($resql) {
254  $num = $db->num_rows($resql);
255  $i = 0;
256  $totalarray = array();
257  $inventoryline = new InventoryLine($db);
258 
259  while ($i < $num) {
260  $line = $db->fetch_object($resql);
261  $lineid = $line->rowid;
262 
263  $result = 0;
264  $resultupdate = 0;
265 
266  if (GETPOST("id_".$lineid, 'alpha') != '') { // If a value was set ('0' or something else)
267  $qtytoupdate = price2num(GETPOST("id_".$lineid, 'alpha'), 'MS');
268  $result = $inventoryline->fetch($lineid);
269  if ($qtytoupdate < 0) {
270  $result = -1;
271  setEventMessages($langs->trans("FieldCannotBeNegative", $langs->transnoentitiesnoconv("RealQty")), null, 'errors');
272  }
273  if ($result > 0) {
274  $inventoryline->qty_stock = price2num(GETPOST('stock_qty_'.$lineid, 'alpha'), 'MS'); // The new value that was set in as hidden field
275  $inventoryline->qty_view = $qtytoupdate; // The new value we want
276  $inventoryline->pmp_real = price2num(GETPOST('realpmp_'.$lineid, 'alpha'), 'MS');
277  $inventoryline->pmp_expected = price2num(GETPOST('expectedpmp_'.$lineid, 'alpha'), 'MS');
278  $resultupdate = $inventoryline->update($user);
279  }
280  } else {
281  // Delete record
282  $result = $inventoryline->fetch($lineid);
283  if ($result > 0) {
284  $inventoryline->qty_view = null; // The new value we want
285  $inventoryline->pmp_real = price2num(GETPOST('realpmp_'.$lineid, 'alpha'), 'MS');
286  $inventoryline->pmp_expected = price2num(GETPOST('expectedpmp_'.$lineid, 'alpha'), 'MS');
287  $resultupdate = $inventoryline->update($user);
288  }
289  }
290 
291  if ($result < 0 || $resultupdate < 0) {
292  $error++;
293  }
294 
295  $i++;
296  }
297  }
298 
299  // Update user that update quantities
300  if (! $error) {
301  $sqlupdate = "UPDATE ".MAIN_DB_PREFIX."inventory";
302  $sqlupdate .= " SET fk_user_modif = ".((int) $user->id);
303  $sqlupdate .= " WHERE rowid = ".((int) $object->id);
304  $resqlupdate = $db->query($sqlupdate);
305  if (! $resqlupdate) {
306  $error++;
307  setEventMessages($db->lasterror(), null, 'errors');
308  }
309  }
310 
311  if (!$error) {
312  $db->commit();
313  } else {
314  $db->rollback();
315  }
316  }
317 
318 
319  $backurlforlist = DOL_URL_ROOT.'/product/inventory/list.php';
320  $backtopage = DOL_URL_ROOT.'/product/inventory/inventory.php?id='.$object->id;
321 
322  // Actions cancel, add, update, delete or clone
323  include DOL_DOCUMENT_ROOT.'/core/actions_addupdatedelete.inc.php';
324 
325  // Actions when linking object each other
326  include DOL_DOCUMENT_ROOT.'/core/actions_dellink.inc.php';
327 
328  // Actions when printing a doc from card
329  include DOL_DOCUMENT_ROOT.'/core/actions_printing.inc.php';
330 
331  // Actions to send emails
332  /*$triggersendname = 'MYOBJECT_SENTBYMAIL';
333  $autocopy='MAIN_MAIL_AUTOCOPY_MYOBJECT_TO';
334  $trackid='stockinv'.$object->id;
335  include DOL_DOCUMENT_ROOT.'/core/actions_sendmails.inc.php';*/
336 
337  if (GETPOST('addline', 'alpha')) {
338  $qty= (GETPOST('qtytoadd') != '' ? price2num(GETPOST('qtytoadd', 'MS')) : null);
339  if ($fk_warehouse <= 0) {
340  $error++;
341  setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Warehouse")), null, 'errors');
342  }
343  if ($fk_product <= 0) {
344  $error++;
345  setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Product")), null, 'errors');
346  }
347  if (price2num(GETPOST('qtytoadd'), 'MS') < 0) {
348  $error++;
349  setEventMessages($langs->trans("FieldCannotBeNegative", $langs->transnoentitiesnoconv("RealQty")), null, 'errors');
350  }
351  if (!$error && !empty($conf->productbatch->enabled)) {
352  $tmpproduct = new Product($db);
353  $result = $tmpproduct->fetch($fk_product);
354 
355  if (empty($error) && $tmpproduct->status_batch>0 && empty($batch)) {
356  $error++;
357  $langs->load("errors");
358  setEventMessages($langs->trans("ErrorProductNeedBatchNumber", $tmpproduct->ref), null, 'errors');
359  }
360  if (empty($error) && $tmpproduct->status_batch==2 && !empty($batch) && $qty>1) {
361  $error++;
362  $langs->load("errors");
363  setEventMessages($langs->trans("TooManyQtyForSerialNumber", $tmpproduct->ref, $batch), null, 'errors');
364  }
365  if (empty($error) && empty($tmpproduct->status_batch) && !empty($batch)) {
366  $error++;
367  $langs->load("errors");
368  setEventMessages($langs->trans("ErrorProductDoesNotNeedBatchNumber", $tmpproduct->ref), null, 'errors');
369  }
370  }
371  if (!$error) {
372  $tmp = new InventoryLine($db);
373  $tmp->fk_inventory = $object->id;
374  $tmp->fk_warehouse = $fk_warehouse;
375  $tmp->fk_product = $fk_product;
376  $tmp->batch = $batch;
377  $tmp->datec = $now;
378  $tmp->qty_view = $qty;
379 
380  $result = $tmp->create($user);
381  if ($result < 0) {
382  if ($db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
383  $langs->load("errors");
384  setEventMessages($langs->trans("ErrorRecordAlreadyExists"), null, 'errors');
385  } else {
386  dol_print_error($db, $tmp->error, $tmp->errors);
387  }
388  } else {
389  // Clear var
390  $_POST['batch'] = '';
391  $_POST['qtytoadd'] = '';
392  }
393  }
394  }
395 }
396 
397 
398 
399 
400 /*
401  * View
402  */
403 
404 $form = new Form($db);
405 $formproduct = new FormProduct($db);
406 
407 $help_url = '';
408 
409 llxHeader('', $langs->trans('Inventory'), $help_url);
410 
411 
412 // Disable button Generate movement if data were modified and not saved
413 print '<script type="text/javascript">
414 function disablebuttonmakemovementandclose() {
415  console.log("Disable button idbuttonmakemovementandclose until we save");
416  jQuery("#idbuttonmakemovementandclose").attr(\'disabled\',\'disabled\');
417  jQuery("#idbuttonmakemovementandclose").attr(\'onclick\', \'return false;\');
418  jQuery("#idbuttonmakemovementandclose").attr(\'title\',\''.dol_escape_js($langs->trans("SaveQtyFirst")).'\');
419  jQuery("#idbuttonmakemovementandclose").attr(\'class\',\'butActionRefused classfortooltip\');
420 };
421 
422 jQuery(document).ready(function() {
423  jQuery(".realqty").keyup(function() {
424  console.log("keyup on realqty");
425  disablebuttonmakemovementandclose();
426  });
427  jQuery(".realqty").change(function() {
428  console.log("change on realqty");
429  disablebuttonmakemovementandclose();
430  });
431 });
432 </script>';
433 
434 
435 // Part to show record
436 if ($object->id > 0) {
437  $res = $object->fetch_optionals();
438 
439  $head = inventoryPrepareHead($object);
440  print dol_get_fiche_head($head, 'inventory', $langs->trans("Inventory"), -1, 'stock');
441 
442  $formconfirm = '';
443 
444  // Confirmation to delete
445  if ($action == 'delete') {
446  $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('DeleteInventory'), $langs->trans('ConfirmDeleteOrder'), 'confirm_delete', '', 0, 1);
447  }
448  // Confirmation to delete line
449  if ($action == 'deleteline') {
450  $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id.'&lineid='.$lineid, $langs->trans('DeleteLine'), $langs->trans('ConfirmDeleteLine'), 'confirm_deleteline', '', 0, 1);
451  }
452 
453  // Clone confirmation
454  if ($action == 'clone') {
455  // Create an array for form
456  $formquestion = array();
457  $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('ToClone'), $langs->trans('ConfirmCloneMyObject', $object->ref), 'confirm_clone', $formquestion, 'yes', 1);
458  }
459 
460  // Confirmation to close
461  if ($action == 'record') {
462  $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('Close'), $langs->trans('ConfirmFinish'), 'update', '', 0, 1);
463  $action = 'view';
464  }
465 
466  // Confirmation to close
467  if ($action == 'confirm_cancel') {
468  $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('Cancel'), $langs->trans('ConfirmCancel'), 'cancel_record', '', 0, 1);
469  $action = 'view';
470  }
471 
472  // Call Hook formConfirm
473  $parameters = array('formConfirm' => $formconfirm, 'lineid' => $lineid);
474  $reshook = $hookmanager->executeHooks('formConfirm', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
475  if (empty($reshook)) {
476  $formconfirm .= $hookmanager->resPrint;
477  } elseif ($reshook > 0) {
478  $formconfirm = $hookmanager->resPrint;
479  }
480 
481  // Print form confirm
482  print $formconfirm;
483 
484 
485  // Object card
486  // ------------------------------------------------------------
487  $linkback = '<a href="'.DOL_URL_ROOT.'/product/inventory/list.php">'.$langs->trans("BackToList").'</a>';
488 
489  $morehtmlref = '<div class="refidno">';
490  /*
491  // Ref bis
492  $morehtmlref.=$form->editfieldkey("RefBis", 'ref_client', $object->ref_client, $object, $user->rights->inventory->creer, 'string', '', 0, 1);
493  $morehtmlref.=$form->editfieldval("RefBis", 'ref_client', $object->ref_client, $object, $user->rights->inventory->creer, 'string', '', null, null, '', 1);
494  // Thirdparty
495  $morehtmlref.='<br>'.$langs->trans('ThirdParty') . ' : ' . $soc->getNomUrl(1);
496  // Project
497  if (! empty($conf->project->enabled))
498  {
499  $langs->load("projects");
500  $morehtmlref.='<br>'.$langs->trans('Project') . ' ';
501  if ($user->rights->inventory->creer)
502  {
503  if ($action != 'classify')
504  {
505  $morehtmlref.='<a class="editfielda" href="' . $_SERVER['PHP_SELF'] . '?action=classify&token='.newToken().'&id=' . $object->id . '">' . img_edit($langs->transnoentitiesnoconv('SetProject')) . '</a> : ';
506  if ($action == 'classify') {
507  //$morehtmlref.=$form->form_project($_SERVER['PHP_SELF'] . '?id=' . $object->id, $object->socid, $object->fk_project, 'projectid', 0, 0, 1, 1);
508  $morehtmlref.='<form method="post" action="'.$_SERVER['PHP_SELF'].'?id='.$object->id.'">';
509  $morehtmlref.='<input type="hidden" name="action" value="classin">';
510  $morehtmlref.='<input type="hidden" name="token" value="'.newToken().'">';
511  $morehtmlref.=$formproject->select_projects($object->socid, $object->fk_project, 'projectid', $maxlength, 0, 1, 0, 1, 0, 0, '', 1);
512  $morehtmlref.='<input type="submit" class="button valignmiddle" value="'.$langs->trans("Modify").'">';
513  $morehtmlref.='</form>';
514  } else {
515  $morehtmlref.=$form->form_project($_SERVER['PHP_SELF'] . '?id=' . $object->id, $object->socid, $object->fk_project, 'none', 0, 0, 0, 1);
516  }
517  }
518  } else {
519  if (! empty($object->fk_project)) {
520  $proj = new Project($db);
521  $proj->fetch($object->fk_project);
522  $morehtmlref.=$proj->getNomUrl();
523  } else {
524  $morehtmlref.='';
525  }
526  }
527  }
528  */
529  $morehtmlref .= '</div>';
530 
531 
532  dol_banner_tab($object, 'ref', $linkback, 1, 'ref', 'ref', $morehtmlref);
533 
534 
535  print '<div class="fichecenter">';
536  print '<div class="fichehalfleft">';
537  print '<div class="underbanner clearboth"></div>';
538  print '<table class="border centpercent tableforfield">'."\n";
539 
540  // Common attributes
541  include DOL_DOCUMENT_ROOT.'/core/tpl/commonfields_view.tpl.php';
542 
543  // Other attributes. Fields from hook formObjectOptions and Extrafields.
544  include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_view.tpl.php';
545 
546  //print '<tr><td class="titlefield fieldname_invcode">'.$langs->trans("InventoryCode").'</td><td>INV'.$object->id.'</td></tr>';
547 
548  print '</table>';
549  print '</div>';
550  print '</div>';
551 
552  print '<div class="clearboth"></div>';
553 
554  print dol_get_fiche_end();
555 
556 
557  print '<form id="formrecord" name="formrecord" method="POST" action="'.$_SERVER["PHP_SELF"].'">';
558  print '<input type="hidden" name="token" value="'.newToken().'">';
559  print '<input type="hidden" name="action" value="updateinventorylines">';
560  print '<input type="hidden" name="id" value="'.$object->id.'">';
561  if ($backtopage) {
562  print '<input type="hidden" name="backtopage" value="'.$backtopage.'">';
563  }
564 
565 
566  // Buttons for actions
567  if ($action != 'record') {
568  print '<div class="tabsAction">'."\n";
569  $parameters = array();
570  $reshook = $hookmanager->executeHooks('addMoreActionsButtons', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
571  if ($reshook < 0) {
572  setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
573  }
574 
575  if (empty($reshook)) {
576  if ($object->status == Inventory::STATUS_DRAFT) {
577  if ($permissiontoadd) {
578  print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=confirm_validate&confirm=yes&token='.newToken().'">'.$langs->trans("Validate").' ('.$langs->trans("Start").')</a>'."\n";
579  } else {
580  print '<a class="butActionRefused classfortooltip" href="#" title="'.dol_escape_htmltag($langs->trans("NotEnoughPermissions")).'">'.$langs->trans('Validate').' ('.$langs->trans("Start").')</a>'."\n";
581  }
582  }
583 
584  // Save
585  if ($object->status == $object::STATUS_VALIDATED) {
586  if ($permissiontoadd) {
587  print '<a class="butAction classfortooltip" id="idbuttonmakemovementandclose" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=record&token='.newToken().'" title="'.dol_escape_htmltag($langs->trans("MakeMovementsAndClose")).'">'.$langs->trans("MakeMovementsAndClose").'</a>'."\n";
588  } else {
589  print '<a class="butActionRefused classfortooltip" href="#" title="'.dol_escape_htmltag($langs->trans("NotEnoughPermissions")).'">'.$langs->trans('MakeMovementsAndClose').'</a>'."\n";
590  }
591 
592  if ($permissiontoadd) {
593  print '<a class="butActionDelete" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=confirm_cancel&token='.newToken().'">'.$langs->trans("Cancel").'</a>'."\n";
594  }
595  }
596  }
597  print '</div>'."\n";
598 
599  if ($object->status != Inventory::STATUS_DRAFT && $object->status != Inventory::STATUS_VALIDATED) {
600  print '<br><br>';
601  }
602  }
603 
604 
605 
606  if ($object->status == Inventory::STATUS_VALIDATED) {
607  print '<center>';
608  if (!empty($conf->use_javascript_ajax)) {
609  if ($permissiontoadd) {
610  // Link to launch scan tool
611  if (!empty($conf->barcode->enabled) || !empty($conf->productbatch->enabled)) {
612  print '<a href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=updatebyscaning" class="marginrightonly paddingright marginleftonly paddingleft">'.img_picto('', 'barcode', 'class="paddingrightonly"').$langs->trans("UpdateByScaning").'</a>';
613  }
614 
615  // Link to autofill
616  print '<a id="fillwithexpected" class="marginrightonly paddingright marginleftonly paddingleft" href="#">'.img_picto('', 'autofill', 'class="paddingrightonly"').$langs->trans('AutofillWithExpected').'</a>';
617  print '<script>';
618  print '$( document ).ready(function() {';
619  print ' $("#fillwithexpected").on("click",function fillWithExpected(){
620  $(".expectedqty").each(function(){
621  var object = $(this)[0];
622  var objecttofill = $("#"+object.id+"_input")[0];
623  objecttofill.value = object.innerText;
624  jQuery(".realqty").trigger("change");
625  })
626  console.log("Values filled (after click on fillwithexpected)");
627  disablebuttonmakemovementandclose();
628  return false;
629  });';
630  print '});';
631  print '</script>';
632 
633  // Link to reset qty
634  print '<a href="#" id="clearqty" class="marginrightonly paddingright marginleftonly paddingleft">'.img_picto('', 'eraser', 'class="paddingrightonly"').$langs->trans("ClearQtys").'</a>';
635  } else {
636  print '<a class="classfortooltip marginrightonly paddingright marginleftonly paddingleft" href="#" title="'.dol_escape_htmltag($langs->trans("NotEnoughPermissions")).'">'.$langs->trans("Save").'</a>'."\n";
637  }
638  }
639  print '<br>';
640  print '<br>';
641  print '</center>';
642  }
643 
644 
645  // Popup for mass barcode scanning
646  if ($action == 'updatebyscaning') {
647  if ($permissiontoadd) {
648  // Output the javascript to manage the scanner tool.
649  print '<script>';
650 
651  print '
652  var duplicatedbatchcode = [];
653  var errortab1 = [];
654  var errortab2 = [];
655  var errortab3 = [];
656  var errortab4 = [];
657 
658  function barcodescannerjs(){
659  console.log("We catch inputs in scanner box");
660  jQuery("#scantoolmessage").text();
661 
662  var selectaddorreplace = $("select[name=selectaddorreplace]").val();
663  var barcodemode = $("input[name=barcodemode]:checked").val();
664  var barcodeproductqty = $("input[name=barcodeproductqty]").val();
665  var textarea = $("textarea[name=barcodelist]").val();
666  var textarray = textarea.split(/[\s,;]+/);
667  var tabproduct = [];
668  duplicatedbatchcode = [];
669  errortab1 = [];
670  errortab2 = [];
671  errortab3 = [];
672  errortab4 = [];
673 
674  textarray = textarray.filter(function(value){
675  return value != "";
676  });
677  if(textarray.some((element) => element != "")){
678  $(".expectedqty").each(function(){
679  id = this.id;
680  console.log("Analyze the line "+id+" in inventory, barcodemode="+barcodemode);
681  warehouse = $("#"+id+"_warehouse").attr(\'data-ref\');
682  //console.log(warehouse);
683  productbarcode = $("#"+id+"_product").attr(\'data-barcode\');
684  //console.log(productbarcode);
685  productbatchcode = $("#"+id+"_batch").attr(\'data-batch\');
686  //console.log(productbatchcode);
687 
688  if (barcodemode != "barcodeforproduct") {
689  tabproduct.forEach(product=>{
690  console.log("product.Batch="+product.Batch+" productbatchcode="+productbatchcode);
691  if(product.Batch != "" && product.Batch == productbatchcode){
692  console.log("duplicate batch code found for batch code "+productbatchcode);
693  duplicatedbatchcode.push(productbatchcode);
694  }
695  })
696  }
697  productinput = $("#"+id+"_input").val();
698  if(productinput == ""){
699  productinput = 0
700  }
701  tabproduct.push({\'Id\':id,\'Warehouse\':warehouse,\'Barcode\':productbarcode,\'Batch\':productbatchcode,\'Qty\':productinput,\'fetched\':false});
702  });
703 
704  console.log("Loop on each record entered in the textarea");
705  textarray.forEach(function(element,index){
706  console.log("Process record element="+element+" id="+id);
707  var verify_batch = false;
708  var verify_barcode = false;
709  switch(barcodemode){
710  case "barcodeforautodetect":
711  verify_barcode = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"barcode",true);
712  verify_batch = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"lotserial",true);
713  break;
714  case "barcodeforproduct":
715  verify_barcode = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"barcode");
716  break;
717  case "barcodeforlotserial":
718  verify_batch = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"lotserial");
719  break;
720  default:
721  alert(\''.dol_escape_js($langs->trans("ErrorWrongBarcodemode")).' "\'+barcodemode+\'"\');
722  throw \''.dol_escape_js($langs->trans('ErrorWrongBarcodemode')).' "\'+barcodemode+\'"\';
723  }
724 
725  if (verify_batch == false && verify_barcode == false) { /* If the 2 flags are false, not found error */
726  errortab2.push(element);
727  } else if (verify_batch == true && verify_barcode == true) { /* If the 2 flags are true, error: we don t know which one to take */
728  errortab3.push(element);
729  } else if (verify_batch == true) {
730  console.log("element="+element);
731  console.log(duplicatedbatchcode);
732  if (duplicatedbatchcode.includes(element)) {
733  errortab1.push(element);
734  }
735  }
736  });
737 
738  if (Object.keys(errortab1).length < 1 && Object.keys(errortab2).length < 1 && Object.keys(errortab3).length < 1) {
739  tabproduct.forEach(product => {
740  if(product.Qty!=0){
741  console.log("We change #"+product.Id+"_input to match input in scanner box");
742  if(product.hasOwnProperty("reelqty")){
743  $.ajax({ url: \''.DOL_URL_ROOT.'/product/inventory/ajax/searchfrombarcode.php\',
744  data: { "token":"'.newToken().'", "action":"addnewlineproduct", "fk_entrepot":product.Warehouse, "batch":product.Batch, "fk_inventory":'.dol_escape_js($object->id).', "fk_product":product.fk_product, "reelqty":product.reelqty},
745  type: \'POST\',
746  async: false,
747  success: function(response) {
748  response = JSON.parse(response);
749  if(response.status == "success"){
750  console.log(response.message);
751  $("<input type=\'text\' value=\'"+product.Qty+"\' />")
752  .attr("id", "id_"+response.id_line+"_input")
753  .attr("name", "id_"+response.id_line)
754  .appendTo("#formrecord");
755  }else{
756  console.error(response.message);
757  }
758  },
759  error : function(output) {
760  console.error("Error on line creation function");
761  },
762  });
763  } else {
764  $("#"+product.Id+"_input").val(product.Qty);
765  }
766  }
767  });
768  jQuery("#scantoolmessage").text("'.dol_escape_js($langs->transnoentities("QtyWasAddedToTheScannedBarcode")).'\n");
769  /* document.forms["formrecord"].submit(); */
770  } else {
771  let stringerror = "";
772  if (Object.keys(errortab1).length > 0) {
773  stringerror += "<br>'.dol_escape_js($langs->transnoentities('ErrorSameBatchNumber')).': ";
774  errortab1.forEach(element => {
775  stringerror += (element + ", ")
776  });
777  stringerror = stringerror.slice(0, -2); /* Remove last ", " */
778  }
779  if (Object.keys(errortab2).length > 0) {
780  stringerror += "<br>'.dol_escape_js($langs->transnoentities('ErrorCantFindCodeInInventory')).': ";
781  errortab2.forEach(element => {
782  stringerror += (element + ", ")
783  });
784  stringerror = stringerror.slice(0, -2); /* Remove last ", " */
785  }
786  if (Object.keys(errortab3).length > 0) {
787  stringerror += "<br>'.dol_escape_js($langs->transnoentities('ErrorCodeScannedIsBothProductAndSerial')).': ";
788  errortab3.forEach(element => {
789  stringerror += (element + ", ")
790  });
791  stringerror = stringerror.slice(0, -2); /* Remove last ", " */
792  }
793  if (Object.keys(errortab4).length > 0) {
794  stringerror += "<br>'.dol_escape_js($langs->transnoentities('ErrorBarcodeNotFoundForProductWarehouse')).': ";
795  errortab4.forEach(element => {
796  stringerror += (element + ", ")
797  });
798  stringerror = stringerror.slice(0, -2); /* Remove last ", " */
799  }
800 
801  jQuery("#scantoolmessage").html(\''.dol_escape_js($langs->transnoentities("ErrorOnElementsInventory")).'\' + stringerror);
802  //alert("'.dol_escape_js($langs->trans("ErrorOnElementsInventory")).' :\n" + stringerror);
803  }
804  }
805 
806  }
807 
808  /* This methode is called by parent barcodescannerjs() */
809  function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,mode,autodetect=false){
810  BarcodeIsInProduct=0;
811  newproductrow=0
812  result=false;
813  tabproduct.forEach(product => {
814  $.ajax({ url: \''.DOL_URL_ROOT.'/product/inventory/ajax/searchfrombarcode.php\',
815  data: { "token":"'.newToken().'", "action":"existbarcode", '.(!empty($object->fk_warehouse)?'"fk_entrepot":'.$object->fk_warehouse.', ':'').(!empty($object->fk_product)?'"fk_product":'.$object->fk_product.', ':'').'"barcode":element, "product":product, "mode":mode},
816  type: \'POST\',
817  async: false,
818  success: function(response) {
819  response = JSON.parse(response);
820  if (response.status == "success"){
821  console.log(response.message);
822  if(!newproductrow){
823  newproductrow = response.object;
824  }
825  }else{
826  if (mode!="lotserial" && autodetect==false && !errortab4.includes(element)){
827  errortab4.push(element);
828  console.error(response.message);
829  }
830  }
831  },
832  error : function(output) {
833  console.error("Error on barcodeserialforproduct function");
834  },
835  });
836  console.log("Product "+(index+=1)+": "+element);
837  if(mode == "barcode"){
838  testonproduct = product.Barcode
839  }else if (mode == "lotserial"){
840  testonproduct = product.Batch
841  }
842  if(testonproduct == element){
843  if(selectaddorreplace == "add"){
844  productqty = parseInt(product.Qty,10);
845  product.Qty = productqty + parseInt(barcodeproductqty,10);
846  }else if(selectaddorreplace == "replace"){
847  if(product.fetched == false){
848  product.Qty = barcodeproductqty
849  product.fetched=true
850  }else{
851  productqty = parseInt(product.Qty,10);
852  product.Qty = productqty + parseInt(barcodeproductqty,10);
853  }
854  }
855  BarcodeIsInProduct+=1;
856  }
857  })
858  if(BarcodeIsInProduct==0 && newproductrow!=0){
859  tabproduct.push({\'Id\':tabproduct.length-1,\'Warehouse\':newproductrow.fk_warehouse,\'Barcode\':mode=="barcode"?element:null,\'Batch\':mode=="lotserial"?element:null,\'Qty\':barcodeproductqty,\'fetched\':true,\'reelqty\':newproductrow.reelqty,\'fk_product\':newproductrow.fk_product,\'mode\':mode});
860  result = true;
861  }
862  if(BarcodeIsInProduct > 0){
863  result = true;
864  }
865  return result;
866  }
867  ';
868  print '</script>';
869  }
870  include DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php';
871  $formother = new FormOther($db);
872  print $formother->getHTMLScannerForm("barcodescannerjs", 'all');
873  }
874 
875  //Call method to undo changes in real qty
876  print '<script>';
877  print 'jQuery(document).ready(function() {
878  $("#clearqty").on("click", function() {
879  console.log("Clear all values");
880  disablebuttonmakemovementandclose();
881  jQuery(".realqty").val("");
882  jQuery(".realqty").trigger("change");
883  return false; /* disable submit */
884  });
885  $(".undochangesqty").on("click", function undochangesqty() {
886  console.log("Clear value of inventory line");
887  id = this.id;
888  id = id.split("_")[1];
889  tmpvalue = $("#id_"+id+"_input_tmp").val()
890  $("#id_"+id+"_input")[0].value = tmpvalue;
891  disablebuttonmakemovementandclose();
892  return false; /* disable submit */
893  });
894  });';
895  print '</script>';
896 
897  print '<div class="fichecenter">';
898  //print '<div class="fichehalfleft">';
899  print '<div class="clearboth"></div>';
900 
901  //print load_fiche_titre($langs->trans('Consumption'), '', '');
902 
903  print '<div class="div-table-responsive-no-min">';
904  print '<table id="tablelines" class="noborder noshadow centpercent">';
905 
906  print '<tr class="liste_titre">';
907  print '<td>'.$langs->trans("Warehouse").'</td>';
908  print '<td>'.$langs->trans("Product").'</td>';
909  if (!empty($conf->productbatch->enabled)) {
910  print '<td>';
911  print $langs->trans("Batch");
912  print '</td>';
913  }
914  print '<td class="right">'.$langs->trans("ExpectedQty").'</td>';
915  if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
916  print '<td class="right">'.$langs->trans('PMPExpected').'</td>';
917  print '<td class="right">'.$langs->trans('ExpectedValuation').'</td>';
918  print '<td class="right">'.$form->textwithpicto($langs->trans("RealQty"), $langs->trans("InventoryRealQtyHelp")).'</td>';
919  print '<td class="right">'.$langs->trans('PMPReal').'</td>';
920  print '<td class="right">'.$langs->trans('RealValuation').'</td>';
921  } else {
922  print '<td class="right">';
923  print $form->textwithpicto($langs->trans("RealQty"), $langs->trans("InventoryRealQtyHelp"));
924  print '</td>';
925  }
926  if ($object->status == $object::STATUS_DRAFT || $object->status == $object::STATUS_VALIDATED) {
927  // Actions or link to stock movement
928  print '<td class="center">';
929  print '</td>';
930  } else {
931  // Actions or link to stock movement
932  print '<td class="right">';
933  //print $langs->trans("StockMovement");
934  print '</td>';
935  }
936  print '</tr>';
937 
938  // Line to add a new line in inventory
939  if ($object->status == $object::STATUS_DRAFT || $object->status == $object::STATUS_VALIDATED) {
940  print '<tr>';
941  print '<td>';
942  print $formproduct->selectWarehouses((GETPOSTISSET('fk_warehouse') ? GETPOST('fk_warehouse', 'int') : $object->fk_warehouse), 'fk_warehouse', 'warehouseopen', 1, 0, 0, '', 0, 0, array(), 'maxwidth300');
943  print '</td>';
944  print '<td>';
945  print $form->select_produits((GETPOSTISSET('fk_product') ? GETPOST('fk_product', 'int') : $object->fk_product), 'fk_product', '', 0, 0, -1, 2, '', 0, null, 0, '1', 0, 'maxwidth300');
946  print '</td>';
947  if (!empty($conf->productbatch->enabled)) {
948  print '<td>';
949  print '<input type="text" name="batch" class="maxwidth100" value="'.(GETPOSTISSET('batch') ? GETPOST('batch') : '').'">';
950  print '</td>';
951  }
952  print '<td class="right"></td>';
953  if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
954  print '<td class="right">';
955  print '</td>';
956  print '<td class="right">';
957  print '</td>';
958  print '<td class="right">';
959  print '<input type="text" name="qtytoadd" class="maxwidth75" value="">';
960  print '</td>';
961  print '<td class="right">';
962  print '</td>';
963  print '<td class="right">';
964  print '</td>';
965  } else {
966  print '<td class="right">';
967  print '<input type="text" name="qtytoadd" class="maxwidth75" value="">';
968  print '</td>';
969  }
970  // Actions
971  print '<td class="center">';
972  print '<input type="submit" class="button paddingright" name="addline" value="'.$langs->trans("Add").'">';
973  print '</td>';
974  print '</tr>';
975  }
976 
977  // Request to show lines of inventory (prefilled after start/validate step)
978  $sql = 'SELECT id.rowid, id.datec as date_creation, id.tms as date_modification, id.fk_inventory, id.fk_warehouse,';
979  $sql .= ' id.fk_product, id.batch, id.qty_stock, id.qty_view, id.qty_regulated, id.fk_movement, id.pmp_real, id.pmp_expected';
980  $sql .= ' FROM '.MAIN_DB_PREFIX.'inventorydet as id';
981  $sql .= ' WHERE id.fk_inventory = '.((int) $object->id);
982 
983  $cacheOfProducts = array();
984  $cacheOfWarehouses = array();
985 
986  //$sql = '';
987  $resql = $db->query($sql);
988  if ($resql) {
989  $num = $db->num_rows($resql);
990 
991  $i = 0;
992  $hasinput = false;
993  $totalarray = array();
994  while ($i < $num) {
995  $obj = $db->fetch_object($resql);
996 
997  if (isset($cacheOfWarehouses[$obj->fk_warehouse])) {
998  $warehouse_static = $cacheOfWarehouses[$obj->fk_warehouse];
999  } else {
1000  $warehouse_static = new Entrepot($db);
1001  $warehouse_static->fetch($obj->fk_warehouse);
1002 
1003  $cacheOfWarehouses[$warehouse_static->id] = $warehouse_static;
1004  }
1005 
1006  // Load real stock we have now
1007  $option = '';
1008  if (isset($cacheOfProducts[$obj->fk_product])) {
1009  $product_static = $cacheOfProducts[$obj->fk_product];
1010  } else {
1011  $product_static = new Product($db);
1012  $result = $product_static->fetch($obj->fk_product, '', '', '', 1, 1, 1);
1013 
1014  //$option = 'nobatch';
1015  $option .= ',novirtual';
1016  $product_static->load_stock($option); // Load stock_reel + stock_warehouse.
1017 
1018  $cacheOfProducts[$product_static->id] = $product_static;
1019  }
1020 
1021  print '<tr class="oddeven">';
1022  print '<td id="id_'.$obj->rowid.'_warehouse" data-ref="'.dol_escape_htmltag($warehouse_static->ref).'">';
1023  print $warehouse_static->getNomUrl(1);
1024  print '</td>';
1025  print '<td id="id_'.$obj->rowid.'_product" data-ref="'.dol_escape_htmltag($product_static->ref).'" data-barcode="'.dol_escape_htmltag($product_static->barcode).'">';
1026  print $product_static->getNomUrl(1).' - '.$product_static->label;
1027  print '</td>';
1028 
1029  if (!empty($conf->productbatch->enabled)) {
1030  print '<td id="id_'.$obj->rowid.'_batch" data-batch="'.dol_escape_htmltag($obj->batch).'">';
1031  $batch_static = new Productlot($db);
1032  $res = $batch_static->fetch(0, $product_static->id, $obj->batch);
1033  if ($res) {
1034  print $batch_static->getNomUrl(1);
1035  } else {
1036  print dol_escape_htmltag($obj->batch);
1037  }
1038  print '</td>';
1039  }
1040 
1041  // Expected quantity = Quantity in stock when we start inventory
1042  print '<td class="right expectedqty" id="id_'.$obj->rowid.'" title="Stock viewed at last update: '.$obj->qty_stock.'">';
1043  $valuetoshow = $obj->qty_stock;
1044  // For inventory not yet close, we overwrite with the real value in stock now
1045  if ($object->status == $object::STATUS_DRAFT || $object->status == $object::STATUS_VALIDATED) {
1046  if (!empty($conf->productbatch->enabled) && $product_static->hasbatch()) {
1047  $valuetoshow = $product_static->stock_warehouse[$obj->fk_warehouse]->detail_batch[$obj->batch]->qty;
1048  } else {
1049  $valuetoshow = $product_static->stock_warehouse[$obj->fk_warehouse]->real;
1050  }
1051  }
1052  print price2num($valuetoshow, 'MS');
1053  print '<input type="hidden" name="stock_qty_'.$obj->rowid.'" value="'.$valuetoshow.'">';
1054  print '</td>';
1055 
1056  // Real quantity
1057  if ($object->status == $object::STATUS_DRAFT || $object->status == $object::STATUS_VALIDATED) {
1058  $qty_view = GETPOST("id_".$obj->rowid) && price2num(GETPOST("id_".$obj->rowid), 'MS') >= 0 ? GETPOST("id_".$obj->rowid) : $obj->qty_view;
1059 
1060  //if (!$hasinput && $qty_view !== null && $obj->qty_stock != $qty_view) {
1061  if ($qty_view != '') {
1062  $hasinput = true;
1063  }
1064 
1065  if (! empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
1066  //PMP Expected
1067  if (! empty($obj->pmp_expected)) $pmp_expected = $obj->pmp_expected;
1068  else $pmp_expected = $product_static->pmp;
1069  $pmp_valuation = $pmp_expected * $valuetoshow;
1070  print '<td class="right">';
1071  print price($pmp_expected);
1072  print '<input type="hidden" name="expectedpmp_'.$obj->rowid.'" value="'.$pmp_expected.'"/>';
1073  print '</td>';
1074  print '<td class="right">';
1075  print price($pmp_valuation);
1076  print '</td>';
1077 
1078  print '<td class="right">';
1079  print '<a id="undochangesqty_'.$obj->rowid.'" href="#" class="undochangesqty reposition marginrightonly" title="'.dol_escape_htmltag($langs->trans("Clear")).'">';
1080  print img_picto('', 'eraser', 'class="opacitymedium"');
1081  print '</a>';
1082  print '<input type="text" class="maxwidth50 right realqty" name="id_'.$obj->rowid.'" id="id_'.$obj->rowid.'_input" value="'.$qty_view.'">';
1083  print '</td>';
1084 
1085  //PMP Real
1086  print '<td class="right">';
1087 
1088 
1089  if (! empty($obj->pmp_real)) $pmp_real = $obj->pmp_real;
1090  else $pmp_real = $product_static->pmp;
1091  $pmp_valuation_real = $pmp_real * $qty_view;
1092  print '<input type="text" class="maxwidth75 right realpmp'.$obj->fk_product.'" name="realpmp_'.$obj->rowid.'" id="id_'.$obj->rowid.'_input_pmp" value="'.price2num($pmp_real).'">';
1093  print '</td>';
1094  print '<td class="right">';
1095  print '<input type="text" class="maxwidth75 right realvaluation'.$obj->fk_product.'" name="realvaluation_'.$obj->rowid.'" id="id_'.$obj->rowid.'_input_real_valuation" value="'.$pmp_valuation_real.'">';
1096  print '</td>';
1097 
1098  $totalExpectedValuation += $pmp_valuation;
1099  $totalRealValuation += $pmp_valuation_real;
1100  } else {
1101  print '<td class="right">';
1102  print '<a id="undochangesqty_'.$obj->rowid.'" href="#" class="undochangesqty reposition marginrightonly" title="'.dol_escape_htmltag($langs->trans("Clear")).'">';
1103  print img_picto('', 'eraser', 'class="opacitymedium"');
1104  print '</a>';
1105  print '<input type="text" class="maxwidth50 right realqty" name="id_'.$obj->rowid.'" id="id_'.$obj->rowid.'_input" value="'.$qty_view.'">';
1106  print '</td>';
1107  }
1108 
1109  // Picto delete line
1110  print '<td class="right">';
1111  print '<a class="reposition" href="'.DOL_URL_ROOT.'/product/inventory/inventory.php?id='.$object->id.'&lineid='.$obj->rowid.'&action=deleteline&token='.newToken().'">'.img_delete().'</a>';
1112  $qty_tmp = price2num(GETPOST("id_".$obj->rowid."_input_tmp", 'MS')) >= 0 ? GETPOST("id_".$obj->rowid."_input_tmp") : $qty_view;
1113  print '<input type="hidden" class="maxwidth50 right realqty" name="id_'.$obj->rowid.'_input_tmp" id="id_'.$obj->rowid.'_input_tmp" value="'.$qty_tmp.'">';
1114  print '</td>';
1115  } else {
1116  if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
1117  //PMP Expected
1118  if (! empty($obj->pmp_expected)) $pmp_expected = $obj->pmp_expected;
1119  else $pmp_expected = $product_static->pmp;
1120  $pmp_valuation = $pmp_expected * $valuetoshow;
1121  print '<td class="right">';
1122  print price($pmp_expected);
1123  print '</td>';
1124  print '<td class="right">';
1125  print price($pmp_valuation);
1126  print '</td>';
1127 
1128  print '<td class="right nowraponall">';
1129  print $obj->qty_view; // qty found
1130  print '</td>';
1131 
1132  //PMP Real
1133  print '<td class="right">';
1134  if (! empty($obj->pmp_real)) $pmp_real = $obj->pmp_real;
1135  else $pmp_real = $product_static->pmp;
1136  $pmp_valuation_real = $pmp_real * $obj->qty_view;
1137  print price($pmp_real);
1138  print '</td>';
1139  print '<td class="right">';
1140  print price($pmp_valuation_real);
1141  print '</td>';
1142  print '<td class="nowraponall right">';
1143 
1144  $totalExpectedValuation += $pmp_valuation;
1145  $totalRealValuation += $pmp_valuation_real;
1146  } else {
1147  print '<td class="right nowraponall">';
1148  print $obj->qty_view; // qty found
1149  print '</td>';
1150  }
1151  if ($obj->fk_movement > 0) {
1152  $stockmovment = new MouvementStock($db);
1153  $stockmovment->fetch($obj->fk_movement);
1154  print $stockmovment->getNomUrl(1, 'movements');
1155  }
1156  print '</td>';
1157  }
1158  print '</tr>';
1159 
1160  $i++;
1161  }
1162  } else {
1163  dol_print_error($db);
1164  }
1165  if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
1166  print '<tr class="liste_total">';
1167  print '<td colspan="4">'.$langs->trans("Total").'</td>';
1168  print '<td class="right" colspan="2">'.price($totalExpectedValuation).'</td>';
1169  print '<td class="right" id="totalRealValuation" colspan="3">'.price($totalRealValuation).'</td>';
1170  print '<td></td>';
1171  print '</tr>';
1172  }
1173  print '</table>';
1174 
1175  print '</div>';
1176 
1177  if ($object->status == $object::STATUS_VALIDATED) {
1178  print '<center><input id="submitrecord" type="submit" class="button button-save" name="save" value="'.$langs->trans("Save").'"></center>';
1179  }
1180 
1181  print '</div>';
1182 
1183 
1184  // Call method to disable the button if no qty entered yet for inventory
1185 
1186  if ($object->status != $object::STATUS_VALIDATED || !$hasinput) {
1187  print '<script type="text/javascript">
1188  jQuery(document).ready(function() {
1189  console.log("Call disablebuttonmakemovementandclose because status = '.((int) $object->status).' or $hasinput = '.((int) $hasinput).'");
1190  disablebuttonmakemovementandclose();
1191  });
1192  </script>';
1193  }
1194  print '</form>';
1195 
1196 
1197  if (! empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
1198  ?>
1199  <script type="text/javascript">
1200  $('.realqty').on('change', function () {
1201  let realqty = $(this).closest('tr').find('.realqty').val();
1202  let inputPmp = $(this).closest('tr').find('input[class*=realpmp]');
1203  let realpmp = $(inputPmp).val();
1204  if (!isNaN(realqty) && !isNaN(realpmp)) {
1205  let realval = realqty * realpmp;
1206  $(this).closest('tr').find('input[name^=realvaluation]').val(realval.toFixed(2));
1207  }
1208  updateTotalValuation();
1209  });
1210 
1211  $('input[class*=realpmp]').on('change', function () {
1212  let inputQtyReal = $(this).closest('tr').find('.realqty');
1213  let realqty = $(inputQtyReal).val();
1214  let inputPmp = $(this).closest('tr').find('input[class*=realpmp]');
1215  console.log(inputPmp);
1216  let realPmpClassname = $(inputPmp).attr('class').match(/[\w-]*realpmp[\w-]*/g)[0];
1217  let realpmp = $(inputPmp).val();
1218  if (!isNaN(realpmp)) {
1219  $('.'+realPmpClassname).val(realpmp); //For batch case if pmp is changed we change it everywhere it's same product and calc back everything
1220 
1221  if (!isNaN(realqty)) {
1222  let realval = realqty * realpmp;
1223  $(this).closest('tr').find('input[name^=realvaluation]').val(realval.toFixed(2));
1224  }
1225  $('.realqty').trigger('change');
1226  updateTotalValuation();
1227  }
1228  });
1229 
1230  $('input[name^=realvaluation]').on('change', function () {
1231  let inputQtyReal = $(this).closest('tr').find('.realqty');
1232  let realqty = $(inputQtyReal).val();
1233  let inputPmp = $(this).closest('tr').find('input[class*=realpmp]');
1234  let inputRealValuation = $(this).closest('tr').find('input[name^=realvaluation]');
1235  let realPmpClassname = $(inputPmp).attr('class').match(/[\w-]*realpmp[\w-]*/g)[0];
1236  let realvaluation = $(inputRealValuation).val();
1237  if (!isNaN(realvaluation) && !isNaN(realqty) && realvaluation !== '' && realqty !== '' && realqty !== 0) {
1238  let realpmp = realvaluation / realqty
1239  $('.'+realPmpClassname).val(realpmp); //For batch case if pmp is changed we change it everywhere it's same product and calc back everything
1240  $('.realqty').trigger('change');
1241  updateTotalValuation();
1242  }
1243  });
1244 
1245  function updateTotalValuation() {
1246  let total = 0;
1247  $('input[name^=realvaluation]').each(function( index ) {
1248  let val = $(this).val();
1249  if(!isNaN(val)) total += parseFloat($(this).val());
1250  });
1251  let currencyFractionDigits = new Intl.NumberFormat('fr-FR', {
1252  style: 'currency',
1253  currency: 'EUR',
1254  }).resolvedOptions().maximumFractionDigits;
1255  $('#totalRealValuation').html(total.toLocaleString('fr-FR', {
1256  maximumFractionDigits: currencyFractionDigits
1257  }));
1258  }
1259 
1260  </script>
1261  <?php
1262  }
1263 }
1264 
1265 // End of page
1266 llxFooter();
1267 $db->close();
GETPOST($paramname, $check= 'alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
Class to manage stock movements.
Class with list of lots and properties.
if($cancel &&!$id) if($action== 'add'&&!$cancel) if($action== 'delete') if($id) $form
Actions.
Definition: card.php:142
Class to manage products or services.
dol_escape_js($stringtoescape, $mode=0, $noescapebackslashn=0)
Returns text escaped for inclusion into javascript code.
dol_now($mode= 'auto')
Return date for now.
if(!defined('NOREQUIRESOC')) if(!defined('NOREQUIRETRAN')) if(!defined('NOCSRFCHECK')) if(!defined('NOTOKENRENEWAL')) if(!defined('NOREQUIREMENU')) if(!defined('NOREQUIREHTML')) if(!defined('NOREQUIREAJAX')) llxHeader()
Empty header.
Definition: wrapper.php:59
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags= '', $escapeonlyhtmltags=0)
Returns text escaped for inclusion in HTML alt or title tags, or into values of HTML input fields...
if(GETPOST('button_removefilter_x', 'alpha')||GETPOST('button_removefilter.x', 'alpha')||GETPOST('button_removefilter', 'alpha')) if(GETPOST('button_search_x', 'alpha')||GETPOST('button_search.x', 'alpha')||GETPOST('button_search', 'alpha')) if($action=="save"&&empty($cancel)) $help_url
View.
Definition: agenda.php:116
Class for Inventory.
price($amount, $form=0, $outlangs= '', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code= '')
Function to format a value into an amount for visual output Function used into PDF and HTML pages...
Class with static methods for building HTML components related to products Only components common to ...
Class to manage standard extra fields.
setEventMessages($mesg, $mesgs, $style= 'mesgs', $messagekey= '')
Set event messages in dol_events session object.
Class to manage generation of HTML components Only common components must be here.
GETPOSTISSET($paramname)
Return true if we are in a context of submitting the parameter $paramname from a POST of a form...
price2num($amount, $rounding= '', $option=0)
Function that return a number with universal decimal format (decimal separator is &#39;...
img_picto($titlealt, $picto, $moreatt= '', $pictoisfullpath=false, $srconly=0, $notitle=0, $alt= '', $morecss= '', $marginleftonlyshort=2)
Show picto whatever it&#39;s its name (generic function)
Classe permettant la generation de composants html autre Only common components are here...
restrictedArea($user, $features, $objectid=0, $tableandshare= '', $feature2= '', $dbt_keyfield= 'fk_soc', $dbt_select= 'rowid', $isdraft=0, $mode=0)
Check permissions of a user to show a page and an object.
if(isModEnabled('facture')&&!empty($user->rights->facture->lire)) if((isModEnabled('fournisseur')&&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD)&&$user->rights->fournisseur->facture->lire)||(isModEnabled('supplier_invoice')&&$user->rights->supplier_invoice->lire)) if(isModEnabled('don')&&!empty($user->rights->don->lire)) if(isModEnabled('tax')&&!empty($user->rights->tax->charges->lire)) if(isModEnabled('facture')&&isModEnabled('commande')&&$user->rights->commande->lire &&empty($conf->global->WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER)) $resql
Social contributions to pay.
Definition: index.php:742
dol_get_fiche_head($links=array(), $active= '', $title= '', $notab=0, $picto= '', $pictoisfullpath=0, $morehtmlright= '', $morecss= '', $limittoshow=0, $moretabssuffix= '')
Show tabs of a record.
div float
Buy price without taxes.
Definition: style.css.php:809
Class InventoryLine.
dol_print_error($db= '', $error= '', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
newToken()
Return the value of token currently saved into session with name &#39;newtoken&#39;.
inventoryPrepareHead(&$inventory, $title= 'Inventory', $get= '')
Define head array for tabs of inventory tools setup pages.
dol_get_fiche_end($notab=0)
Return tab footer of a card.
dol_banner_tab($object, $paramid, $morehtml= '', $shownav=1, $fieldid= 'rowid', $fieldref= 'ref', $morehtmlref= '', $moreparam= '', $nodbprefix=0, $morehtmlleft= '', $morehtmlstatus= '', $onlybanner=0, $morehtmlright= '')
Show tab footer of a card.
llxFooter()
Empty footer.
Definition: wrapper.php:73
img_delete($titlealt= 'default', $other= 'class="pictodelete"', $morecss= '')
Show delete logo.
$formconfirm
if ($action == &#39;delbookkeepingyear&#39;) {
if(preg_match('/crypted:/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
Definition: repair.php:119
Class to manage warehouses.