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