dolibarr  18.0.6
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 $paramwithsearch = '';
101 if ($limit > 0 && $limit != $conf->liste_limit) {
102  $paramwithsearch .= '&limit='.((int) $limit);
103 }
104 
105 
106 if (empty($conf->global->MAIN_USE_ADVANCED_PERMS)) {
107  $permissiontoadd = $user->rights->stock->creer;
108  $permissiontodelete = $user->rights->stock->supprimer;
109 } else {
110  $permissiontoadd = $user->rights->stock->inventory_advance->write;
111  $permissiontodelete = $user->rights->stock->inventory_advance->write;
112 }
113 
114 $now = dol_now();
115 
116 
117 
118 /*
119  * Actions
120  */
121 
122 if ($cancel) {
123  $action = '';
124 }
125 
126 
127 $parameters = array();
128 $reshook = $hookmanager->executeHooks('doActions', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
129 if ($reshook < 0) {
130  setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
131 }
132 
133 if (empty($reshook)) {
134  $error = 0;
135 
136  if ($action == 'cancel_record' && $permissiontoadd) {
137  $object->setCanceled($user);
138  }
139 
140  // Close inventory by recording the stock movements
141  if ($action == 'update' && !empty($user->rights->stock->mouvement->creer) && $object->status == $object::STATUS_VALIDATED) {
142  $stockmovment = new MouvementStock($db);
143  $stockmovment->setOrigin($object->element, $object->id);
144 
145  $cacheOfProducts = array();
146 
147  $db->begin();
148 
149  $sql = 'SELECT id.rowid, id.datec as date_creation, id.tms as date_modification, id.fk_inventory, id.fk_warehouse,';
150  $sql .= ' id.fk_product, id.batch, id.qty_stock, id.qty_view, id.qty_regulated, id.pmp_real';
151  $sql .= ' FROM '.MAIN_DB_PREFIX.'inventorydet as id';
152  $sql .= ' WHERE id.fk_inventory = '.((int) $object->id);
153 
154  $resql = $db->query($sql);
155  if ($resql) {
156  $num = $db->num_rows($resql);
157  $i = 0;
158  $totalarray = array();
159  while ($i < $num) {
160  $line = $db->fetch_object($resql);
161 
162  $qty_stock = $line->qty_stock;
163  $qty_view = $line->qty_view; // The quantity viewed by inventorier, the qty we target
164 
165 
166  // Load real stock we have now.
167  if (isset($cacheOfProducts[$line->fk_product])) {
168  $product_static = $cacheOfProducts[$line->fk_product];
169  } else {
170  $product_static = new Product($db);
171  $result = $product_static->fetch($line->fk_product, '', '', '', 1, 1, 1);
172 
173  //$option = 'nobatch';
174  $option .= ',novirtual';
175  $product_static->load_stock($option); // Load stock_reel + stock_warehouse.
176 
177  $cacheOfProducts[$product_static->id] = $product_static;
178  }
179 
180  // Get the real quantity in stock now, but before the stock move for inventory.
181  $realqtynow = $product_static->stock_warehouse[$line->fk_warehouse]->real;
182  if (isModEnabled('productbatch') && $product_static->hasbatch()) {
183  $realqtynow = $product_static->stock_warehouse[$line->fk_warehouse]->detail_batch[$line->batch]->qty;
184  }
185 
186 
187  if (!is_null($qty_view)) {
188  $stock_movement_qty = price2num($qty_view - $realqtynow, 'MS');
189  if ($stock_movement_qty != 0) {
190  if ($stock_movement_qty < 0) {
191  $movement_type = 1;
192  } else {
193  $movement_type = 0;
194  }
195 
196  $datemovement = '';
197  //$inventorycode = 'INV'.$object->id;
198  $inventorycode = 'INV-'.$object->ref;
199  $price = 0;
200  if (!empty($line->pmp_real) && !empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) $price = $line->pmp_real;
201 
202  $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);
203  if ($idstockmove < 0) {
204  $error++;
205  setEventMessages($stockmovment->error, $stockmovment->errors, 'errors');
206  break;
207  }
208 
209  // Update line with id of stock movement (and the start quantity if it has changed this last recording)
210  $sqlupdate = "UPDATE ".MAIN_DB_PREFIX."inventorydet";
211  $sqlupdate .= " SET fk_movement = ".((int) $idstockmove);
212  if ($qty_stock != $realqtynow) {
213  $sqlupdate .= ", qty_stock = ".((float) $realqtynow);
214  }
215  $sqlupdate .= " WHERE rowid = ".((int) $line->rowid);
216  $resqlupdate = $db->query($sqlupdate);
217  if (! $resqlupdate) {
218  $error++;
219  setEventMessages($db->lasterror(), null, 'errors');
220  break;
221  }
222  }
223 
224  if (!empty($line->pmp_real) && !empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
225  $sqlpmp = 'UPDATE '.MAIN_DB_PREFIX.'product SET pmp = '.((float) $line->pmp_real).' WHERE rowid = '.((int) $line->fk_product);
226  $resqlpmp = $db->query($sqlpmp);
227  if (! $resqlpmp) {
228  $error++;
229  setEventMessages($db->lasterror(), null, 'errors');
230  break;
231  }
232  if (!empty($conf->global->MAIN_PRODUCT_PERENTITY_SHARED)) {
233  $sqlpmp = 'UPDATE '.MAIN_DB_PREFIX.'product_perentity SET pmp = '.((float) $line->pmp_real).' WHERE fk_product = '.((int) $line->fk_product).' AND entity='.$conf->entity;
234  $resqlpmp = $db->query($sqlpmp);
235  if (! $resqlpmp) {
236  $error++;
237  setEventMessages($db->lasterror(), null, 'errors');
238  break;
239  }
240  }
241  }
242  }
243  $i++;
244  }
245 
246  if (!$error) {
247  $object->setRecorded($user);
248  }
249  } else {
250  setEventMessages($db->lasterror, null, 'errors');
251  $error++;
252  }
253 
254  if (! $error) {
255  $db->commit();
256  } else {
257  $db->rollback();
258  }
259  }
260 
261  // Save quantity found during inventory (when we click on Save button on inventory page)
262  if ($action =='updateinventorylines' && $permissiontoadd) {
263  $sql = 'SELECT id.rowid, id.datec as date_creation, id.tms as date_modification, id.fk_inventory, id.fk_warehouse,';
264  $sql .= ' id.fk_product, id.batch, id.qty_stock, id.qty_view, id.qty_regulated';
265  $sql .= ' FROM '.MAIN_DB_PREFIX.'inventorydet as id';
266  $sql .= ' WHERE id.fk_inventory = '.((int) $object->id);
267  $sql .= $db->order('id.rowid', 'ASC');
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.'&page='.$page.$paramwithsearch, $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&page='.$page.$paramwithsearch.'&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&page='.$page.$paramwithsearch.'&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"], '&id='.$object->id.$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 
1049  // For inventory not yet close, we overwrite with the real value in stock now
1050  if (($object->status == $object::STATUS_DRAFT || $object->status == $object::STATUS_VALIDATED) && !getDolGlobalString('DISABLE_QTY_OVERWRITE')) {
1051  if (isModEnabled('productbatch') && $product_static->hasbatch()) {
1052  $valuetoshow = $product_static->stock_warehouse[$obj->fk_warehouse]->detail_batch[$obj->batch]->qty;
1053  } else {
1054  $valuetoshow = $product_static->stock_warehouse[$obj->fk_warehouse]->real;
1055  }
1056  }
1057  print price2num($valuetoshow, 'MS');
1058  print '<input type="hidden" name="stock_qty_'.$obj->rowid.'" value="'.$valuetoshow.'">';
1059  print '</td>';
1060 
1061  // Real quantity
1062  if ($object->status == $object::STATUS_DRAFT || $object->status == $object::STATUS_VALIDATED) {
1063  $qty_view = GETPOST("id_".$obj->rowid) && price2num(GETPOST("id_".$obj->rowid), 'MS') >= 0 ? GETPOST("id_".$obj->rowid) : $obj->qty_view;
1064 
1065  //if (!$hasinput && $qty_view !== null && $obj->qty_stock != $qty_view) {
1066  if ($qty_view != '') {
1067  $hasinput = true;
1068  }
1069 
1070  if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
1071  //PMP Expected
1072  if (!empty($obj->pmp_expected)) $pmp_expected = $obj->pmp_expected;
1073  else $pmp_expected = $product_static->pmp;
1074  $pmp_valuation = $pmp_expected * $valuetoshow;
1075  print '<td class="right">';
1076  print price($pmp_expected);
1077  print '<input type="hidden" name="expectedpmp_'.$obj->rowid.'" value="'.$pmp_expected.'"/>';
1078  print '</td>';
1079  print '<td class="right">';
1080  print price($pmp_valuation);
1081  print '</td>';
1082 
1083  print '<td class="right">';
1084  print '<a id="undochangesqty_'.$obj->rowid.'" href="#" class="undochangesqty reposition marginrightonly" title="'.dol_escape_htmltag($langs->trans("Clear")).'">';
1085  print img_picto('', 'eraser', 'class="opacitymedium"');
1086  print '</a>';
1087  print '<input type="text" class="maxwidth50 right realqty" name="id_'.$obj->rowid.'" id="id_'.$obj->rowid.'_input" value="'.$qty_view.'">';
1088  print '</td>';
1089 
1090  //PMP Real
1091  print '<td class="right">';
1092 
1093 
1094  if (!empty($obj->pmp_real)) $pmp_real = $obj->pmp_real;
1095  else $pmp_real = $product_static->pmp;
1096  $pmp_valuation_real = $pmp_real * $qty_view;
1097  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).'">';
1098  print '</td>';
1099  print '<td class="right">';
1100  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.'">';
1101  print '</td>';
1102 
1103  $totalExpectedValuation += $pmp_valuation;
1104  $totalRealValuation += $pmp_valuation_real;
1105  } else {
1106  print '<td class="right">';
1107  print '<a id="undochangesqty_'.$obj->rowid.'" href="#" class="undochangesqty reposition marginrightonly" title="'.dol_escape_htmltag($langs->trans("Clear")).'">';
1108  print img_picto('', 'eraser', 'class="opacitymedium"');
1109  print '</a>';
1110  print '<input type="text" class="maxwidth50 right realqty" name="id_'.$obj->rowid.'" id="id_'.$obj->rowid.'_input" value="'.$qty_view.'">';
1111  print '</td>';
1112  }
1113 
1114  // Picto delete line
1115  print '<td class="right">';
1116  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>';
1117  $qty_tmp = price2num(GETPOST("id_".$obj->rowid."_input_tmp", 'MS')) >= 0 ? GETPOST("id_".$obj->rowid."_input_tmp") : $qty_view;
1118  print '<input type="hidden" class="maxwidth50 right realqty" name="id_'.$obj->rowid.'_input_tmp" id="id_'.$obj->rowid.'_input_tmp" value="'.$qty_tmp.'">';
1119  print '</td>';
1120  } else {
1121  if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
1122  //PMP Expected
1123  if (!empty($obj->pmp_expected)) $pmp_expected = $obj->pmp_expected;
1124  else $pmp_expected = $product_static->pmp;
1125  $pmp_valuation = $pmp_expected * $valuetoshow;
1126  print '<td class="right">';
1127  print price($pmp_expected);
1128  print '</td>';
1129  print '<td class="right">';
1130  print price($pmp_valuation);
1131  print '</td>';
1132 
1133  print '<td class="right nowraponall">';
1134  print $obj->qty_view; // qty found
1135  print '</td>';
1136 
1137  //PMP Real
1138  print '<td class="right">';
1139  if (!empty($obj->pmp_real)) $pmp_real = $obj->pmp_real;
1140  else $pmp_real = $product_static->pmp;
1141  $pmp_valuation_real = $pmp_real * $obj->qty_view;
1142  print price($pmp_real);
1143  print '</td>';
1144  print '<td class="right">';
1145  print price($pmp_valuation_real);
1146  print '</td>';
1147  print '<td class="nowraponall right">';
1148 
1149  $totalExpectedValuation += $pmp_valuation;
1150  $totalRealValuation += $pmp_valuation_real;
1151  } else {
1152  print '<td class="right nowraponall">';
1153  print $obj->qty_view; // qty found
1154  print '</td>';
1155  }
1156  print '<td>';
1157  if ($obj->fk_movement > 0) {
1158  $stockmovment = new MouvementStock($db);
1159  $stockmovment->fetch($obj->fk_movement);
1160  print $stockmovment->getNomUrl(1, 'movements');
1161  }
1162  print '</td>';
1163  }
1164  print '</tr>';
1165 
1166  $i++;
1167  }
1168 } else {
1169  dol_print_error($db);
1170 }
1171 if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
1172  print '<tr class="liste_total">';
1173  print '<td colspan="4">'.$langs->trans("Total").'</td>';
1174  print '<td class="right" colspan="2">'.price($totalExpectedValuation).'</td>';
1175  print '<td class="right" id="totalRealValuation" colspan="3">'.price($totalRealValuation).'</td>';
1176  print '<td></td>';
1177  print '</tr>';
1178 }
1179 print '</table>';
1180 
1181 print '</div>';
1182 
1183 if ($object->status == $object::STATUS_VALIDATED) {
1184  print '<center><input id="submitrecord" type="submit" class="button button-save" name="save" value="'.$langs->trans("Save").'"></center>';
1185 }
1186 
1187 print '</div>';
1188 
1189 
1190 // Call method to disable the button if no qty entered yet for inventory
1191 /*
1192 if ($object->status != $object::STATUS_VALIDATED || !$hasinput) {
1193  print '<script type="text/javascript">
1194  jQuery(document).ready(function() {
1195  console.log("Call disablebuttonmakemovementandclose because status = '.((int) $object->status).' or $hasinput = '.((int) $hasinput).'");
1196  disablebuttonmakemovementandclose();
1197  });
1198  </script>';
1199 }
1200 */
1201 
1202 print '</form>';
1203 
1204 print '<script type="text/javascript">
1205  $(document).ready(function() {
1206 
1207  $(".paginationnext:last").click(function(e){
1208  var form = $("#formrecord");
1209  var actionURL = "'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&page='.($page).$paramwithsearch.'";
1210  $.ajax({
1211  url: actionURL,
1212  data: form.serialize(),
1213  cache: false,
1214  success: function(result){
1215  window.location.href = "'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&page='.($page + 1).$paramwithsearch.'";
1216  }});
1217  });
1218 
1219 
1220  $(".paginationprevious:last").click(function(e){
1221  var form = $("#formrecord");
1222  var actionURL = "'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&page='.($page).$paramwithsearch.'";
1223  $.ajax({
1224  url: actionURL,
1225  data: form.serialize(),
1226  cache: false,
1227  success: function(result){
1228  window.location.href = "'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&page='.($page - 1).$paramwithsearch.'";
1229  }});
1230  });
1231 
1232  $("#idbuttonmakemovementandclose").click(function(e){
1233  var form = $("#formrecord");
1234  var actionURL = "'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&page='.($page).$paramwithsearch.'";
1235  $.ajax({
1236  url: actionURL,
1237  data: form.serialize(),
1238  cache: false,
1239  success: function(result){
1240  window.location.href = "'.$_SERVER['PHP_SELF'].'?id='.$object->id.'&page='.($page - 1).$paramwithsearch.'&action=record";
1241  }});
1242  });
1243  });
1244 </script>';
1245 
1246 
1247 if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
1248  ?>
1249 <script type="text/javascript">
1250 $('.realqty').on('change', function () {
1251  let realqty = $(this).closest('tr').find('.realqty').val();
1252  let inputPmp = $(this).closest('tr').find('input[class*=realpmp]');
1253  let realpmp = $(inputPmp).val();
1254  if (!isNaN(realqty) && !isNaN(realpmp)) {
1255  let realval = realqty * realpmp;
1256  $(this).closest('tr').find('input[name^=realvaluation]').val(realval.toFixed(2));
1257  }
1258  updateTotalValuation();
1259 });
1260 
1261 $('input[class*=realpmp]').on('change', function () {
1262  let inputQtyReal = $(this).closest('tr').find('.realqty');
1263  let realqty = $(inputQtyReal).val();
1264  let inputPmp = $(this).closest('tr').find('input[class*=realpmp]');
1265  console.log(inputPmp);
1266  let realPmpClassname = $(inputPmp).attr('class').match(/[\w-]*realpmp[\w-]*/g)[0];
1267  let realpmp = $(inputPmp).val();
1268  if (!isNaN(realpmp)) {
1269  $('.'+realPmpClassname).val(realpmp); //For batch case if pmp is changed we change it everywhere it's same product and calc back everything
1270 
1271  if (!isNaN(realqty)) {
1272  let realval = realqty * realpmp;
1273  $(this).closest('tr').find('input[name^=realvaluation]').val(realval.toFixed(2));
1274  }
1275  $('.realqty').trigger('change');
1276  updateTotalValuation();
1277  }
1278 });
1279 
1280 $('input[name^=realvaluation]').on('change', function () {
1281  let inputQtyReal = $(this).closest('tr').find('.realqty');
1282  let realqty = $(inputQtyReal).val();
1283  let inputPmp = $(this).closest('tr').find('input[class*=realpmp]');
1284  let inputRealValuation = $(this).closest('tr').find('input[name^=realvaluation]');
1285  let realPmpClassname = $(inputPmp).attr('class').match(/[\w-]*realpmp[\w-]*/g)[0];
1286  let realvaluation = $(inputRealValuation).val();
1287  if (!isNaN(realvaluation) && !isNaN(realqty) && realvaluation !== '' && realqty !== '' && realqty !== 0) {
1288  let realpmp = realvaluation / realqty
1289  $('.'+realPmpClassname).val(realpmp); //For batch case if pmp is changed we change it everywhere it's same product and calc back everything
1290  $('.realqty').trigger('change');
1291  updateTotalValuation();
1292  }
1293 });
1294 
1295 function updateTotalValuation() {
1296  let total = 0;
1297  $('input[name^=realvaluation]').each(function( index ) {
1298  let val = $(this).val();
1299  if(!isNaN(val)) total += parseFloat($(this).val());
1300  });
1301  let currencyFractionDigits = new Intl.NumberFormat('fr-FR', {
1302  style: 'currency',
1303  currency: 'EUR',
1304  }).resolvedOptions().maximumFractionDigits;
1305  $('#totalRealValuation').html(total.toLocaleString('fr-FR', {
1306  maximumFractionDigits: currencyFractionDigits
1307  }));
1308 }
1309 
1310 </script>
1311  <?php
1312 }
1313 
1314 // End of page
1315 llxFooter();
1316 $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.
getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
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:926
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.