dolibarr  x.y.z
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)) {
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  $res = $object->fetch_optionals();
433 
434  $head = inventoryPrepareHead($object);
435  print dol_get_fiche_head($head, 'inventory', $langs->trans("Inventory"), -1, 'stock');
436 
437  $formconfirm = '';
438 
439  // Confirmation to delete
440  if ($action == 'delete') {
441  $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('DeleteInventory'), $langs->trans('ConfirmDeleteOrder'), 'confirm_delete', '', 0, 1);
442  }
443  // Confirmation to delete line
444  if ($action == 'deleteline') {
445  $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id.'&lineid='.$lineid.'&page='.$page.$paramwithsearch, $langs->trans('DeleteLine'), $langs->trans('ConfirmDeleteLine'), 'confirm_deleteline', '', 0, 1);
446  }
447 
448  // Clone confirmation
449  if ($action == 'clone') {
450  // Create an array for form
451  $formquestion = array();
452  $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('ToClone'), $langs->trans('ConfirmCloneMyObject', $object->ref), 'confirm_clone', $formquestion, 'yes', 1);
453  }
454 
455  // Confirmation to close
456  if ($action == 'record') {
457  $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('Close'), $langs->trans('ConfirmFinish'), 'update', '', 0, 1);
458  $action = 'view';
459  }
460 
461  // Confirmation to close
462  if ($action == 'confirm_cancel') {
463  $formconfirm = $form->formconfirm($_SERVER["PHP_SELF"].'?id='.$object->id, $langs->trans('Cancel'), $langs->trans('ConfirmCancel'), 'cancel_record', '', 0, 1);
464  $action = 'view';
465  }
466 
467  // Call Hook formConfirm
468  $parameters = array('formConfirm' => $formconfirm, 'lineid' => $lineid);
469  $reshook = $hookmanager->executeHooks('formConfirm', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
470  if (empty($reshook)) {
471  $formconfirm .= $hookmanager->resPrint;
472  } elseif ($reshook > 0) {
473  $formconfirm = $hookmanager->resPrint;
474  }
475 
476  // Print form confirm
477  print $formconfirm;
478 
479 
480  // Object card
481  // ------------------------------------------------------------
482  $linkback = '<a href="'.DOL_URL_ROOT.'/product/inventory/list.php">'.$langs->trans("BackToList").'</a>';
483 
484  $morehtmlref = '<div class="refidno">';
485  /*
486  // Ref bis
487  $morehtmlref.=$form->editfieldkey("RefBis", 'ref_client', $object->ref_client, $object, $user->rights->inventory->creer, 'string', '', 0, 1);
488  $morehtmlref.=$form->editfieldval("RefBis", 'ref_client', $object->ref_client, $object, $user->rights->inventory->creer, 'string', '', null, null, '', 1);
489  // Thirdparty
490  $morehtmlref.='<br>'.$langs->trans('ThirdParty') . ' : ' . $soc->getNomUrl(1);
491  // Project
492  if (!empty($conf->project->enabled))
493  {
494  $langs->load("projects");
495  $morehtmlref.='<br>'.$langs->trans('Project') . ' ';
496  if ($user->rights->inventory->creer)
497  {
498  if ($action != 'classify')
499  {
500  $morehtmlref.='<a class="editfielda" href="' . $_SERVER['PHP_SELF'] . '?action=classify&token='.newToken().'&id=' . $object->id . '">' . img_edit($langs->transnoentitiesnoconv('SetProject')) . '</a> : ';
501  if ($action == 'classify') {
502  //$morehtmlref.=$form->form_project($_SERVER['PHP_SELF'] . '?id=' . $object->id, $object->socid, $object->fk_project, 'projectid', 0, 0, 1, 1);
503  $morehtmlref.='<form method="post" action="'.$_SERVER['PHP_SELF'].'?id='.$object->id.'">';
504  $morehtmlref.='<input type="hidden" name="action" value="classin">';
505  $morehtmlref.='<input type="hidden" name="token" value="'.newToken().'">';
506  $morehtmlref.=$formproject->select_projects($object->socid, $object->fk_project, 'projectid', $maxlength, 0, 1, 0, 1, 0, 0, '', 1);
507  $morehtmlref.='<input type="submit" class="button valignmiddle" value="'.$langs->trans("Modify").'">';
508  $morehtmlref.='</form>';
509  } else {
510  $morehtmlref.=$form->form_project($_SERVER['PHP_SELF'] . '?id=' . $object->id, $object->socid, $object->fk_project, 'none', 0, 0, 0, 1);
511  }
512  }
513  } else {
514  if (!empty($object->fk_project)) {
515  $proj = new Project($db);
516  $proj->fetch($object->fk_project);
517  $morehtmlref.=$proj->getNomUrl();
518  } else {
519  $morehtmlref.='';
520  }
521  }
522  }
523  */
524  $morehtmlref .= '</div>';
525 
526 
527  dol_banner_tab($object, 'ref', $linkback, 1, 'ref', 'ref', $morehtmlref);
528 
529 
530  print '<div class="fichecenter">';
531  print '<div class="fichehalfleft">';
532  print '<div class="underbanner clearboth"></div>';
533  print '<table class="border centpercent tableforfield">'."\n";
534 
535  // Common attributes
536  include DOL_DOCUMENT_ROOT.'/core/tpl/commonfields_view.tpl.php';
537 
538  // Other attributes. Fields from hook formObjectOptions and Extrafields.
539  include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_view.tpl.php';
540 
541  //print '<tr><td class="titlefield fieldname_invcode">'.$langs->trans("InventoryCode").'</td><td>INV'.$object->id.'</td></tr>';
542 
543  print '</table>';
544  print '</div>';
545  print '</div>';
546 
547  print '<div class="clearboth"></div>';
548 
549  print dol_get_fiche_end();
550 
551  print '<form id="formrecord" name="formrecord" method="POST" action="'.$_SERVER["PHP_SELF"].'?page='.$page.'&id='.$object->id.'">';
552  print '<input type="hidden" name="token" value="'.newToken().'">';
553  print '<input type="hidden" name="action" value="updateinventorylines">';
554  print '<input type="hidden" name="id" value="'.$object->id.'">';
555  if ($backtopage) {
556  print '<input type="hidden" name="backtopage" value="'.$backtopage.'">';
557  }
558 
559 
560  // Buttons for actions
561  if ($action != 'record') {
562  print '<div class="tabsAction">'."\n";
563  $parameters = array();
564  $reshook = $hookmanager->executeHooks('addMoreActionsButtons', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
565  if ($reshook < 0) {
566  setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
567  }
568 
569  if (empty($reshook)) {
570  if ($object->status == Inventory::STATUS_DRAFT) {
571  if ($permissiontoadd) {
572  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";
573  } else {
574  print '<a class="butActionRefused classfortooltip" href="#" title="'.dol_escape_htmltag($langs->trans("NotEnoughPermissions")).'">'.$langs->trans('Validate').' ('.$langs->trans("Start").')</a>'."\n";
575  }
576  }
577 
578  // Save
579  if ($object->status == $object::STATUS_VALIDATED) {
580  if ($permissiontoadd) {
581  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";
582  } else {
583  print '<a class="butActionRefused classfortooltip" href="#" title="'.dol_escape_htmltag($langs->trans("NotEnoughPermissions")).'">'.$langs->trans('MakeMovementsAndClose').'</a>'."\n";
584  }
585 
586  if ($permissiontoadd) {
587  print '<a class="butActionDelete" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=confirm_cancel&token='.newToken().'">'.$langs->trans("Cancel").'</a>'."\n";
588  }
589  }
590  }
591  print '</div>'."\n";
592 
593  if ($object->status != Inventory::STATUS_DRAFT && $object->status != Inventory::STATUS_VALIDATED) {
594  print '<br><br>';
595  }
596  }
597 
598 
599 
600  if ($object->status == Inventory::STATUS_VALIDATED) {
601  print '<center>';
602  if (!empty($conf->use_javascript_ajax)) {
603  if ($permissiontoadd) {
604  // Link to launch scan tool
605  if (isModEnabled('barcode') || isModEnabled('productbatch')) {
606  print '<a href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=updatebyscaning" class="marginrightonly paddingright marginleftonly paddingleft">'.img_picto('', 'barcode', 'class="paddingrightonly"').$langs->trans("UpdateByScaning").'</a>';
607  }
608 
609  // Link to autofill
610  print '<a id="fillwithexpected" class="marginrightonly paddingright marginleftonly paddingleft" href="#">'.img_picto('', 'autofill', 'class="paddingrightonly"').$langs->trans('AutofillWithExpected').'</a>';
611  print '<script>';
612  print '$( document ).ready(function() {';
613  print ' $("#fillwithexpected").on("click",function fillWithExpected(){
614  $(".expectedqty").each(function(){
615  var object = $(this)[0];
616  var objecttofill = $("#"+object.id+"_input")[0];
617  objecttofill.value = object.innerText;
618  jQuery(".realqty").trigger("change");
619  })
620  console.log("Values filled (after click on fillwithexpected)");
621  disablebuttonmakemovementandclose();
622  return false;
623  });';
624  print '});';
625  print '</script>';
626 
627  // Link to reset qty
628  print '<a href="#" id="clearqty" class="marginrightonly paddingright marginleftonly paddingleft">'.img_picto('', 'eraser', 'class="paddingrightonly"').$langs->trans("ClearQtys").'</a>';
629  } else {
630  print '<a class="classfortooltip marginrightonly paddingright marginleftonly paddingleft" href="#" title="'.dol_escape_htmltag($langs->trans("NotEnoughPermissions")).'">'.$langs->trans("Save").'</a>'."\n";
631  }
632  }
633  print '<br>';
634  print '<br>';
635  print '</center>';
636  }
637 
638 
639  // Popup for mass barcode scanning
640  if ($action == 'updatebyscaning') {
641  if ($permissiontoadd) {
642  // Output the javascript to manage the scanner tool.
643  print '<script>';
644 
645  print '
646  var duplicatedbatchcode = [];
647  var errortab1 = [];
648  var errortab2 = [];
649  var errortab3 = [];
650  var errortab4 = [];
651 
652  function barcodescannerjs(){
653  console.log("We catch inputs in scanner box");
654  jQuery("#scantoolmessage").text();
655 
656  var selectaddorreplace = $("select[name=selectaddorreplace]").val();
657  var barcodemode = $("input[name=barcodemode]:checked").val();
658  var barcodeproductqty = $("input[name=barcodeproductqty]").val();
659  var textarea = $("textarea[name=barcodelist]").val();
660  var textarray = textarea.split(/[\s,;]+/);
661  var tabproduct = [];
662  duplicatedbatchcode = [];
663  errortab1 = [];
664  errortab2 = [];
665  errortab3 = [];
666  errortab4 = [];
667 
668  textarray = textarray.filter(function(value){
669  return value != "";
670  });
671  if(textarray.some((element) => element != "")){
672  $(".expectedqty").each(function(){
673  id = this.id;
674  console.log("Analyze the line "+id+" in inventory, barcodemode="+barcodemode);
675  warehouse = $("#"+id+"_warehouse").attr(\'data-ref\');
676  //console.log(warehouse);
677  productbarcode = $("#"+id+"_product").attr(\'data-barcode\');
678  //console.log(productbarcode);
679  productbatchcode = $("#"+id+"_batch").attr(\'data-batch\');
680  //console.log(productbatchcode);
681 
682  if (barcodemode != "barcodeforproduct") {
683  tabproduct.forEach(product=>{
684  console.log("product.Batch="+product.Batch+" productbatchcode="+productbatchcode);
685  if(product.Batch != "" && product.Batch == productbatchcode){
686  console.log("duplicate batch code found for batch code "+productbatchcode);
687  duplicatedbatchcode.push(productbatchcode);
688  }
689  })
690  }
691  productinput = $("#"+id+"_input").val();
692  if(productinput == ""){
693  productinput = 0
694  }
695  tabproduct.push({\'Id\':id,\'Warehouse\':warehouse,\'Barcode\':productbarcode,\'Batch\':productbatchcode,\'Qty\':productinput,\'fetched\':false});
696  });
697 
698  console.log("Loop on each record entered in the textarea");
699  textarray.forEach(function(element,index){
700  console.log("Process record element="+element+" id="+id);
701  var verify_batch = false;
702  var verify_barcode = false;
703  switch(barcodemode){
704  case "barcodeforautodetect":
705  verify_barcode = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"barcode",true);
706  verify_batch = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"lotserial",true);
707  break;
708  case "barcodeforproduct":
709  verify_barcode = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"barcode");
710  break;
711  case "barcodeforlotserial":
712  verify_batch = barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,"lotserial");
713  break;
714  default:
715  alert(\''.dol_escape_js($langs->trans("ErrorWrongBarcodemode")).' "\'+barcodemode+\'"\');
716  throw \''.dol_escape_js($langs->trans('ErrorWrongBarcodemode')).' "\'+barcodemode+\'"\';
717  }
718 
719  if (verify_batch == false && verify_barcode == false) { /* If the 2 flags are false, not found error */
720  errortab2.push(element);
721  } else if (verify_batch == true && verify_barcode == true) { /* If the 2 flags are true, error: we don t know which one to take */
722  errortab3.push(element);
723  } else if (verify_batch == true) {
724  console.log("element="+element);
725  console.log(duplicatedbatchcode);
726  if (duplicatedbatchcode.includes(element)) {
727  errortab1.push(element);
728  }
729  }
730  });
731 
732  if (Object.keys(errortab1).length < 1 && Object.keys(errortab2).length < 1 && Object.keys(errortab3).length < 1) {
733  tabproduct.forEach(product => {
734  if(product.Qty!=0){
735  console.log("We change #"+product.Id+"_input to match input in scanner box");
736  if(product.hasOwnProperty("reelqty")){
737  $.ajax({ url: \''.DOL_URL_ROOT.'/product/inventory/ajax/searchfrombarcode.php\',
738  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},
739  type: \'POST\',
740  async: false,
741  success: function(response) {
742  response = JSON.parse(response);
743  if(response.status == "success"){
744  console.log(response.message);
745  $("<input type=\'text\' value=\'"+product.Qty+"\' />")
746  .attr("id", "id_"+response.id_line+"_input")
747  .attr("name", "id_"+response.id_line)
748  .appendTo("#formrecord");
749  }else{
750  console.error(response.message);
751  }
752  },
753  error : function(output) {
754  console.error("Error on line creation function");
755  },
756  });
757  } else {
758  $("#"+product.Id+"_input").val(product.Qty);
759  }
760  }
761  });
762  jQuery("#scantoolmessage").text("'.dol_escape_js($langs->transnoentities("QtyWasAddedToTheScannedBarcode")).'\n");
763  /* document.forms["formrecord"].submit(); */
764  } else {
765  let stringerror = "";
766  if (Object.keys(errortab1).length > 0) {
767  stringerror += "<br>'.dol_escape_js($langs->transnoentities('ErrorSameBatchNumber')).': ";
768  errortab1.forEach(element => {
769  stringerror += (element + ", ")
770  });
771  stringerror = stringerror.slice(0, -2); /* Remove last ", " */
772  }
773  if (Object.keys(errortab2).length > 0) {
774  stringerror += "<br>'.dol_escape_js($langs->transnoentities('ErrorCantFindCodeInInventory')).': ";
775  errortab2.forEach(element => {
776  stringerror += (element + ", ")
777  });
778  stringerror = stringerror.slice(0, -2); /* Remove last ", " */
779  }
780  if (Object.keys(errortab3).length > 0) {
781  stringerror += "<br>'.dol_escape_js($langs->transnoentities('ErrorCodeScannedIsBothProductAndSerial')).': ";
782  errortab3.forEach(element => {
783  stringerror += (element + ", ")
784  });
785  stringerror = stringerror.slice(0, -2); /* Remove last ", " */
786  }
787  if (Object.keys(errortab4).length > 0) {
788  stringerror += "<br>'.dol_escape_js($langs->transnoentities('ErrorBarcodeNotFoundForProductWarehouse')).': ";
789  errortab4.forEach(element => {
790  stringerror += (element + ", ")
791  });
792  stringerror = stringerror.slice(0, -2); /* Remove last ", " */
793  }
794 
795  jQuery("#scantoolmessage").html(\''.dol_escape_js($langs->transnoentities("ErrorOnElementsInventory")).'\' + stringerror);
796  //alert("'.dol_escape_js($langs->trans("ErrorOnElementsInventory")).' :\n" + stringerror);
797  }
798  }
799 
800  }
801 
802  /* This methode is called by parent barcodescannerjs() */
803  function barcodeserialforproduct(tabproduct,index,element,barcodeproductqty,selectaddorreplace,mode,autodetect=false){
804  BarcodeIsInProduct=0;
805  newproductrow=0
806  result=false;
807  tabproduct.forEach(product => {
808  $.ajax({ url: \''.DOL_URL_ROOT.'/product/inventory/ajax/searchfrombarcode.php\',
809  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},
810  type: \'POST\',
811  async: false,
812  success: function(response) {
813  response = JSON.parse(response);
814  if (response.status == "success"){
815  console.log(response.message);
816  if(!newproductrow){
817  newproductrow = response.object;
818  }
819  }else{
820  if (mode!="lotserial" && autodetect==false && !errortab4.includes(element)){
821  errortab4.push(element);
822  console.error(response.message);
823  }
824  }
825  },
826  error : function(output) {
827  console.error("Error on barcodeserialforproduct function");
828  },
829  });
830  console.log("Product "+(index+=1)+": "+element);
831  if(mode == "barcode"){
832  testonproduct = product.Barcode
833  }else if (mode == "lotserial"){
834  testonproduct = product.Batch
835  }
836  if(testonproduct == element){
837  if(selectaddorreplace == "add"){
838  productqty = parseInt(product.Qty,10);
839  product.Qty = productqty + parseInt(barcodeproductqty,10);
840  }else if(selectaddorreplace == "replace"){
841  if(product.fetched == false){
842  product.Qty = barcodeproductqty
843  product.fetched=true
844  }else{
845  productqty = parseInt(product.Qty,10);
846  product.Qty = productqty + parseInt(barcodeproductqty,10);
847  }
848  }
849  BarcodeIsInProduct+=1;
850  }
851  })
852  if(BarcodeIsInProduct==0 && newproductrow!=0){
853  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});
854  result = true;
855  }
856  if(BarcodeIsInProduct > 0){
857  result = true;
858  }
859  return result;
860  }
861  ';
862  print '</script>';
863  }
864  include DOL_DOCUMENT_ROOT.'/core/class/html.formother.class.php';
865  $formother = new FormOther($db);
866  print $formother->getHTMLScannerForm("barcodescannerjs", 'all');
867  }
868 
869  //Call method to undo changes in real qty
870  print '<script>';
871  print 'jQuery(document).ready(function() {
872  $("#clearqty").on("click", function() {
873  console.log("Clear all values");
874  disablebuttonmakemovementandclose();
875  jQuery(".realqty").val("");
876  jQuery(".realqty").trigger("change");
877  return false; /* disable submit */
878  });
879  $(".undochangesqty").on("click", function undochangesqty() {
880  console.log("Clear value of inventory line");
881  id = this.id;
882  id = id.split("_")[1];
883  tmpvalue = $("#id_"+id+"_input_tmp").val()
884  $("#id_"+id+"_input")[0].value = tmpvalue;
885  disablebuttonmakemovementandclose();
886  return false; /* disable submit */
887  });
888  });';
889  print '</script>';
890 
891  print '<div class="fichecenter">';
892  //print '<div class="fichehalfleft">';
893  print '<div class="clearboth"></div>';
894 
895  //print load_fiche_titre($langs->trans('Consumption'), '', '');
896 
897  print '<div class="div-table-responsive-no-min">';
898  print '<table id="tablelines" class="noborder noshadow centpercent">';
899 
900  print '<tr class="liste_titre">';
901  print '<td>'.$langs->trans("Warehouse").'</td>';
902  print '<td>'.$langs->trans("Product").'</td>';
903  if (isModEnabled('productbatch')) {
904  print '<td>';
905  print $langs->trans("Batch");
906  print '</td>';
907  }
908  print '<td class="right">'.$langs->trans("ExpectedQty").'</td>';
909  if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
910  print '<td class="right">'.$langs->trans('PMPExpected').'</td>';
911  print '<td class="right">'.$langs->trans('ExpectedValuation').'</td>';
912  print '<td class="right">'.$form->textwithpicto($langs->trans("RealQty"), $langs->trans("InventoryRealQtyHelp")).'</td>';
913  print '<td class="right">'.$langs->trans('PMPReal').'</td>';
914  print '<td class="right">'.$langs->trans('RealValuation').'</td>';
915  } else {
916  print '<td class="right">';
917  print $form->textwithpicto($langs->trans("RealQty"), $langs->trans("InventoryRealQtyHelp"));
918  print '</td>';
919  }
920  if ($object->status == $object::STATUS_DRAFT || $object->status == $object::STATUS_VALIDATED) {
921  // Actions or link to stock movement
922  print '<td class="center">';
923  print '</td>';
924  } else {
925  // Actions or link to stock movement
926  print '<td class="right">';
927  //print $langs->trans("StockMovement");
928  print '</td>';
929  }
930  print '</tr>';
931 
932  // Line to add a new line in inventory
933  if ($object->status == $object::STATUS_DRAFT || $object->status == $object::STATUS_VALIDATED) {
934  print '<tr>';
935  print '<td>';
936  print $formproduct->selectWarehouses((GETPOSTISSET('fk_warehouse') ? GETPOST('fk_warehouse', 'int') : $object->fk_warehouse), 'fk_warehouse', 'warehouseopen', 1, 0, 0, '', 0, 0, array(), 'maxwidth300');
937  print '</td>';
938  print '<td>';
939  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');
940  print '</td>';
941  if (isModEnabled('productbatch')) {
942  print '<td>';
943  print '<input type="text" name="batch" class="maxwidth100" value="'.(GETPOSTISSET('batch') ? GETPOST('batch') : '').'">';
944  print '</td>';
945  }
946  print '<td class="right"></td>';
947  if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
948  print '<td class="right">';
949  print '</td>';
950  print '<td class="right">';
951  print '</td>';
952  print '<td class="right">';
953  print '<input type="text" name="qtytoadd" class="maxwidth75" value="">';
954  print '</td>';
955  print '<td class="right">';
956  print '</td>';
957  print '<td class="right">';
958  print '</td>';
959  } else {
960  print '<td class="right">';
961  print '<input type="text" name="qtytoadd" class="maxwidth75" value="">';
962  print '</td>';
963  }
964  // Actions
965  print '<td class="center">';
966  print '<input type="submit" class="button paddingright" name="addline" value="'.$langs->trans("Add").'">';
967  print '</td>';
968  print '</tr>';
969  }
970 
971  // Request to show lines of inventory (prefilled after start/validate step)
972  $sql = 'SELECT id.rowid, id.datec as date_creation, id.tms as date_modification, id.fk_inventory, id.fk_warehouse,';
973  $sql .= ' id.fk_product, id.batch, id.qty_stock, id.qty_view, id.qty_regulated, id.fk_movement, id.pmp_real, id.pmp_expected';
974  $sql .= ' FROM '.MAIN_DB_PREFIX.'inventorydet as id';
975  $sql .= ' WHERE id.fk_inventory = '.((int) $object->id);
976  $sql .= $db->order('id.rowid', 'ASC');
977  $sql .= $db->plimit($limit, $offset);
978 
979  $cacheOfProducts = array();
980  $cacheOfWarehouses = array();
981 
982  //$sql = '';
983  $resql = $db->query($sql);
984  if ($resql) {
985  $num = $db->num_rows($resql);
986 
987  if (!empty($limit != 0) || $num > $limit || $page) {
988  print_fleche_navigation($page, $_SERVER["PHP_SELF"], $paramwithsearch, ($num >= $limit), '<li class="pagination"><span>' . $langs->trans("Page") . ' ' . ($page + 1) . '</span></li>', '', $limit);
989  }
990 
991  $i = 0;
992  $hasinput = false;
993  $totalarray = array();
994  while ($i < $num) {
995  $obj = $db->fetch_object($resql);
996 
997  if (isset($cacheOfWarehouses[$obj->fk_warehouse])) {
998  $warehouse_static = $cacheOfWarehouses[$obj->fk_warehouse];
999  } else {
1000  $warehouse_static = new Entrepot($db);
1001  $warehouse_static->fetch($obj->fk_warehouse);
1002 
1003  $cacheOfWarehouses[$warehouse_static->id] = $warehouse_static;
1004  }
1005 
1006  // Load real stock we have now
1007  $option = '';
1008  if (isset($cacheOfProducts[$obj->fk_product])) {
1009  $product_static = $cacheOfProducts[$obj->fk_product];
1010  } else {
1011  $product_static = new Product($db);
1012  $result = $product_static->fetch($obj->fk_product, '', '', '', 1, 1, 1);
1013 
1014  //$option = 'nobatch';
1015  $option .= ',novirtual';
1016  $product_static->load_stock($option); // Load stock_reel + stock_warehouse.
1017 
1018  $cacheOfProducts[$product_static->id] = $product_static;
1019  }
1020 
1021  print '<tr class="oddeven">';
1022  print '<td id="id_'.$obj->rowid.'_warehouse" data-ref="'.dol_escape_htmltag($warehouse_static->ref).'">';
1023  print $warehouse_static->getNomUrl(1);
1024  print '</td>';
1025  print '<td id="id_'.$obj->rowid.'_product" data-ref="'.dol_escape_htmltag($product_static->ref).'" data-barcode="'.dol_escape_htmltag($product_static->barcode).'">';
1026  print $product_static->getNomUrl(1).' - '.$product_static->label;
1027  print '</td>';
1028 
1029  if (isModEnabled('productbatch')) {
1030  print '<td id="id_'.$obj->rowid.'_batch" data-batch="'.dol_escape_htmltag($obj->batch).'">';
1031  $batch_static = new Productlot($db);
1032  $res = $batch_static->fetch(0, $product_static->id, $obj->batch);
1033  if ($res) {
1034  print $batch_static->getNomUrl(1);
1035  } else {
1036  print dol_escape_htmltag($obj->batch);
1037  }
1038  print '</td>';
1039  }
1040 
1041  // Expected quantity = Quantity in stock when we start inventory
1042  print '<td class="right expectedqty" id="id_'.$obj->rowid.'" title="Stock viewed at last update: '.$obj->qty_stock.'">';
1043  $valuetoshow = $obj->qty_stock;
1044  // For inventory not yet close, we overwrite with the real value in stock now
1045  if ($object->status == $object::STATUS_DRAFT || $object->status == $object::STATUS_VALIDATED) {
1046  if (isModEnabled('productbatch') && $product_static->hasbatch()) {
1047  $valuetoshow = $product_static->stock_warehouse[$obj->fk_warehouse]->detail_batch[$obj->batch]->qty;
1048  } else {
1049  $valuetoshow = $product_static->stock_warehouse[$obj->fk_warehouse]->real;
1050  }
1051  }
1052  print price2num($valuetoshow, 'MS');
1053  print '<input type="hidden" name="stock_qty_'.$obj->rowid.'" value="'.$valuetoshow.'">';
1054  print '</td>';
1055 
1056  // Real quantity
1057  if ($object->status == $object::STATUS_DRAFT || $object->status == $object::STATUS_VALIDATED) {
1058  $qty_view = GETPOST("id_".$obj->rowid) && price2num(GETPOST("id_".$obj->rowid), 'MS') >= 0 ? GETPOST("id_".$obj->rowid) : $obj->qty_view;
1059 
1060  //if (!$hasinput && $qty_view !== null && $obj->qty_stock != $qty_view) {
1061  if ($qty_view != '') {
1062  $hasinput = true;
1063  }
1064 
1065  if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
1066  //PMP Expected
1067  if (!empty($obj->pmp_expected)) $pmp_expected = $obj->pmp_expected;
1068  else $pmp_expected = $product_static->pmp;
1069  $pmp_valuation = $pmp_expected * $valuetoshow;
1070  print '<td class="right">';
1071  print price($pmp_expected);
1072  print '<input type="hidden" name="expectedpmp_'.$obj->rowid.'" value="'.$pmp_expected.'"/>';
1073  print '</td>';
1074  print '<td class="right">';
1075  print price($pmp_valuation);
1076  print '</td>';
1077 
1078  print '<td class="right">';
1079  print '<a id="undochangesqty_'.$obj->rowid.'" href="#" class="undochangesqty reposition marginrightonly" title="'.dol_escape_htmltag($langs->trans("Clear")).'">';
1080  print img_picto('', 'eraser', 'class="opacitymedium"');
1081  print '</a>';
1082  print '<input type="text" class="maxwidth50 right realqty" name="id_'.$obj->rowid.'" id="id_'.$obj->rowid.'_input" value="'.$qty_view.'">';
1083  print '</td>';
1084 
1085  //PMP Real
1086  print '<td class="right">';
1087 
1088 
1089  if (!empty($obj->pmp_real)) $pmp_real = $obj->pmp_real;
1090  else $pmp_real = $product_static->pmp;
1091  $pmp_valuation_real = $pmp_real * $qty_view;
1092  print '<input type="text" class="maxwidth75 right realpmp'.$obj->fk_product.'" name="realpmp_'.$obj->rowid.'" id="id_'.$obj->rowid.'_input_pmp" value="'.price2num($pmp_real).'">';
1093  print '</td>';
1094  print '<td class="right">';
1095  print '<input type="text" class="maxwidth75 right realvaluation'.$obj->fk_product.'" name="realvaluation_'.$obj->rowid.'" id="id_'.$obj->rowid.'_input_real_valuation" value="'.$pmp_valuation_real.'">';
1096  print '</td>';
1097 
1098  $totalExpectedValuation += $pmp_valuation;
1099  $totalRealValuation += $pmp_valuation_real;
1100  } else {
1101  print '<td class="right">';
1102  print '<a id="undochangesqty_'.$obj->rowid.'" href="#" class="undochangesqty reposition marginrightonly" title="'.dol_escape_htmltag($langs->trans("Clear")).'">';
1103  print img_picto('', 'eraser', 'class="opacitymedium"');
1104  print '</a>';
1105  print '<input type="text" class="maxwidth50 right realqty" name="id_'.$obj->rowid.'" id="id_'.$obj->rowid.'_input" value="'.$qty_view.'">';
1106  print '</td>';
1107  }
1108 
1109  // Picto delete line
1110  print '<td class="right">';
1111  print '<a class="reposition" href="'.DOL_URL_ROOT.'/product/inventory/inventory.php?id='.$object->id.'&lineid='.$obj->rowid.'&action=deleteline&page='.$page.$paramwithsearch.'&token='.newToken().'">'.img_delete().'</a>';
1112  $qty_tmp = price2num(GETPOST("id_".$obj->rowid."_input_tmp", 'MS')) >= 0 ? GETPOST("id_".$obj->rowid."_input_tmp") : $qty_view;
1113  print '<input type="hidden" class="maxwidth50 right realqty" name="id_'.$obj->rowid.'_input_tmp" id="id_'.$obj->rowid.'_input_tmp" value="'.$qty_tmp.'">';
1114  print '</td>';
1115  } else {
1116  if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
1117  //PMP Expected
1118  if (!empty($obj->pmp_expected)) $pmp_expected = $obj->pmp_expected;
1119  else $pmp_expected = $product_static->pmp;
1120  $pmp_valuation = $pmp_expected * $valuetoshow;
1121  print '<td class="right">';
1122  print price($pmp_expected);
1123  print '</td>';
1124  print '<td class="right">';
1125  print price($pmp_valuation);
1126  print '</td>';
1127 
1128  print '<td class="right nowraponall">';
1129  print $obj->qty_view; // qty found
1130  print '</td>';
1131 
1132  //PMP Real
1133  print '<td class="right">';
1134  if (!empty($obj->pmp_real)) $pmp_real = $obj->pmp_real;
1135  else $pmp_real = $product_static->pmp;
1136  $pmp_valuation_real = $pmp_real * $obj->qty_view;
1137  print price($pmp_real);
1138  print '</td>';
1139  print '<td class="right">';
1140  print price($pmp_valuation_real);
1141  print '</td>';
1142  print '<td class="nowraponall right">';
1143 
1144  $totalExpectedValuation += $pmp_valuation;
1145  $totalRealValuation += $pmp_valuation_real;
1146  } else {
1147  print '<td class="right nowraponall">';
1148  print $obj->qty_view; // qty found
1149  print '</td>';
1150  }
1151  if ($obj->fk_movement > 0) {
1152  $stockmovment = new MouvementStock($db);
1153  $stockmovment->fetch($obj->fk_movement);
1154  print $stockmovment->getNomUrl(1, 'movements');
1155  }
1156  print '</td>';
1157  }
1158  print '</tr>';
1159 
1160  $i++;
1161  }
1162  } else {
1163  dol_print_error($db);
1164  }
1165  if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
1166  print '<tr class="liste_total">';
1167  print '<td colspan="4">'.$langs->trans("Total").'</td>';
1168  print '<td class="right" colspan="2">'.price($totalExpectedValuation).'</td>';
1169  print '<td class="right" id="totalRealValuation" colspan="3">'.price($totalRealValuation).'</td>';
1170  print '<td></td>';
1171  print '</tr>';
1172  }
1173  print '</table>';
1174 
1175  print '</div>';
1176 
1177  if ($object->status == $object::STATUS_VALIDATED) {
1178  print '<center><input id="submitrecord" type="submit" class="button button-save" name="save" value="'.$langs->trans("Save").'"></center>';
1179  }
1180 
1181  print '</div>';
1182 
1183 
1184  // Call method to disable the button if no qty entered yet for inventory
1185 
1186  if ($object->status != $object::STATUS_VALIDATED || !$hasinput) {
1187  print '<script type="text/javascript">
1188  jQuery(document).ready(function() {
1189  console.log("Call disablebuttonmakemovementandclose because status = '.((int) $object->status).' or $hasinput = '.((int) $hasinput).'");
1190  disablebuttonmakemovementandclose();
1191  });
1192  </script>';
1193  }
1194  print '</form>';
1195 
1196  print '<script type="text/javascript">
1197  $(document).ready(function() {
1198 
1199  $(".paginationnext:last").click(function(e){
1200  var form = $("#formrecord");
1201  var actionURL = "'.$_SERVER['PHP_SELF']."?page=".($page).$paramwithsearch.'";
1202  $.ajax({
1203  url: actionURL,
1204  data: form.serialize(),
1205  cache: false,
1206  success: function(result){
1207  window.location.href = "'.$_SERVER['PHP_SELF']."?page=".($page + 1).$paramwithsearch.'";
1208  }});
1209  });
1210 
1211 
1212  $(".paginationprevious:last").click(function(e){
1213  var form = $("#formrecord");
1214  var actionURL = "'.$_SERVER['PHP_SELF']."?page=".($page).$paramwithsearch.'";
1215  $.ajax({
1216  url: actionURL,
1217  data: form.serialize(),
1218  cache: false,
1219  success: function(result){
1220  window.location.href = "'.$_SERVER['PHP_SELF']."?page=".($page - 1).$paramwithsearch.'";
1221  }});
1222  });
1223 
1224  $("#idbuttonmakemovementandclose").click(function(e){
1225  var form = $("#formrecord");
1226  var actionURL = "'.$_SERVER['PHP_SELF']."?page=".($page).$paramwithsearch.'";
1227  $.ajax({
1228  url: actionURL,
1229  data: form.serialize(),
1230  cache: false,
1231  success: function(result){
1232  window.location.href = "'.$_SERVER['PHP_SELF']."?page=".($page - 1).$paramwithsearch.'&action=record";
1233  }});
1234  });
1235  });
1236  </script>';
1237 
1238 
1239  if (!empty($conf->global->INVENTORY_MANAGE_REAL_PMP)) {
1240  ?>
1241  <script type="text/javascript">
1242  $('.realqty').on('change', function () {
1243  let realqty = $(this).closest('tr').find('.realqty').val();
1244  let inputPmp = $(this).closest('tr').find('input[class*=realpmp]');
1245  let realpmp = $(inputPmp).val();
1246  if (!isNaN(realqty) && !isNaN(realpmp)) {
1247  let realval = realqty * realpmp;
1248  $(this).closest('tr').find('input[name^=realvaluation]').val(realval.toFixed(2));
1249  }
1250  updateTotalValuation();
1251  });
1252 
1253  $('input[class*=realpmp]').on('change', function () {
1254  let inputQtyReal = $(this).closest('tr').find('.realqty');
1255  let realqty = $(inputQtyReal).val();
1256  let inputPmp = $(this).closest('tr').find('input[class*=realpmp]');
1257  console.log(inputPmp);
1258  let realPmpClassname = $(inputPmp).attr('class').match(/[\w-]*realpmp[\w-]*/g)[0];
1259  let realpmp = $(inputPmp).val();
1260  if (!isNaN(realpmp)) {
1261  $('.'+realPmpClassname).val(realpmp); //For batch case if pmp is changed we change it everywhere it's same product and calc back everything
1262 
1263  if (!isNaN(realqty)) {
1264  let realval = realqty * realpmp;
1265  $(this).closest('tr').find('input[name^=realvaluation]').val(realval.toFixed(2));
1266  }
1267  $('.realqty').trigger('change');
1268  updateTotalValuation();
1269  }
1270  });
1271 
1272  $('input[name^=realvaluation]').on('change', function () {
1273  let inputQtyReal = $(this).closest('tr').find('.realqty');
1274  let realqty = $(inputQtyReal).val();
1275  let inputPmp = $(this).closest('tr').find('input[class*=realpmp]');
1276  let inputRealValuation = $(this).closest('tr').find('input[name^=realvaluation]');
1277  let realPmpClassname = $(inputPmp).attr('class').match(/[\w-]*realpmp[\w-]*/g)[0];
1278  let realvaluation = $(inputRealValuation).val();
1279  if (!isNaN(realvaluation) && !isNaN(realqty) && realvaluation !== '' && realqty !== '' && realqty !== 0) {
1280  let realpmp = realvaluation / realqty
1281  $('.'+realPmpClassname).val(realpmp); //For batch case if pmp is changed we change it everywhere it's same product and calc back everything
1282  $('.realqty').trigger('change');
1283  updateTotalValuation();
1284  }
1285  });
1286 
1287  function updateTotalValuation() {
1288  let total = 0;
1289  $('input[name^=realvaluation]').each(function( index ) {
1290  let val = $(this).val();
1291  if(!isNaN(val)) total += parseFloat($(this).val());
1292  });
1293  let currencyFractionDigits = new Intl.NumberFormat('fr-FR', {
1294  style: 'currency',
1295  currency: 'EUR',
1296  }).resolvedOptions().maximumFractionDigits;
1297  $('#totalRealValuation').html(total.toLocaleString('fr-FR', {
1298  maximumFractionDigits: currencyFractionDigits
1299  }));
1300  }
1301 
1302  </script>
1303  <?php
1304  }
1305 }
1306 
1307 // End of page
1308 llxFooter();
1309 $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...
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.