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