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