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