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