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