dolibarr  17.0.4
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='.urlencode($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  } else {
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 /*
420  * View
421  */
422 
423 $form = new Form($db);
424 $formproduct = new FormProduct($db);
425 
426 $help_url = '';
427 
428 llxHeader('', $langs->trans('Inventory'), $help_url);
429 
430 // Part to show record
431 if ($object->id <= 0) {
432  dol_print_error('', 'Bad value for object id');
433  exit;
434 }
435 
436 
437 $res = $object->fetch_optionals();
438 
439 $head = inventoryPrepareHead($object);
440 print dol_get_fiche_head($head, 'inventory', $langs->trans("Inventory"), -1, 'stock');
441 
442 $formconfirm = '';
443 
444 // Confirmation to delete
445 if ($action == 'delete') {
446  $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('DeleteInventory'), $langs->trans('ConfirmDeleteOrder'), 'confirm_delete', '', 0, 1);
447 }
448 // Confirmation to delete line
449 if ($action == 'deleteline') {
450  $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id.'&lineid='.$lineid.'&page='.$page.$paramwithsearch, $langs->trans('DeleteLine'), $langs->trans('ConfirmDeleteLine'), 'confirm_deleteline', '', 0, 1);
451 }
452 
453 // Clone confirmation
454 if ($action == 'clone') {
455  // Create an array for form
456  $formquestion = array();
457  $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('ToClone'), $langs->trans('ConfirmCloneMyObject', $object->ref), 'confirm_clone', $formquestion, 'yes', 1);
458 }
459 
460 // Confirmation to close
461 if ($action == 'record') {
462  $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('Close'), $langs->trans('ConfirmFinish'), 'update', '', 0, 1);
463  $action = 'view';
464 }
465 
466 // Confirmation to close
467 if ($action == 'confirm_cancel') {
468  $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('Cancel'), $langs->trans('ConfirmCancel'), 'cancel_record', '', 0, 1);
469  $action = 'view';
470 }
471 
472 // Call Hook formConfirm
473 $parameters = array('formConfirm' => $formconfirm, 'lineid' => $lineid);
474 $reshook = $hookmanager->executeHooks('formConfirm', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
475 if (empty($reshook)) {
476  $formconfirm .= $hookmanager->resPrint;
477 } elseif ($reshook > 0) {
478  $formconfirm = $hookmanager->resPrint;
479 }
480 
481 // Print form confirm
482 print $formconfirm;
483 
484 
485 // Object card
486 // ------------------------------------------------------------
487 $linkback = '<a href="'.DOL_URL_ROOT.'/product/inventory/list.php">'.$langs->trans("BackToList").'</a>';
488 
489 $morehtmlref = '<div class="refidno">';
490 /*
491 // Ref bis
492 $morehtmlref.=$form->editfieldkey("RefBis", 'ref_client', $object->ref_client, $object, $user->rights->inventory->creer, 'string', '', 0, 1);
493 $morehtmlref.=$form->editfieldval("RefBis", 'ref_client', $object->ref_client, $object, $user->rights->inventory->creer, 'string', '', null, null, '', 1);
494 // Thirdparty
495 $morehtmlref.='<br>'.$langs->trans('ThirdParty') . ' : ' . $soc->getNomUrl(1);
496 // Project
497 if (!empty($conf->project->enabled))
498 {
499  $langs->load("projects");
500  $morehtmlref.='<br>'.$langs->trans('Project') . ' ';
501  if ($user->rights->inventory->creer)
502  {
503  if ($action != 'classify')
504  {
505  $morehtmlref.='<a class="editfielda" href="' . $_SERVER['PHP_SELF'] . '?action=classify&token='.newToken().'&id=' . $object->id . '">' . img_edit($langs->transnoentitiesnoconv('SetProject')) . '</a> : ';
506  if ($action == 'classify') {
507  //$morehtmlref.=$form->form_project($_SERVER['PHP_SELF'] . '?id=' . $object->id, $object->socid, $object->fk_project, 'projectid', 0, 0, 1, 1);
508  $morehtmlref.='<form method="post" action="'.$_SERVER['PHP_SELF'].'?id='.$object->id.'">';
509  $morehtmlref.='<input type="hidden" name="action" value="classin">';
510  $morehtmlref.='<input type="hidden" name="token" value="'.newToken().'">';
511  $morehtmlref.=$formproject->select_projects($object->socid, $object->fk_project, 'projectid', $maxlength, 0, 1, 0, 1, 0, 0, '', 1);
512  $morehtmlref.='<input type="submit" class="button valignmiddle" value="'.$langs->trans("Modify").'">';
513  $morehtmlref.='</form>';
514  } else {
515  $morehtmlref.=$form->form_project($_SERVER['PHP_SELF'] . '?id=' . $object->id, $object->socid, $object->fk_project, 'none', 0, 0, 0, 1);
516  }
517  }
518  } else {
519  if (!empty($object->fk_project)) {
520  $proj = new Project($db);
521  $proj->fetch($object->fk_project);
522  $morehtmlref.=$proj->getNomUrl();
523  } else {
524  $morehtmlref.='';
525  }
526  }
527 }
528 */
529 $morehtmlref .= '</div>';
530 
531 
532 dol_banner_tab($object, 'ref', $linkback, 1, 'ref', 'ref', $morehtmlref);
533 
534 
535 print '<div class="fichecenter">';
536 print '<div class="fichehalfleft">';
537 print '<div class="underbanner clearboth"></div>';
538 print '<table class="border centpercent tableforfield">'."\n";
539 
540 // Common attributes
541 include DOL_DOCUMENT_ROOT.'/core/tpl/commonfields_view.tpl.php';
542 
543 // Other attributes. Fields from hook formObjectOptions and Extrafields.
544 include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_view.tpl.php';
545 
546 //print '<tr><td class="titlefield fieldname_invcode">'.$langs->trans("InventoryCode").'</td><td>INV'.$object->id.'</td></tr>';
547 
548 print '</table>';
549 print '</div>';
550 print '</div>';
551 
552 print '<div class="clearboth"></div>';
553 
554 print dol_get_fiche_end();
555 
556 print '<form id="formrecord" name="formrecord" method="POST" action="'.$_SERVER["PHP_SELF"].'?page='.$page.'&id='.$object->id.'">';
557 print '<input type="hidden" name="token" value="'.newToken().'">';
558 print '<input type="hidden" name="action" value="updateinventorylines">';
559 print '<input type="hidden" name="id" value="'.$object->id.'">';
560 if ($backtopage) {
561  print '<input type="hidden" name="backtopage" value="'.$backtopage.'">';
562 }
563 
564 
565 // Buttons for actions
566 if ($action != 'record') {
567  print '<div class="tabsAction">'."\n";
568  $parameters = array();
569  $reshook = $hookmanager->executeHooks('addMoreActionsButtons', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
570  if ($reshook < 0) {
571  setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
572  }
573 
574  if (empty($reshook)) {
575  if ($object->status == Inventory::STATUS_DRAFT) {
576  if ($permissiontoadd) {
577  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";
578  } else {
579  print '<a class="butActionRefused classfortooltip" href="#" title="'.dol_escape_htmltag($langs->trans("NotEnoughPermissions")).'">'.$langs->trans('Validate').' ('.$langs->trans("Start").')</a>'."\n";
580  }
581  }
582 
583  // Save
584  if ($object->status == $object::STATUS_VALIDATED) {
585  if ($permissiontoadd) {
586  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";
587  } else {
588  print '<a class="butActionRefused classfortooltip" href="#" title="'.dol_escape_htmltag($langs->trans("NotEnoughPermissions")).'">'.$langs->trans('MakeMovementsAndClose').'</a>'."\n";
589  }
590 
591  if ($permissiontoadd) {
592  print '<a class="butActionDelete" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=confirm_cancel&token='.newToken().'">'.$langs->trans("Cancel").'</a>'."\n";
593  }
594  }
595  }
596  print '</div>'."\n";
597 
598  if ($object->status != Inventory::STATUS_DRAFT && $object->status != Inventory::STATUS_VALIDATED) {
599  print '<br><br>';
600  }
601 }
602 
603 
604 
605 if ($object->status == Inventory::STATUS_VALIDATED) {
606  print '<center>';
607  if (!empty($conf->use_javascript_ajax)) {
608  if ($permissiontoadd) {
609  // Link to launch scan tool
610  if (isModEnabled('barcode') || isModEnabled('productbatch')) {
611  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>';
612  }
613 
614  // Link to autofill
615  print '<a id="fillwithexpected" class="marginrightonly paddingright marginleftonly paddingleft" href="#">'.img_picto('', 'autofill', 'class="paddingrightonly"').$langs->trans('AutofillWithExpected').'</a>';
616  print '<script>';
617  print '$( document ).ready(function() {';
618  print ' $("#fillwithexpected").on("click",function fillWithExpected(){
619  $(".expectedqty").each(function(){
620  var object = $(this)[0];
621  var objecttofill = $("#"+object.id+"_input")[0];
622  objecttofill.value = object.innerText;
623  jQuery(".realqty").trigger("change");
624  })
625  console.log("Values filled (after click on fillwithexpected)");
626  /* disablebuttonmakemovementandclose(); */
627  return false;
628  });';
629  print '});';
630  print '</script>';
631 
632  // Link to reset qty
633  print '<a href="#" id="clearqty" class="marginrightonly paddingright marginleftonly paddingleft">'.img_picto('', 'eraser', 'class="paddingrightonly"').$langs->trans("ClearQtys").'</a>';
634  } else {
635  print '<a class="classfortooltip marginrightonly paddingright marginleftonly paddingleft" href="#" title="'.dol_escape_htmltag($langs->trans("NotEnoughPermissions")).'">'.$langs->trans("Save").'</a>'."\n";
636  }
637  }
638  print '<br>';
639  print '<br>';
640  print '</center>';
641 }
642 
643 
644 // Popup for mass barcode scanning
645 if ($action == 'updatebyscaning') {
646  if ($permissiontoadd) {
647  // Output the javascript to manage the scanner tool.
648  print '<script>';
649 
650  print '
651  var duplicatedbatchcode = [];
652  var errortab1 = [];
653  var errortab2 = [];
654  var errortab3 = [];
655  var errortab4 = [];
656 
657  function barcodescannerjs(){
658  console.log("We catch inputs in scanner box");
659  jQuery("#scantoolmessage").text();
660 
661  var selectaddorreplace = $("select[name=selectaddorreplace]").val();
662  var barcodemode = $("input[name=barcodemode]:checked").val();
663  var barcodeproductqty = $("input[name=barcodeproductqty]").val();
664  var textarea = $("textarea[name=barcodelist]").val();
665  var textarray = textarea.split(/[\s,;]+/);
666  var tabproduct = [];
667  duplicatedbatchcode = [];
668  errortab1 = [];
669  errortab2 = [];
670  errortab3 = [];
671  errortab4 = [];
672 
673  textarray = textarray.filter(function(value){
674  return value != "";
675  });
676  if(textarray.some((element) => element != "")){
677  $(".expectedqty").each(function(){
678  id = this.id;
679  console.log("Analyze the line "+id+" in inventory, barcodemode="+barcodemode);
680  warehouse = $("#"+id+"_warehouse").attr(\'data-ref\');
681  //console.log(warehouse);
682  productbarcode = $("#"+id+"_product").attr(\'data-barcode\');
683  //console.log(productbarcode);
684  productbatchcode = $("#"+id+"_batch").attr(\'data-batch\');
685  //console.log(productbatchcode);
686 
687  if (barcodemode != "barcodeforproduct") {
688  tabproduct.forEach(product=>{
689  console.log("product.Batch="+product.Batch+" productbatchcode="+productbatchcode);
690  if(product.Batch != "" && product.Batch == productbatchcode){
691  console.log("duplicate batch code found for batch code "+productbatchcode);
692  duplicatedbatchcode.push(productbatchcode);
693  }
694  })
695  }
696  productinput = $("#"+id+"_input").val();
697  if(productinput == ""){
698  productinput = 0
699  }
700  tabproduct.push({\'Id\':id,\'Warehouse\':warehouse,\'Barcode\':productbarcode,\'Batch\':productbatchcode,\'Qty\':productinput,\'fetched\':false});
701  });
702 
703  console.log("Loop on each record entered in the textarea");
704  textarray.forEach(function(element,index){
705  console.log("Process record element="+element+" id="+id);
706  var verify_batch = false;
707  var verify_barcode = false;
708  switch(barcodemode){
709  case "barcodeforautodetect":
710  verify_barcode = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"barcode",true);
711  verify_batch = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"lotserial",true);
712  break;
713  case "barcodeforproduct":
714  verify_barcode = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"barcode");
715  break;
716  case "barcodeforlotserial":
717  verify_batch = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"lotserial");
718  break;
719  default:
720  alert(\''.dol_escape_js($langs->trans("ErrorWrongBarcodemode")).' "\'+barcodemode+\'"\');
721  throw \''.dol_escape_js($langs->trans('ErrorWrongBarcodemode')).' "\'+barcodemode+\'"\';
722  }
723 
724  if (verify_batch == false && verify_barcode == false) { /* If the 2 flags are false, not found error */
725  errortab2.push(element);
726  } else if (verify_batch == true && verify_barcode == true) { /* If the 2 flags are true, error: we don t know which one to take */
727  errortab3.push(element);
728  } else if (verify_batch == true) {
729  console.log("element="+element);
730  console.log(duplicatedbatchcode);
731  if (duplicatedbatchcode.includes(element)) {
732  errortab1.push(element);
733  }
734  }
735  });
736 
737  if (Object.keys(errortab1).length < 1 && Object.keys(errortab2).length < 1 && Object.keys(errortab3).length < 1) {
738  tabproduct.forEach(product => {
739  if(product.Qty!=0){
740  console.log("We change #"+product.Id+"_input to match input in scanner box");
741  if(product.hasOwnProperty("reelqty")){
742  $.ajax({ url: \''.DOL_URL_ROOT.'/product/inventory/ajax/searchfrombarcode.php\',
743  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},
744  type: \'POST\',
745  async: false,
746  success: function(response) {
747  response = JSON.parse(response);
748  if(response.status == "success"){
749  console.log(response.message);
750  $("<input type=\'text\' value=\'"+product.Qty+"\' />")
751  .attr("id", "id_"+response.id_line+"_input")
752  .attr("name", "id_"+response.id_line)
753  .appendTo("#formrecord");
754  }else{
755  console.error(response.message);
756  }
757  },
758  error : function(output) {
759  console.error("Error on line creation function");
760  },
761  });
762  } else {
763  $("#"+product.Id+"_input").val(product.Qty);
764  }
765  }
766  });
767  jQuery("#scantoolmessage").text("'.dol_escape_js($langs->transnoentities("QtyWasAddedToTheScannedBarcode")).'\n");
768  /* document.forms["formrecord"].submit(); */
769  } else {
770  let stringerror = "";
771  if (Object.keys(errortab1).length > 0) {
772  stringerror += "<br>'.dol_escape_js($langs->transnoentities('ErrorSameBatchNumber')).': ";
773  errortab1.forEach(element => {
774  stringerror += (element + ", ")
775  });
776  stringerror = stringerror.slice(0, -2); /* Remove last ", " */
777  }
778  if (Object.keys(errortab2).length > 0) {
779  stringerror += "<br>'.dol_escape_js($langs->transnoentities('ErrorCantFindCodeInInventory')).': ";
780  errortab2.forEach(element => {
781  stringerror += (element + ", ")
782  });
783  stringerror = stringerror.slice(0, -2); /* Remove last ", " */
784  }
785  if (Object.keys(errortab3).length > 0) {
786  stringerror += "<br>'.dol_escape_js($langs->transnoentities('ErrorCodeScannedIsBothProductAndSerial')).': ";
787  errortab3.forEach(element => {
788  stringerror += (element + ", ")
789  });
790  stringerror = stringerror.slice(0, -2); /* Remove last ", " */
791  }
792  if (Object.keys(errortab4).length > 0) {
793  stringerror += "<br>'.dol_escape_js($langs->transnoentities('ErrorBarcodeNotFoundForProductWarehouse')).': ";
794  errortab4.forEach(element => {
795  stringerror += (element + ", ")
796  });
797  stringerror = stringerror.slice(0, -2); /* Remove last ", " */
798  }
799 
800  jQuery("#scantoolmessage").html(\''.dol_escape_js($langs->transnoentities("ErrorOnElementsInventory")).'\' + stringerror);
801  //alert("'.dol_escape_js($langs->trans("ErrorOnElementsInventory")).' :\n" + stringerror);
802  }
803  }
804 
805  }
806 
807  /* This methode is called by parent barcodescannerjs() */
808  function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,mode,autodetect=false){
809  BarcodeIsInProduct=0;
810  newproductrow=0
811  result=false;
812  tabproduct.forEach(product => {
813  $.ajax({ url: \''.DOL_URL_ROOT.'/product/inventory/ajax/searchfrombarcode.php\',
814  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},
815  type: \'POST\',
816  async: false,
817  success: function(response) {
818  response = JSON.parse(response);
819  if (response.status == "success"){
820  console.log(response.message);
821  if(!newproductrow){
822  newproductrow = response.object;
823  }
824  }else{
825  if (mode!="lotserial" && autodetect==false && !errortab4.includes(element)){
826  errortab4.push(element);
827  console.error(response.message);
828  }
829  }
830  },
831  error : function(output) {
832  console.error("Error on barcodeserialforproduct function");
833  },
834  });
835  console.log("Product "+(index+=1)+": "+element);
836  if(mode == "barcode"){
837  testonproduct = product.Barcode
838  }else if (mode == "lotserial"){
839  testonproduct = product.Batch
840  }
841  if(testonproduct == element){
842  if(selectaddorreplace == "add"){
843  productqty = parseInt(product.Qty,10);
844  product.Qty = productqty + parseInt(barcodeproductqty,10);
845  }else if(selectaddorreplace == "replace"){
846  if(product.fetched == false){
847  product.Qty = barcodeproductqty
848  product.fetched=true
849  }else{
850  productqty = parseInt(product.Qty,10);
851  product.Qty = productqty + parseInt(barcodeproductqty,10);
852  }
853  }
854  BarcodeIsInProduct+=1;
855  }
856  })
857  if(BarcodeIsInProduct==0 && newproductrow!=0){
858  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});
859  result = true;
860  }
861  if(BarcodeIsInProduct > 0){
862  result = true;
863  }
864  return result;
865  }
866  ';
867  print '</script>';
868  }
869  include DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php';
870  $formother = new FormOther($db);
871  print $formother->getHTMLScannerForm("barcodescannerjs", 'all');
872 }
873 
874 //Call method to undo changes in real qty
875 print '<script>';
876 print 'jQuery(document).ready(function() {
877  $("#clearqty").on("click", function() {
878  console.log("Clear all values");
879  /* disablebuttonmakemovementandclose(); */
880  jQuery(".realqty").val("");
881  jQuery(".realqty").trigger("change");
882  return false; /* disable submit */
883  });
884  $(".undochangesqty").on("click", function undochangesqty() {
885  console.log("Clear value of inventory line");
886  id = this.id;
887  id = id.split("_")[1];
888  tmpvalue = $("#id_"+id+"_input_tmp").val()
889  $("#id_"+id+"_input")[0].value = tmpvalue;
890  /* disablebuttonmakemovementandclose(); */
891  return false; /* disable submit */
892  });
893 });';
894 print '</script>';
895 
896 print '<div class="fichecenter">';
897 //print '<div class="fichehalfleft">';
898 print '<div class="clearboth"></div>';
899 
900 //print load_fiche_titre($langs->trans('Consumption'), '', '');
901 
902 print '<div class="div-table-responsive-no-min">';
903 print '<table id="tablelines" class="noborder noshadow centpercent">';
904 
905 print '<tr class="liste_titre">';
906 print '<td>'.$langs->trans("Warehouse").'</td>';
907 print '<td>'.$langs->trans("Product").'</td>';
908 if (isModEnabled('productbatch')) {
909  print '<td>';
910  print $langs->trans("Batch");
911  print '</td>';
912 }
913 print '<td class="right">'.$langs->trans("ExpectedQty").'</td>';
914 if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
915  print '<td class="right">'.$langs->trans('PMPExpected').'</td>';
916  print '<td class="right">'.$langs->trans('ExpectedValuation').'</td>';
917  print '<td class="right">'.$form->textwithpicto($langs->trans("RealQty"), $langs->trans("InventoryRealQtyHelp")).'</td>';
918  print '<td class="right">'.$langs->trans('PMPReal').'</td>';
919  print '<td class="right">'.$langs->trans('RealValuation').'</td>';
920 } else {
921  print '<td class="right">';
922  print $form->textwithpicto($langs->trans("RealQty"), $langs->trans("InventoryRealQtyHelp"));
923  print '</td>';
924 }
925 if ($object->status == $object::STATUS_DRAFT || $object->status == $object::STATUS_VALIDATED) {
926  // Actions or link to stock movement
927  print '<td class="center">';
928  print '</td>';
929 } else {
930  // Actions or link to stock movement
931  print '<td class="right">';
932  //print $langs->trans("StockMovement");
933  print '</td>';
934 }
935 print '</tr>';
936 
937 // Line to add a new line in inventory
938 if ($object->status == $object::STATUS_DRAFT || $object->status == $object::STATUS_VALIDATED) {
939  print '<tr>';
940  print '<td>';
941  print $formproduct->selectWarehouses((GETPOSTISSET('fk_warehouse') ? GETPOST('fk_warehouse', 'int') : $object->fk_warehouse), 'fk_warehouse', 'warehouseopen', 1, 0, 0, '', 0, 0, array(), 'maxwidth300');
942  print '</td>';
943  print '<td>';
944  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');
945  print '</td>';
946  if (isModEnabled('productbatch')) {
947  print '<td>';
948  print '<input type="text" name="batch" class="maxwidth100" value="'.(GETPOSTISSET('batch') ? GETPOST('batch') : '').'">';
949  print '</td>';
950  }
951  print '<td class="right"></td>';
952  if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
953  print '<td class="right">';
954  print '</td>';
955  print '<td class="right">';
956  print '</td>';
957  print '<td class="right">';
958  print '<input type="text" name="qtytoadd" class="maxwidth75" value="">';
959  print '</td>';
960  print '<td class="right">';
961  print '</td>';
962  print '<td class="right">';
963  print '</td>';
964  } else {
965  print '<td class="right">';
966  print '<input type="text" name="qtytoadd" class="maxwidth75" value="">';
967  print '</td>';
968  }
969  // Actions
970  print '<td class="center">';
971  print '<input type="submit" class="button paddingright" name="addline" value="'.$langs->trans("Add").'">';
972  print '</td>';
973  print '</tr>';
974 }
975 
976 // Request to show lines of inventory (prefilled after start/validate step)
977 $sql = 'SELECT id.rowid, id.datec as date_creation, id.tms as date_modification, id.fk_inventory, id.fk_warehouse,';
978 $sql .= ' id.fk_product, id.batch, id.qty_stock, id.qty_view, id.qty_regulated, id.fk_movement, id.pmp_real, id.pmp_expected';
979 $sql .= ' FROM '.MAIN_DB_PREFIX.'inventorydet as id';
980 $sql .= ' WHERE id.fk_inventory = '.((int) $object->id);
981 $sql .= $db->order('id.rowid', 'ASC');
982 $sql .= $db->plimit($limit, $offset);
983 
984 $cacheOfProducts = array();
985 $cacheOfWarehouses = array();
986 
987 //$sql = '';
988 $resql = $db->query($sql);
989 if ($resql) {
990  $num = $db->num_rows($resql);
991 
992  if (!empty($limit != 0) || $num > $limit || $page) {
993  print_fleche_navigation($page, $_SERVER["PHP_SELF"], $paramwithsearch, ($num >= $limit), '<li class="pagination"><span>' . $langs->trans("Page") . ' ' . ($page + 1) . '</span></li>', '', $limit);
994  }
995 
996  $i = 0;
997  $hasinput = false;
998  $totalarray = array();
999  while ($i < $num) {
1000  $obj = $db->fetch_object($resql);
1001 
1002  if (isset($cacheOfWarehouses[$obj->fk_warehouse])) {
1003  $warehouse_static = $cacheOfWarehouses[$obj->fk_warehouse];
1004  } else {
1005  $warehouse_static = new Entrepot($db);
1006  $warehouse_static->fetch($obj->fk_warehouse);
1007 
1008  $cacheOfWarehouses[$warehouse_static->id] = $warehouse_static;
1009  }
1010 
1011  // Load real stock we have now
1012  $option = '';
1013  if (isset($cacheOfProducts[$obj->fk_product])) {
1014  $product_static = $cacheOfProducts[$obj->fk_product];
1015  } else {
1016  $product_static = new Product($db);
1017  $result = $product_static->fetch($obj->fk_product, '', '', '', 1, 1, 1);
1018 
1019  //$option = 'nobatch';
1020  $option .= ',novirtual';
1021  $product_static->load_stock($option); // Load stock_reel + stock_warehouse.
1022 
1023  $cacheOfProducts[$product_static->id] = $product_static;
1024  }
1025 
1026  print '<tr class="oddeven">';
1027  print '<td id="id_'.$obj->rowid.'_warehouse" data-ref="'.dol_escape_htmltag($warehouse_static->ref).'">';
1028  print $warehouse_static->getNomUrl(1);
1029  print '</td>';
1030  print '<td id="id_'.$obj->rowid.'_product" data-ref="'.dol_escape_htmltag($product_static->ref).'" data-barcode="'.dol_escape_htmltag($product_static->barcode).'">';
1031  print $product_static->getNomUrl(1).' - '.$product_static->label;
1032  print '</td>';
1033 
1034  if (isModEnabled('productbatch')) {
1035  print '<td id="id_'.$obj->rowid.'_batch" data-batch="'.dol_escape_htmltag($obj->batch).'">';
1036  $batch_static = new Productlot($db);
1037  $res = $batch_static->fetch(0, $product_static->id, $obj->batch);
1038  if ($res) {
1039  print $batch_static->getNomUrl(1);
1040  } else {
1041  print dol_escape_htmltag($obj->batch);
1042  }
1043  print '</td>';
1044  }
1045 
1046  // Expected quantity = Quantity in stock when we start inventory
1047  print '<td class="right expectedqty" id="id_'.$obj->rowid.'" title="Stock viewed at last update: '.$obj->qty_stock.'">';
1048  $valuetoshow = $obj->qty_stock;
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) {
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']."?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']."?page=".($page + 1).$paramwithsearch.'";
1216  }});
1217  });
1218 
1219 
1220  $(".paginationprevious:last").click(function(e){
1221  var form = $("#formrecord");
1222  var actionURL = "'.$_SERVER['PHP_SELF']."?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']."?page=".($page - 1).$paramwithsearch.'";
1229  }});
1230  });
1231 
1232  $("#idbuttonmakemovementandclose").click(function(e){
1233  var form = $("#formrecord");
1234  var actionURL = "'.$_SERVER['PHP_SELF']."?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']."?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') &&!empty($user->rights->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') &&!empty($user->rights->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)) $resql
Social contributions to pay.
Definition: index.php:745
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.
dol_get_fiche_head($links=array(), $active='', $title='', $notab=0, $picto='', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limittoshow=0, $moretabssuffix='')
Show tabs of a record.
img_delete($titlealt='default', $other='class="pictodelete"', $morecss='')
Show delete logo.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0)
Returns text escaped for inclusion in HTML alt or title tags, or into values of HTML input fields.
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.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='')
Set event messages in dol_events session object.
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'.
print_fleche_navigation($page, $file, $options='', $nextpage=0, $betweenarrows='', $afterarrows='', $limit=-1, $totalnboflines=0, $hideselectlimit=0, $beforearrows='')
Function to show navigation arrows into lists.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
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.
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:913
if(preg_match('/crypted:/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
Definition: repair.php:119
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.