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