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