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