dolibarr 18.0.6
mouvementstock.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2003-2006 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2005-2015 Laurent Destailleur <eldy@users.sourceforge.net>
4 * Copyright (C) 2011 Jean Heimburger <jean@tiaris.info>
5 * Copyright (C) 2014 Cedric GROSS <c.gross@kreiz-it.fr>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
32{
36 public $element = 'stockmouvement';
37
41 public $table_element = 'stock_mouvement';
42
43
47 public $product_id;
48
54 public $entrepot_id;
55
59 public $warehouse_id;
60
61 public $qty;
62
71 public $type;
72
73 public $tms = '';
74 public $datem = '';
75 public $price;
76
80 public $fk_user_author;
81
85 public $label;
86
92 public $fk_origin;
93
97 public $origin_id;
98
104 public $origintype;
105
109 public $origin_type;
110
111
112 public $inventorycode;
113 public $batch;
114
115 public $line_id_object_src;
116 public $line_id_object_origin;
117
118
119 public $fields = array(
120 'rowid' =>array('type'=>'integer', 'label'=>'TechnicalID', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>10, 'showoncombobox'=>1),
121 'tms' =>array('type'=>'timestamp', 'label'=>'DateModification', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>15),
122 'datem' =>array('type'=>'datetime', 'label'=>'Datem', 'enabled'=>1, 'visible'=>-1, 'position'=>20),
123 'fk_product' =>array('type'=>'integer:Product:product/class/product.class.php:1', 'label'=>'Product', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>25),
124 'fk_entrepot' =>array('type'=>'integer:Entrepot:product/stock/class/entrepot.class.php', 'label'=>'Warehouse', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>30),
125 'value' =>array('type'=>'double', 'label'=>'Value', 'enabled'=>1, 'visible'=>-1, 'position'=>35),
126 'price' =>array('type'=>'double(24,8)', 'label'=>'Price', 'enabled'=>1, 'visible'=>-1, 'position'=>40),
127 'type_mouvement' =>array('type'=>'smallint(6)', 'label'=>'Type mouvement', 'enabled'=>1, 'visible'=>-1, 'position'=>45),
128 'fk_user_author' =>array('type'=>'integer:User:user/class/user.class.php', 'label'=>'Fk user author', 'enabled'=>1, 'visible'=>-1, 'position'=>50),
129 'label' =>array('type'=>'varchar(255)', 'label'=>'Label', 'enabled'=>1, 'visible'=>-1, 'position'=>55),
130 'fk_origin' =>array('type'=>'integer', 'label'=>'Fk origin', 'enabled'=>1, 'visible'=>-1, 'position'=>60),
131 'origintype' =>array('type'=>'varchar(32)', 'label'=>'Origintype', 'enabled'=>1, 'visible'=>-1, 'position'=>65),
132 'model_pdf' =>array('type'=>'varchar(255)', 'label'=>'Model pdf', 'enabled'=>1, 'visible'=>0, 'position'=>70),
133 'fk_projet' =>array('type'=>'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label'=>'Project', 'enabled'=>'$conf->project->enabled', 'visible'=>-1, 'notnull'=>1, 'position'=>75),
134 'inventorycode' =>array('type'=>'varchar(128)', 'label'=>'InventoryCode', 'enabled'=>1, 'visible'=>-1, 'position'=>80),
135 'batch' =>array('type'=>'varchar(30)', 'label'=>'Batch', 'enabled'=>1, 'visible'=>-1, 'position'=>85),
136 'eatby' =>array('type'=>'date', 'label'=>'Eatby', 'enabled'=>1, 'visible'=>-1, 'position'=>90),
137 'sellby' =>array('type'=>'date', 'label'=>'Sellby', 'enabled'=>1, 'visible'=>-1, 'position'=>95),
138 'fk_project' =>array('type'=>'integer:Project:projet/class/project.class.php:1:(fk_statut:=:1)', 'label'=>'Fk project', 'enabled'=>1, 'visible'=>-1, 'position'=>100),
139 );
140
141
142
148 public function __construct($db)
149 {
150 $this->db = $db;
151 }
152
153 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
180 public function _create($user, $fk_product, $entrepot_id, $qty, $type, $price = 0, $label = '', $inventorycode = '', $datem = '', $eatby = '', $sellby = '', $batch = '', $skip_batch = false, $id_product_batch = 0, $disablestockchangeforsubproduct = 0, $donotcleanemptylines = 0)
181 {
182 // phpcs:enable
183 global $conf, $langs;
184
185 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
186 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/productlot.class.php';
187
188 $error = 0;
189 dol_syslog(get_class($this)."::_create start userid=$user->id, fk_product=$fk_product, warehouse_id=$entrepot_id, qty=$qty, type=$type, price=$price, label=$label, inventorycode=$inventorycode, datem=".$datem.", eatby=".$eatby.", sellby=".$sellby.", batch=".$batch.", skip_batch=".$skip_batch);
190
191 // Call hook at beginning
192 global $action, $hookmanager;
193 $hookmanager->initHooks(array('mouvementstock'));
194
195 if (is_object($hookmanager)) {
196 $parameters = array(
197 'currentcontext' => 'mouvementstock',
198 'user' => &$user,
199 'fk_product' => &$fk_product,
200 'entrepot_id' => &$entrepot_id,
201 'qty' => &$qty,
202 'type' => &$type,
203 'price' => &$price,
204 'label' => &$label,
205 'inventorycode' => &$inventorycode,
206 'datem' => &$datem,
207 'eatby' => &$eatby,
208 'sellby' => &$sellby,
209 'batch' => &$batch,
210 'skip_batch' => &$skip_batch,
211 'id_product_batch' => &$id_product_batch
212 );
213 $reshook = $hookmanager->executeHooks('stockMovementCreate', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
214
215 if ($reshook < 0) {
216 if (!empty($hookmanager->resPrint)) {
217 dol_print_error('', $hookmanager->resPrint);
218 }
219 return $reshook;
220 } elseif ($reshook > 0) {
221 return $hookmanager->resPrint;
222 }
223 }
224 // end hook at beginning
225
226 // Clean parameters
227 $price = price2num($price, 'MU'); // Clean value for the casse we receive a float zero value, to have it a real zero value.
228 if (empty($price)) $price = 0;
229 $now = (!empty($datem) ? $datem : dol_now());
230
231 // Check parameters
232 if (!($fk_product > 0)) return 0;
233 if (!($entrepot_id > 0)) return 0;
234
235 if (is_numeric($eatby) && $eatby < 0) {
236 dol_syslog(get_class($this)."::_create start ErrorBadValueForParameterEatBy eatby = ".$eatby);
237 $this->errors[] = 'ErrorBadValueForParameterEatBy';
238 return -1;
239 }
240 if (is_numeric($sellby) && $sellby < 0) {
241 dol_syslog(get_class($this)."::_create start ErrorBadValueForParameterSellBy sellby = ".$sellby);
242 $this->errors[] = 'ErrorBadValueForParameterSellBy';
243 return -1;
244 }
245
246 // Set properties of movement
247 $this->product_id = $fk_product;
248 $this->entrepot_id = $entrepot_id; // deprecated
249 $this->warehouse_id = $entrepot_id;
250 $this->qty = $qty;
251 $this->type = $type;
252 $this->price = price2num($price);
253 $this->label = $label;
254 $this->inventorycode = $inventorycode;
255 $this->datem = $now;
256 $this->batch = $batch;
257
258 $mvid = 0;
259
260 $product = new Product($this->db);
261
262 $result = $product->fetch($fk_product);
263 if ($result < 0) {
264 $this->error = $product->error;
265 $this->errors = $product->errors;
266 dol_print_error('', "Failed to fetch product");
267 return -1;
268 }
269 if ($product->id <= 0) { // Can happen if database is corrupted (a product id exist in stock with product that has been removed)
270 return 0;
271 }
272
273 // Define if we must make the stock change (If product type is a service or if stock is used also for services)
274 // Only record into stock tables wil be disabled by this (the rest like writing into lot table or movement of subproucts are done)
275 $movestock = 0;
276 if ($product->type != Product::TYPE_SERVICE || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) $movestock = 1;
277
278 $this->db->begin();
279
280 // Set value $product->stock_reel and detail per warehouse into $product->stock_warehouse array
281 if ($movestock) {
282 $product->load_stock('novirtual');
283 }
284
285 // Test if product require batch data. If yes, and there is not or values are not correct, we throw an error.
286 if (isModEnabled('productbatch') && $product->hasbatch() && !$skip_batch) {
287 if (empty($batch)) {
288 $langs->load("errors");
289 $this->errors[] = $langs->transnoentitiesnoconv("ErrorTryToMakeMoveOnProductRequiringBatchData", $product->ref);
290 dol_syslog("Try to make a movement of a product with status_batch on without any batch data");
291
292 $this->db->rollback();
293 return -2;
294 }
295
296 // Check table llx_product_lot from batchnumber for same product
297 // If found and eatby/sellby defined into table and provided and differs, return error
298 // If found and eatby/sellby defined into table and not provided, we take value from table
299 // If found and eatby/sellby not defined into table and provided, we update table
300 // If found and eatby/sellby not defined into table and not provided, we do nothing
301 // If not found, we add record
302 $sql = "SELECT pb.rowid, pb.batch, pb.eatby, pb.sellby FROM ".$this->db->prefix()."product_lot as pb";
303 $sql .= " WHERE pb.fk_product = ".((int) $fk_product)." AND pb.batch = '".$this->db->escape($batch)."'";
304
305 dol_syslog(get_class($this)."::_create scan serial for this product to check if eatby and sellby match", LOG_DEBUG);
306
307 $resql = $this->db->query($sql);
308 if ($resql) {
309 $num = $this->db->num_rows($resql);
310 $i = 0;
311 if ($num > 0) {
312 while ($i < $num) {
313 $obj = $this->db->fetch_object($resql);
314 if ($obj->eatby) {
315 if ($eatby) {
316 $tmparray = dol_getdate($eatby, true);
317 $eatbywithouthour = dol_mktime(0, 0, 0, $tmparray['mon'], $tmparray['mday'], $tmparray['year']);
318 if ($this->db->jdate($obj->eatby) != $eatby && $this->db->jdate($obj->eatby) != $eatbywithouthour) { // We test date without hours and with hours for backward compatibility
319 // If found and eatby/sellby defined into table and provided and differs, return error
320 $langs->load("stocks");
321 $this->errors[] = $langs->transnoentitiesnoconv("ThisSerialAlreadyExistWithDifferentDate", $batch, dol_print_date($this->db->jdate($obj->eatby), 'dayhour'), dol_print_date($eatbywithouthour, 'dayhour'));
322 dol_syslog("ThisSerialAlreadyExistWithDifferentDate batch=".$batch.", eatby found into product_lot = ".$obj->eatby." = ".dol_print_date($this->db->jdate($obj->eatby), 'dayhourrfc')." so eatbywithouthour = ".$eatbywithouthour." = ".dol_print_date($eatbywithouthour)." - eatby provided = ".$eatby." = ".dol_print_date($eatby, 'dayhourrfc'), LOG_ERR);
323 $this->db->rollback();
324 return -3;
325 }
326 } else {
327 $eatby = $obj->eatby; // If found and eatby/sellby defined into table and not provided, we take value from table
328 }
329 } else {
330 if ($eatby) { // If found and eatby/sellby not defined into table and provided, we update table
331 $productlot = new Productlot($this->db);
332 $result = $productlot->fetch($obj->rowid);
333 $productlot->eatby = $eatby;
334 $result = $productlot->update($user);
335 if ($result <= 0) {
336 $this->error = $productlot->error;
337 $this->errors = $productlot->errors;
338 $this->db->rollback();
339 return -5;
340 }
341 }
342 }
343 if ($obj->sellby) {
344 if ($sellby) {
345 $tmparray = dol_getdate($sellby, true);
346 $sellbywithouthour = dol_mktime(0, 0, 0, $tmparray['mon'], $tmparray['mday'], $tmparray['year']);
347 if ($this->db->jdate($obj->sellby) != $sellby && $this->db->jdate($obj->sellby) != $sellbywithouthour) { // We test date without hours and with hours for backward compatibility
348 // If found and eatby/sellby defined into table and provided and differs, return error
349 $this->errors[] = $langs->transnoentitiesnoconv("ThisSerialAlreadyExistWithDifferentDate", $batch, dol_print_date($this->db->jdate($obj->sellby)), dol_print_date($sellby));
350 dol_syslog($langs->transnoentities("ThisSerialAlreadyExistWithDifferentDate", $batch, dol_print_date($this->db->jdate($obj->sellby)), dol_print_date($sellby)), LOG_ERR);
351 $this->db->rollback();
352 return -3;
353 }
354 } else {
355 $sellby = $obj->sellby; // If found and eatby/sellby defined into table and not provided, we take value from table
356 }
357 } else {
358 if ($sellby) { // If found and eatby/sellby not defined into table and provided, we update table
359 $productlot = new Productlot($this->db);
360 $result = $productlot->fetch($obj->rowid);
361 $productlot->sellby = $sellby;
362 $result = $productlot->update($user);
363 if ($result <= 0) {
364 $this->error = $productlot->error;
365 $this->errors = $productlot->errors;
366 $this->db->rollback();
367 return -5;
368 }
369 }
370 }
371
372 $i++;
373 }
374 } else { // If not found, we add record
375 $productlot = new Productlot($this->db);
376 $productlot->origin = !empty($this->origin_type) ? $this->origin_type : '';
377 $productlot->origin_id = !empty($this->origin_id) ? $this->origin_id : 0;
378 $productlot->entity = $conf->entity;
379 $productlot->fk_product = $fk_product;
380 $productlot->batch = $batch;
381 // If we are here = first time we manage this batch, so we used dates provided by users to create lot
382 $productlot->eatby = $eatby;
383 $productlot->sellby = $sellby;
384 $result = $productlot->create($user);
385 if ($result <= 0) {
386 $this->error = $productlot->error;
387 $this->errors = $productlot->errors;
388 $this->db->rollback();
389 return -4;
390 }
391 }
392 } else {
393 dol_print_error($this->db);
394 $this->db->rollback();
395 return -1;
396 }
397 }
398
399 // Check if stock is enough when qty is < 0
400 // Note that qty should be > 0 with type 0 or 3, < 0 with type 1 or 2.
401 if ($movestock && $qty < 0 && !getDolGlobalInt('STOCK_ALLOW_NEGATIVE_TRANSFER')) {
402 if (isModEnabled('productbatch') && $product->hasbatch() && !$skip_batch) {
403 $foundforbatch = 0;
404 $qtyisnotenough = 0;
405
406 foreach ($product->stock_warehouse[$entrepot_id]->detail_batch as $batchcursor => $prodbatch) {
407 if ((string) $batch != (string) $batchcursor) { // Lot '59' must be different than lot '59c'
408 continue;
409 }
410
411 $foundforbatch = 1;
412 if ($prodbatch->qty < abs($qty)) $qtyisnotenough = $prodbatch->qty;
413 break;
414 }
415 if (!$foundforbatch || $qtyisnotenough) {
416 $langs->load("stocks");
417 include_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
418 $tmpwarehouse = new Entrepot($this->db);
419 $tmpwarehouse->fetch($entrepot_id);
420
421 $this->error = $langs->trans('qtyToTranferLotIsNotEnough', $product->ref, $batch, $qtyisnotenough, $tmpwarehouse->ref);
422 $this->errors[] = $langs->trans('qtyToTranferLotIsNotEnough', $product->ref, $batch, $qtyisnotenough, $tmpwarehouse->ref);
423 $this->db->rollback();
424 return -8;
425 }
426 } else {
427 if (empty($product->stock_warehouse[$entrepot_id]->real) || $product->stock_warehouse[$entrepot_id]->real < abs($qty)) {
428 $langs->load("stocks");
429 $this->error = $langs->trans('qtyToTranferIsNotEnough').' : '.$product->ref;
430 $this->errors[] = $langs->trans('qtyToTranferIsNotEnough').' : '.$product->ref;
431 $this->db->rollback();
432 return -8;
433 }
434 }
435 }
436
437 if ($movestock) { // Change stock for current product, change for subproduct is done after
438 // Set $origin_type, origin_id and fk_project
439 $fk_project = $this->fk_project;
440 if (!empty($this->origin_type)) { // This is set by caller for tracking reason
441 $origin_type = $this->origin_type;
442 $origin_id = $this->origin_id;
443 if (empty($fk_project) && $origin_type == 'project') {
444 $fk_project = $origin_id;
445 $origin_type = '';
446 $origin_id = 0;
447 }
448 } else {
449 $fk_project = 0;
450 $origin_type = '';
451 $origin_id = 0;
452 }
453
454 $sql = "INSERT INTO ".$this->db->prefix()."stock_mouvement(";
455 $sql .= " datem, fk_product, batch, eatby, sellby,";
456 $sql .= " fk_entrepot, value, type_mouvement, fk_user_author, label, inventorycode, price, fk_origin, origintype, fk_projet";
457 $sql .= ")";
458 $sql .= " VALUES ('".$this->db->idate($this->datem)."', ".((int) $this->product_id).", ";
459 $sql .= " ".($batch ? "'".$this->db->escape($batch)."'" : "null").", ";
460 $sql .= " ".($eatby ? "'".$this->db->idate($eatby)."'" : "null").", ";
461 $sql .= " ".($sellby ? "'".$this->db->idate($sellby)."'" : "null").", ";
462 $sql .= " ".((int) $this->entrepot_id).", ".((float) $this->qty).", ".((int) $this->type).",";
463 $sql .= " ".((int) $user->id).",";
464 $sql .= " '".$this->db->escape($label)."',";
465 $sql .= " ".($inventorycode ? "'".$this->db->escape($inventorycode)."'" : "null").",";
466 $sql .= " ".((float) price2num($price)).",";
467 $sql .= " ".((int) $origin_id).",";
468 $sql .= " '".$this->db->escape($origin_type)."',";
469 $sql .= " ".((int) $fk_project);
470 $sql .= ")";
471
472 dol_syslog(get_class($this)."::_create insert record into stock_mouvement", LOG_DEBUG);
473 $resql = $this->db->query($sql);
474
475 if ($resql) {
476 $mvid = $this->db->last_insert_id($this->db->prefix()."stock_mouvement");
477 $this->id = $mvid;
478 } else {
479 $this->error = $this->db->lasterror();
480 $this->errors[] = $this->error;
481 $error = -1;
482 }
483
484 // Define current values for qty and pmp
485 $oldqty = $product->stock_reel;
486 $oldpmp = $product->pmp;
487 $oldqtywarehouse = 0;
488
489 // Test if there is already a record for couple (warehouse / product), so later we will make an update or create.
490 $alreadyarecord = 0;
491 if (!$error) {
492 $sql = "SELECT rowid, reel FROM ".$this->db->prefix()."product_stock";
493 $sql .= " WHERE fk_entrepot = ".((int) $entrepot_id)." AND fk_product = ".((int) $fk_product); // This is a unique key
494
495 dol_syslog(get_class($this)."::_create check if a record already exists in product_stock", LOG_DEBUG);
496 $resql = $this->db->query($sql);
497 if ($resql) {
498 $obj = $this->db->fetch_object($resql);
499 if ($obj) {
500 $alreadyarecord = 1;
501 $oldqtywarehouse = $obj->reel;
502 $fk_product_stock = $obj->rowid;
503 }
504 $this->db->free($resql);
505 } else {
506 $this->errors[] = $this->db->lasterror();
507 $error = -2;
508 }
509 }
510
511 // Calculate new AWP (PMP)
512 $newpmp = 0;
513 if (!$error) {
514 if ($type == 0 || $type == 3) {
515 // After a stock increase
516 // Note: PMP is calculated on stock input only (type of movement = 0 or 3). If type == 0 or 3, qty should be > 0.
517 // Note: Price should always be >0 or 0. PMP should be always >0 (calculated on input)
518 if ($price > 0 || (!empty($conf->global->STOCK_UPDATE_AWP_EVEN_WHEN_ENTRY_PRICE_IS_NULL) && $price == 0)) {
519 $oldqtytouse = ($oldqty >= 0 ? $oldqty : 0);
520 // We make a test on oldpmp>0 to avoid to use normal rule on old data with no pmp field defined
521 if ($oldpmp > 0) {
522 $newpmp = price2num((($oldqtytouse * $oldpmp) + ($qty * $price)) / ($oldqtytouse + $qty), 'MU');
523 } else {
524 $newpmp = $price; // For this product, PMP was not yet set. We set it to input price.
525 }
526 //print "oldqtytouse=".$oldqtytouse." oldpmp=".$oldpmp." oldqtywarehousetouse=".$oldqtywarehousetouse." ";
527 //print "qty=".$qty." newpmp=".$newpmp;
528 //exit;
529 } else {
530 $newpmp = $oldpmp;
531 }
532 } elseif ($type == 1 || $type == 2) {
533 // After a stock decrease, we don't change value of the AWP/PMP of a product.
534 $newpmp = $oldpmp;
535 } else {
536 // Type of movement unknown
537 $newpmp = $oldpmp;
538 }
539 }
540 // Update stock quantity
541 if (!$error) {
542 if ($alreadyarecord > 0) {
543 $sql = "UPDATE ".$this->db->prefix()."product_stock SET reel = COALESCE(reel, 0) + ".((float) $qty);
544 $sql .= " WHERE fk_entrepot = ".((int) $entrepot_id)." AND fk_product = ".((int) $fk_product);
545 } else {
546 $sql = "INSERT INTO ".$this->db->prefix()."product_stock";
547 $sql .= " (reel, fk_entrepot, fk_product) VALUES ";
548 $sql .= " (".((float) $qty).", ".((int) $entrepot_id).", ".((int) $fk_product).")";
549 }
550
551 dol_syslog(get_class($this)."::_create update stock value", LOG_DEBUG);
552 $resql = $this->db->query($sql);
553 if (!$resql) {
554 $this->errors[] = $this->db->lasterror();
555 $error = -3;
556 } elseif (empty($fk_product_stock)) {
557 $fk_product_stock = $this->db->last_insert_id($this->db->prefix()."product_stock");
558 }
559 }
560
561 // Update detail of stock for the lot.
562 if (!$error && isModEnabled('productbatch') && $product->hasbatch() && !$skip_batch) {
563 if ($id_product_batch > 0) {
564 $result = $this->createBatch($id_product_batch, $qty);
565 if ($result == -2 && $fk_product_stock > 0) { // The entry for this product batch does not exists anymore, bu we already have a llx_product_stock, so we recreate the batch entry in product_batch
566 $param_batch = array('fk_product_stock' =>$fk_product_stock, 'batchnumber'=>$batch);
567 $result = $this->createBatch($param_batch, $qty);
568 }
569 } else {
570 $param_batch = array('fk_product_stock' =>$fk_product_stock, 'batchnumber'=>$batch);
571 $result = $this->createBatch($param_batch, $qty);
572 }
573 if ($result < 0) {
574 $error++;
575 }
576 }
577
578 // Update PMP and denormalized value of stock qty at product level
579 if (!$error) {
580 $newpmp = price2num($newpmp, 'MU');
581
582 // $sql = "UPDATE ".$this->db->prefix()."product SET pmp = ".$newpmp.", stock = ".$this->db->ifsql("stock IS NULL", 0, "stock") . " + ".$qty;
583 // $sql.= " WHERE rowid = ".((int) $fk_product);
584 // Update pmp + denormalized fields because we change content of produt_stock. Warning: Do not use "SET p.stock", does not works with pgsql
585 $sql = "UPDATE ".$this->db->prefix()."product as p SET pmp = ".((float) $newpmp).",";
586 $sql .= " stock=(SELECT SUM(ps.reel) FROM ".$this->db->prefix()."product_stock as ps WHERE ps.fk_product = p.rowid)";
587 $sql .= " WHERE rowid = ".((int) $fk_product);
588
589 dol_syslog(get_class($this)."::_create update AWP", LOG_DEBUG);
590 $resql = $this->db->query($sql);
591 if (!$resql) {
592 $this->errors[] = $this->db->lasterror();
593 $error = -4;
594 }
595 }
596
597 if (empty($donotcleanemptylines)) {
598 // If stock is now 0, we can remove entry into llx_product_stock, but only if there is no child lines into llx_product_batch (detail of batch, because we can imagine
599 // having a lot1/qty=X and lot2/qty=-X, so 0 but we must not loose repartition of different lot.
600 $sql = "DELETE FROM ".$this->db->prefix()."product_stock WHERE reel = 0 AND rowid NOT IN (SELECT fk_product_stock FROM ".$this->db->prefix()."product_batch as pb)";
601 $resql = $this->db->query($sql);
602 // We do not test error, it can fails if there is child in batch details
603 }
604 }
605
606 // Add movement for sub products (recursive call)
607 if (!$error && !empty($conf->global->PRODUIT_SOUSPRODUITS) && empty($conf->global->INDEPENDANT_SUBPRODUCT_STOCK) && empty($disablestockchangeforsubproduct)) {
608 $error = $this->_createSubProduct($user, $fk_product, $entrepot_id, $qty, $type, 0, $label, $inventorycode, $datem); // we use 0 as price, because AWP must not change for subproduct
609 }
610
611 if ($movestock && !$error) {
612 // Call trigger
613 $result = $this->call_trigger('STOCK_MOVEMENT', $user);
614 if ($result < 0) $error++;
615 // End call triggers
616
617 // Check unicity for serial numbered equipments once all movement were done.
618 if (!$error && isModEnabled('productbatch') && $product->hasbatch() && !$skip_batch) {
619 if ($product->status_batch == 2 && $qty > 0) { // We check only if we increased qty
620 if ($this->getBatchCount($fk_product, $batch) > 1) {
621 $error++;
622 $this->errors[] = $langs->trans("TooManyQtyForSerialNumber", $product->ref, $batch);
623 }
624 }
625 }
626 }
627
628 if (!$error) {
629 $this->db->commit();
630 return $mvid;
631 } else {
632 $this->db->rollback();
633 dol_syslog(get_class($this)."::_create error code=".$error, LOG_ERR);
634 return -6;
635 }
636 }
637
638
639
647 public function fetch($id)
648 {
649 dol_syslog(__METHOD__, LOG_DEBUG);
650
651 $sql = "SELECT";
652 $sql .= " t.rowid,";
653 $sql .= " t.tms,";
654 $sql .= " t.datem,";
655 $sql .= " t.fk_product,";
656 $sql .= " t.fk_entrepot,";
657 $sql .= " t.value,";
658 $sql .= " t.price,";
659 $sql .= " t.type_mouvement,";
660 $sql .= " t.fk_user_author,";
661 $sql .= " t.label,";
662 $sql .= " t.fk_origin as origin_id,";
663 $sql .= " t.origintype as origin_type,";
664 $sql .= " t.inventorycode,";
665 $sql .= " t.batch,";
666 $sql .= " t.eatby,";
667 $sql .= " t.sellby,";
668 $sql .= " t.fk_projet as fk_project";
669 $sql .= " FROM ".$this->db->prefix().$this->table_element." as t";
670 $sql .= " WHERE t.rowid = ".((int) $id);
671
672 $resql = $this->db->query($sql);
673 if ($resql) {
674 $numrows = $this->db->num_rows($resql);
675 if ($numrows) {
676 $obj = $this->db->fetch_object($resql);
677
678 $this->id = $obj->rowid;
679
680 $this->product_id = $obj->fk_product;
681 $this->warehouse_id = $obj->fk_entrepot;
682 $this->qty = $obj->value;
683 $this->type = $obj->type_mouvement;
684
685 $this->tms = $this->db->jdate($obj->tms);
686 $this->datem = $this->db->jdate($obj->datem);
687 $this->price = $obj->price;
688 $this->fk_user_author = $obj->fk_user_author;
689 $this->label = $obj->label;
690 $this->fk_origin = $obj->origin_id; // For backward compatibility
691 $this->origintype = $obj->origin_type; // For backward compatibility
692 $this->origin_id = $obj->origin_id;
693 $this->origin_type = $obj->origin_type;
694 $this->inventorycode = $obj->inventorycode;
695 $this->batch = $obj->batch;
696 $this->eatby = $this->db->jdate($obj->eatby);
697 $this->sellby = $this->db->jdate($obj->sellby);
698 $this->fk_project = $obj->fk_project;
699 }
700
701 // Retrieve all extrafield
702 $this->fetch_optionals();
703
704 // $this->fetch_lines();
705
706 $this->db->free($resql);
707
708 if ($numrows) {
709 return 1;
710 } else {
711 return 0;
712 }
713 } else {
714 $this->errors[] = 'Error '.$this->db->lasterror();
715 dol_syslog(__METHOD__.' '.implode(',', $this->errors), LOG_ERR);
716
717 return -1;
718 }
719 }
720
721
722
723
738 private function _createSubProduct($user, $idProduct, $entrepot_id, $qty, $type, $price = 0, $label = '', $inventorycode = '', $datem = '')
739 {
740 global $langs;
741
742 $error = 0;
743 $pids = array();
744 $pqtys = array();
745
746 $sql = "SELECT fk_product_pere, fk_product_fils, qty";
747 $sql .= " FROM ".$this->db->prefix()."product_association";
748 $sql .= " WHERE fk_product_pere = ".((int) $idProduct);
749 $sql .= " AND incdec = 1";
750
751 dol_syslog(get_class($this)."::_createSubProduct for parent product ".$idProduct, LOG_DEBUG);
752 $resql = $this->db->query($sql);
753 if ($resql) {
754 $i = 0;
755 while ($obj = $this->db->fetch_object($resql)) {
756 $pids[$i] = $obj->fk_product_fils;
757 $pqtys[$i] = $obj->qty;
758 $i++;
759 }
760 $this->db->free($resql);
761 } else {
762 $error = -2;
763 }
764
765 // Create movement for each subproduct
766 foreach ($pids as $key => $value) {
767 if (!$error) {
768 $tmpmove = dol_clone($this, 1);
769
770 $result = $tmpmove->_create($user, $pids[$key], $entrepot_id, ($qty * $pqtys[$key]), $type, 0, $label, $inventorycode, $datem); // This will also call _createSubProduct making this recursive
771 if ($result < 0) {
772 $this->error = $tmpmove->error;
773 $this->errors = array_merge($this->errors, $tmpmove->errors);
774 if ($result == -2) {
775 $this->errors[] = $langs->trans("ErrorNoteAlsoThatSubProductCantBeFollowedByLot");
776 }
777 $error = $result;
778 }
779 unset($tmpmove);
780 }
781 }
782
783 return $error;
784 }
785
786
805 public function livraison($user, $fk_product, $entrepot_id, $qty, $price = 0, $label = '', $datem = '', $eatby = '', $sellby = '', $batch = '', $id_product_batch = 0, $inventorycode = '', $donotcleanemptylines = 0)
806 {
807 global $conf;
808
809 $skip_batch = empty($conf->productbatch->enabled);
810
811 return $this->_create($user, $fk_product, $entrepot_id, (0 - $qty), 2, $price, $label, $inventorycode, $datem, $eatby, $sellby, $batch, $skip_batch, $id_product_batch, 0, $donotcleanemptylines);
812 }
813
832 public function reception($user, $fk_product, $entrepot_id, $qty, $price = 0, $label = '', $eatby = '', $sellby = '', $batch = '', $datem = '', $id_product_batch = 0, $inventorycode = '', $donotcleanemptylines = 0)
833 {
834 global $conf;
835
836 $skip_batch = empty($conf->productbatch->enabled);
837
838 return $this->_create($user, $fk_product, $entrepot_id, $qty, 3, $price, $label, $inventorycode, $datem, $eatby, $sellby, $batch, $skip_batch, $id_product_batch, 0, $donotcleanemptylines);
839 }
840
848 public function calculateBalanceForProductBefore($productidselected, $datebefore)
849 {
850 $nb = 0;
851
852 $sql = "SELECT SUM(value) as nb from ".$this->db->prefix()."stock_mouvement";
853 $sql .= " WHERE fk_product = ".((int) $productidselected);
854 $sql .= " AND datem < '".$this->db->idate($datebefore)."'";
855
856 dol_syslog(get_class($this).__METHOD__, LOG_DEBUG);
857 $resql = $this->db->query($sql);
858 if ($resql) {
859 $obj = $this->db->fetch_object($resql);
860 if ($obj) $nb = $obj->nb;
861 return (empty($nb) ? 0 : $nb);
862 } else {
863 dol_print_error($this->db);
864 return -1;
865 }
866 }
867
877 private function createBatch($dluo, $qty)
878 {
879 global $user, $langs;
880
881 $langs->load('productbatch');
882
883 $pdluo = new Productbatch($this->db);
884
885 $result = 0;
886
887 // Try to find an existing record with same batch number or id
888 if (is_numeric($dluo)) {
889 $result = $pdluo->fetch($dluo);
890 if (empty($pdluo->id)) {
891 // We didn't find the line. May be it was deleted before by a previous move in same transaction.
892 $this->error = $langs->trans('CantMoveNonExistantSerial');
893 $this->errors[] = $this->error;
894 $result = -2;
895 }
896 } elseif (is_array($dluo)) {
897 if (isset($dluo['fk_product_stock'])) {
898 $vfk_product_stock = $dluo['fk_product_stock'];
899 $vbatchnumber = $dluo['batchnumber'];
900
901 $result = $pdluo->find($vfk_product_stock, '', '', $vbatchnumber); // Search on batch number only (eatby and sellby are deprecated here)
902 } else {
903 dol_syslog(get_class($this)."::createBatch array param dluo must contain at least key fk_product_stock", LOG_ERR);
904 $result = -1;
905 }
906 } else {
907 dol_syslog(get_class($this)."::createBatch error invalid param dluo", LOG_ERR);
908 $result = -1;
909 }
910
911 if ($result >= 0) {
912 // No error
913 if ($pdluo->id > 0) { // product_batch record found
914 //print "Avant ".$pdluo->qty." Apres ".($pdluo->qty + $qty)."<br>";
915 $pdluo->qty += $qty;
916 if ($pdluo->qty == 0) {
917 $result = $pdluo->delete($user, 1);
918 } else {
919 $result = $pdluo->update($user, 1);
920 }
921 } else { // product_batch record not found
922 $pdluo->fk_product_stock = $vfk_product_stock;
923 $pdluo->qty = $qty;
924 $pdluo->eatby = empty($dluo['eatby']) ? '' : $dluo['eatby']; // No more used. Now eatby date is store in table of lot, no more into prouct_batch table.
925 $pdluo->sellby = empty($dluo['sellby']) ? '' : $dluo['sellby']; // No more used. Now sellby date is store in table of lot, no more into prouct_batch table.
926 $pdluo->batch = $vbatchnumber;
927
928 $result = $pdluo->create($user, 1);
929 if ($result < 0) {
930 $this->error = $pdluo->error;
931 $this->errors = $pdluo->errors;
932 }
933 }
934 }
935
936 return $result;
937 }
938
939 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
947 public function get_origin($origin_id, $origin_type)
948 {
949 // phpcs:enable
950 $origin = '';
951
952 switch ($origin_type) {
953 case 'commande':
954 require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
955 $origin = new Commande($this->db);
956 break;
957 case 'shipping':
958 require_once DOL_DOCUMENT_ROOT.'/expedition/class/expedition.class.php';
959 $origin = new Expedition($this->db);
960 break;
961 case 'facture':
962 require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
963 $origin = new Facture($this->db);
964 break;
965 case 'order_supplier':
966 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
967 $origin = new CommandeFournisseur($this->db);
968 break;
969 case 'invoice_supplier':
970 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php';
971 $origin = new FactureFournisseur($this->db);
972 break;
973 case 'project':
974 require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
975 $origin = new Project($this->db);
976 break;
977 case 'mo':
978 require_once DOL_DOCUMENT_ROOT.'/mrp/class/mo.class.php';
979 $origin = new Mo($this->db);
980 break;
981 case 'user':
982 require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php';
983 $origin = new User($this->db);
984 break;
985 case 'reception':
986 require_once DOL_DOCUMENT_ROOT.'/reception/class/reception.class.php';
987 $origin = new Reception($this->db);
988 break;
989 case 'inventory':
990 require_once DOL_DOCUMENT_ROOT.'/product/inventory/class/inventory.class.php';
991 $origin = new Inventory($this->db);
992 break;
993 default:
994 if ($origin_type) {
995 // Separate originetype with "@" : left part is class name, right part is module name
996 $origin_type_array = explode('@', $origin_type);
997 $classname = $origin_type_array[0];
998 $modulename = empty($origin_type_array[1]) ? strtolower($classname) : $origin_type_array[1];
999
1000 $result = dol_include_once('/'.$modulename.'/class/'.$classname.'.class.php');
1001
1002 if ($result) {
1003 $classname = ucfirst($classname);
1004 $origin = new $classname($this->db);
1005 }
1006 }
1007 break;
1008 }
1009
1010 if (empty($origin) || !is_object($origin)) {
1011 return '';
1012 }
1013
1014 if ($origin->fetch($origin_id) > 0) {
1015 return $origin->getNomUrl(1);
1016 }
1017
1018 return '';
1019 }
1020
1031 public function setOrigin($origin_element, $origin_id, $line_id_object_src = 0, $line_id_object_origin = 0)
1032 {
1033 $this->origin_type = $origin_element;
1034 $this->origin_id = $origin_id;
1035 $this->line_id_object_src = $line_id_object_src;
1036 $this->line_id_object_origin = $line_id_object_origin;
1037 // For backward compatibility
1038 $this->origintype = $origin_element;
1039 $this->fk_origin = $origin_id;
1040 }
1041
1042
1050 public function initAsSpecimen()
1051 {
1052 global $user, $langs, $conf, $mysoc;
1053
1054 // Initialize parameters
1055 $this->id = 0;
1056
1057 // There is no specific properties. All data into insert are provided as method parameter.
1058 }
1059
1066 public function getTypeMovement($withlabel = 0)
1067 {
1068 global $langs;
1069
1070 $s = '';
1071 switch ($this->type) {
1072 case "0":
1073 $s = '<span class="fa fa-level-down-alt stockmovemententry stockmovementtransfer" title="'.$langs->trans('StockIncreaseAfterCorrectTransfer').'"></span>';
1074 if ($withlabel) {
1075 $s .= $langs->trans('StockIncreaseAfterCorrectTransfer');
1076 }
1077 break;
1078 case "1":
1079 $s = '<span class="fa fa-level-up-alt stockmovementexit stockmovementtransfer" title="'.$langs->trans('StockDecreaseAfterCorrectTransfer').'"></span>';
1080 if ($withlabel) {
1081 $s .= $langs->trans('StockDecreaseAfterCorrectTransfer');
1082 }
1083 break;
1084 case "2":
1085 $s = '<span class="fa fa-long-arrow-alt-up stockmovementexit stockmovement" title="'.$langs->trans('StockDecrease').'"></span>';
1086 if ($withlabel) {
1087 $s .= $langs->trans('StockDecrease');
1088 }
1089 break;
1090 case "3":
1091 $s = '<span class="fa fa-long-arrow-alt-down stockmovemententry stockmovement" title="'.$langs->trans('StockIncrease').'"></span>';
1092 if ($withlabel) {
1093 $s .= $langs->trans('StockIncrease');
1094 }
1095 break;
1096 }
1097
1098 return $s;
1099 }
1100
1112 public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $maxlen = 24, $morecss = '')
1113 {
1114 global $langs, $conf, $db;
1115
1116 $result = '';
1117
1118 $label = img_picto('', 'stock', 'class="pictofixedwidth"').'<u>'.$langs->trans("Movement").' '.$this->id.'</u>';
1119 $label .= '<div width="100%">';
1120 $label .= '<b>'.$langs->trans('Label').':</b> '.$this->label;
1121 $label .= '<br><b>'.$langs->trans('Qty').':</b> '.($this->qty > 0 ? '+' : '').$this->qty;
1122 if ($this->batch) {
1123 $label .= '<br><b>'.$langs->trans('Batch').':</b> '.$this->batch;
1124 }
1125 /* TODO Get also warehouse label in a property instead of id
1126 if ($this->warehouse_id > 0) {
1127 $label .= '<br><b>'.$langs->trans('Warehouse').':</b> '.$this->warehouse_id;
1128 }*/
1129 $label .= '</div>';
1130
1131 // Link to page of warehouse tab
1132 if ($option == 'movements') {
1133 $url = DOL_URL_ROOT.'/product/stock/movement_list.php?search_ref='.$this->id;
1134 } else {
1135 $url = DOL_URL_ROOT.'/product/stock/movement_list.php?id='.$this->warehouse_id.'&msid='.$this->id;
1136 }
1137
1138 $link = '<a href="'.$url.'"'.($notooltip ? '' : ' title="'.dol_escape_htmltag($label, 1).'" class="classfortooltip'.($morecss ? ' '.$morecss : '').'"');
1139 $link .= '>';
1140 $linkend = '</a>';
1141
1142 if ($withpicto) {
1143 $result .= ($link.img_object(($notooltip ? '' : $label), 'stock', ($notooltip ? '' : 'class="classfortooltip"')).$linkend);
1144 if ($withpicto != 2) {
1145 $result .= ' ';
1146 }
1147 }
1148 $result .= $link.$this->id.$linkend;
1149 return $result;
1150 }
1151
1158 public function getLibStatut($mode = 0)
1159 {
1160 return $this->LibStatut($mode);
1161 }
1162
1163 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1170 public function LibStatut($mode = 0)
1171 {
1172 // phpcs:enable
1173 global $langs;
1174
1175 if ($mode == 0 || $mode == 1) {
1176 return $langs->trans('StatusNotApplicable');
1177 } elseif ($mode == 2) {
1178 return img_picto($langs->trans('StatusNotApplicable'), 'statut9').' '.$langs->trans('StatusNotApplicable');
1179 } elseif ($mode == 3) {
1180 return img_picto($langs->trans('StatusNotApplicable'), 'statut9');
1181 } elseif ($mode == 4) {
1182 return img_picto($langs->trans('StatusNotApplicable'), 'statut9').' '.$langs->trans('StatusNotApplicable');
1183 } elseif ($mode == 5) {
1184 return $langs->trans('StatusNotApplicable').' '.img_picto($langs->trans('StatusNotApplicable'), 'statut9');
1185 }
1186 }
1187
1198 public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
1199 {
1200 global $conf, $user, $langs;
1201
1202 $langs->load("stocks");
1203 $outputlangs->load("products");
1204
1205 if (!dol_strlen($modele)) {
1206 $modele = 'stdmovement';
1207
1208 if ($this->model_pdf) {
1209 $modele = $this->model_pdf;
1210 } elseif (!empty($conf->global->MOUVEMENT_ADDON_PDF)) {
1211 $modele = $conf->global->MOUVEMENT_ADDON_PDF;
1212 }
1213 }
1214
1215 $modelpath = "core/modules/stock/doc/";
1216
1217 return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
1218 }
1219
1227 public function delete(User $user, $notrigger = false)
1228 {
1229 return $this->deleteCommon($user, $notrigger);
1230 //return $this->deleteCommon($user, $notrigger, 1);
1231 }
1232
1240 private function getBatchCount($fk_product, $batch)
1241 {
1242 $cpt = 0;
1243
1244 $sql = "SELECT sum(pb.qty) as cpt";
1245 $sql .= " FROM ".$this->db->prefix()."product_batch as pb";
1246 $sql .= " INNER JOIN ".$this->db->prefix()."product_stock as ps ON ps.rowid = pb.fk_product_stock";
1247 $sql .= " WHERE ps.fk_product = " . ((int) $fk_product);
1248 $sql .= " AND pb.batch = '" . $this->db->escape($batch) . "'";
1249
1250 $result = $this->db->query($sql);
1251 if ($result) {
1252 if ($this->db->num_rows($result)) {
1253 $obj = $this->db->fetch_object($result);
1254 $cpt = $obj->cpt;
1255 }
1256
1257 $this->db->free($result);
1258 } else {
1259 dol_print_error($this->db);
1260 return -1;
1261 }
1262
1263 return $cpt;
1264 }
1265}
Class to manage predefined suppliers products.
Class to manage customers orders.
Parent class of all other business classes (invoices, contracts, proposals, orders,...
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this->array_options This method is in most cases call...
commonGenerateDocument($modelspath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams=null)
Common function for all objects extending CommonObject for generating documents.
deleteCommon(User $user, $notrigger=false, $forcechilddeletion=0)
Delete object in database.
call_trigger($triggerName, $user)
Call trigger based on this instance.
Class to manage warehouses.
Class to manage shipments.
Class to manage suppliers invoices.
Class to manage invoices.
Class for Inventory.
Class for Mo.
Definition mo.class.php:34
Class to manage stock movements.
__construct($db)
Constructor.
generateDocument($modele, $outputlangs, $hidedetails=0, $hidedesc=0, $hideref=0)
Create object on disk.
getNomUrl($withpicto=0, $option='', $notooltip=0, $maxlen=24, $morecss='')
Return a link (with optionaly the picto) Use this->id,this->lastname, this->firstname.
setOrigin($origin_element, $origin_id, $line_id_object_src=0, $line_id_object_origin=0)
Set attribute origin_type and fk_origin to object.
LibStatut($mode=0)
Return the label of the status.
createBatch($dluo, $qty)
Create or update batch record (update table llx_product_batch).
getBatchCount($fk_product, $batch)
Retrieve number of equipments for a product lot/serial.
get_origin($origin_id, $origin_type)
Return Url link of origin object.
livraison($user, $fk_product, $entrepot_id, $qty, $price=0, $label='', $datem='', $eatby='', $sellby='', $batch='', $id_product_batch=0, $inventorycode='', $donotcleanemptylines=0)
Decrease stock for product and subproducts.
fetch($id)
Load object in memory from the database.
reception($user, $fk_product, $entrepot_id, $qty, $price=0, $label='', $eatby='', $sellby='', $batch='', $datem='', $id_product_batch=0, $inventorycode='', $donotcleanemptylines=0)
Increase stock for product and subproducts.
_createSubProduct($user, $idProduct, $entrepot_id, $qty, $type, $price=0, $label='', $inventorycode='', $datem='')
Create movement in database for all subproducts.
_create($user, $fk_product, $entrepot_id, $qty, $type, $price=0, $label='', $inventorycode='', $datem='', $eatby='', $sellby='', $batch='', $skip_batch=false, $id_product_batch=0, $disablestockchangeforsubproduct=0, $donotcleanemptylines=0)
Add a movement of stock (in one direction only).
initAsSpecimen()
Initialise an instance with random values.
getTypeMovement($withlabel=0)
Return html string with picto for type of movement.
calculateBalanceForProductBefore($productidselected, $datebefore)
Count number of product in stock before a specific date.
getLibStatut($mode=0)
Return label statut.
Class to manage products or services.
const TYPE_SERVICE
Service.
Manage record for batch number management.
Class with list of lots and properties.
Class to manage projects.
Class to manage receptions.
Class to manage Dolibarr users.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed informations (by default a local PHP server timestamp) Re...
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_print_error($db='', $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
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_print_date($time, $format='', $tzoutput='auto', $outputlangs='', $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
dol_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return dolibarr global constant int value.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
if(!function_exists( 'dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
dol_clone($object, $native=0)
Create a clone of instance of object (new instance with same value for each properties) With native =...
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
dol_getdate($timestamp, $fast=false, $forcetimezone='')
Return an array with locale date info.
if(preg_match('/crypted:/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
Definition repair.php:120