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