dolibarr  17.0.4
productbatch.class.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (C) 2007-2021 Laurent Destailleur <eldy@users.sourceforge.net>
3  * Copyright (C) 2013-2014 Cedric GROSS <c.gross@kreiz-it.fr>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <https://www.gnu.org/licenses/>.
17  */
18 
25 require_once DOL_DOCUMENT_ROOT."/core/class/commonobject.class.php";
26 
27 
32 {
37 
41  public $element = 'productbatch';
42 
43  private static $_table_element = 'product_batch';
44 
45  public $tms = '';
46  public $fk_product_stock;
47  public $sellby = ''; // dlc
48  public $eatby = ''; // dmd/dluo
49  public $batch = '';
50  public $qty;
51  public $warehouseid;
52 
56  public $fk_product;
57 
58 
59 
65  public function __construct($db)
66  {
67  $this->db = $db;
68  }
69 
70 
78  public function create($user, $notrigger = 0)
79  {
80  global $conf, $langs;
81  $error = 0;
82 
83  // Clean parameters
84  $this->cleanParam();
85 
86  // Check parameters
87  // Put here code to add control on parameters values
88 
89  // Insert request
90  $sql = "INSERT INTO ".$this->db->prefix()."product_batch (";
91  $sql .= "fk_product_stock,";
92  $sql .= "sellby,"; // no more used
93  $sql .= "eatby,"; // no more used
94  $sql .= "batch,";
95  $sql .= "qty,";
96  $sql .= "import_key";
97  $sql .= ") VALUES (";
98  $sql .= " ".(!isset($this->fk_product_stock) ? 'NULL' : $this->fk_product_stock).",";
99  $sql .= " ".(!isset($this->sellby) || dol_strlen($this->sellby) == 0 ? 'NULL' : "'".$this->db->idate($this->sellby)."'").","; // no more used
100  $sql .= " ".(!isset($this->eatby) || dol_strlen($this->eatby) == 0 ? 'NULL' : "'".$this->db->idate($this->eatby)."'").","; // no more used
101  $sql .= " ".(!isset($this->batch) ? 'NULL' : "'".$this->db->escape($this->batch)."'").",";
102  $sql .= " ".(!isset($this->qty) ? 'NULL' : $this->qty).",";
103  $sql .= " ".(!isset($this->import_key) ? 'NULL' : "'".$this->db->escape($this->import_key)."'")."";
104  $sql .= ")";
105 
106  $this->db->begin();
107 
108  dol_syslog(get_class($this)."::create", LOG_DEBUG);
109  $resql = $this->db->query($sql);
110  if (!$resql) {
111  $error++; $this->errors[] = "Error ".$this->db->lasterror();
112  }
113  if (!$error) {
114  $this->id = $this->db->last_insert_id($this->db->prefix().self::$_table_element);
115  }
116 
117  // Commit or rollback
118  if ($error) {
119  $this->db->rollback();
120  return -1 * $error;
121  } else {
122  $this->db->commit();
123  return $this->id;
124  }
125  }
126 
127 
134  public function fetch($id)
135  {
136  global $langs;
137  $sql = "SELECT";
138  $sql .= " t.rowid,";
139  $sql .= " t.tms,";
140  $sql .= " t.fk_product_stock,";
141  $sql .= " t.sellby as oldsellby,";
142  $sql .= " t.eatby as oldeatby,";
143  $sql .= " t.batch,";
144  $sql .= " t.qty,";
145  $sql .= " t.import_key,";
146  $sql .= " w.fk_entrepot,";
147  $sql .= " w.fk_product,";
148  $sql .= " pl.eatby,";
149  $sql .= " pl.sellby";
150  $sql .= " FROM ".$this->db->prefix()."product_batch as t";
151  $sql .= " INNER JOIN ".$this->db->prefix()."product_stock w on t.fk_product_stock = w.rowid"; // llx_product_stock is a parent table so this link does NOT generate duplicate record
152  $sql .= " LEFT JOIN ".$this->db->prefix()."product_lot as pl on pl.fk_product = w.fk_product and pl.batch = t.batch";
153  $sql .= " WHERE t.rowid = ".((int) $id);
154 
155  dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
156  $resql = $this->db->query($sql);
157  if ($resql) {
158  if ($this->db->num_rows($resql)) {
159  $obj = $this->db->fetch_object($resql);
160 
161  $this->id = $obj->rowid;
162  $this->tms = $this->db->jdate($obj->tms);
163  $this->fk_product_stock = $obj->fk_product_stock;
164  $this->sellby = $this->db->jdate($obj->sellby ? $obj->sellby : $obj->oldsellby);
165  $this->eatby = $this->db->jdate($obj->eatby ? $obj->eatby : $obj->oldeatby);
166  $this->batch = $obj->batch;
167  $this->qty = $obj->qty;
168  $this->import_key = $obj->import_key;
169  $this->warehouseid = $obj->fk_entrepot;
170  $this->fk_product = $obj->fk_product;
171  }
172  $this->db->free($resql);
173 
174  return 1;
175  } else {
176  $this->error = "Error ".$this->db->lasterror();
177  return -1;
178  }
179  }
180 
188  public function update($user = null, $notrigger = 0)
189  {
190  global $conf, $langs;
191  $error = 0;
192 
193  // Clean parameters
194  $this->cleanParam();
195 
196  // TODO Check qty is ok for stock move. Negative may not be allowed.
197  if ($this->qty < 0) {
198  }
199 
200  // Update request
201  $sql = "UPDATE ".$this->db->prefix().self::$_table_element." SET";
202  $sql .= " fk_product_stock=".(isset($this->fk_product_stock) ? $this->fk_product_stock : "null").",";
203  $sql .= " sellby=".(dol_strlen($this->sellby) != 0 ? "'".$this->db->idate($this->sellby)."'" : 'null').",";
204  $sql .= " eatby=".(dol_strlen($this->eatby) != 0 ? "'".$this->db->idate($this->eatby)."'" : 'null').",";
205  $sql .= " batch=".(isset($this->batch) ? "'".$this->db->escape($this->batch)."'" : "null").",";
206  $sql .= " qty=".(isset($this->qty) ? $this->qty : "null").",";
207  $sql .= " import_key=".(isset($this->import_key) ? "'".$this->db->escape($this->import_key)."'" : "null")."";
208  $sql .= " WHERE rowid=".((int) $this->id);
209 
210  $this->db->begin();
211 
212  dol_syslog(get_class($this)."::update", LOG_DEBUG);
213  $resql = $this->db->query($sql);
214  if (!$resql) {
215  $error++; $this->errors[] = "Error ".$this->db->lasterror();
216  }
217 
218  // Commit or rollback
219  if ($error) {
220  foreach ($this->errors as $errmsg) {
221  dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
222  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
223  }
224  $this->db->rollback();
225  return -1 * $error;
226  } else {
227  $this->db->commit();
228  return 1;
229  }
230  }
231 
239  public function delete($user, $notrigger = 0)
240  {
241  global $conf, $langs;
242  $error = 0;
243 
244  $this->db->begin();
245 
246  if (!$error) {
247  $sql = "DELETE FROM ".$this->db->prefix().self::$_table_element."";
248  $sql .= " WHERE rowid=".((int) $this->id);
249 
250  dol_syslog(get_class($this)."::delete", LOG_DEBUG);
251  $resql = $this->db->query($sql);
252  if (!$resql) {
253  $error++; $this->errors[] = "Error ".$this->db->lasterror();
254  }
255  }
256 
257  // Commit or rollback
258  if ($error) {
259  foreach ($this->errors as $errmsg) {
260  dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
261  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
262  }
263  $this->db->rollback();
264  return -1 * $error;
265  } else {
266  $this->db->commit();
267  return 1;
268  }
269  }
270 
271 
272 
280  public function createFromClone(User $user, $fromid)
281  {
282  $error = 0;
283 
284  $object = new Productbatch($this->db);
285 
286  $this->db->begin();
287 
288  // Load source object
289  $object->fetch($fromid);
290  $object->id = 0;
291  $object->statut = 0;
292 
293  // Clear fields
294  // ...
295 
296  // Create clone
297  $object->context['createfromclone'] = 'createfromclone';
298  $result = $object->create($user);
299 
300  // Other options
301  if ($result < 0) {
302  $this->error = $object->error;
303  $this->errors = array_merge($this->errors, $object->errors);
304  $error++;
305  }
306 
307  if (!$error) {
308  }
309 
310  unset($object->context['createfromclone']);
311 
312  // End
313  if (!$error) {
314  $this->db->commit();
315  return $object->id;
316  } else {
317  $this->db->rollback();
318  return -1;
319  }
320  }
321 
322 
329  public function initAsSpecimen()
330  {
331  $this->id = 0;
332 
333  $this->tms = '';
334  $this->fk_product_stock = '';
335  $this->sellby = '';
336  $this->eatby = '';
337  $this->batch = '';
338  $this->import_key = '';
339  }
340 
346  private function cleanParam()
347  {
348  if (isset($this->fk_product_stock)) {
349  $this->fk_product_stock = (int) trim($this->fk_product_stock);
350  }
351  if (isset($this->batch)) {
352  $this->batch = trim($this->batch);
353  }
354  if (isset($this->qty)) {
355  $this->qty = (float) trim($this->qty);
356  }
357  if (isset($this->import_key)) {
358  $this->import_key = trim($this->import_key);
359  }
360  }
361 
371  public function find($fk_product_stock = 0, $eatby = '', $sellby = '', $batch_number = '')
372  {
373  global $langs;
374 
375  $where = array();
376  $sql = "SELECT";
377  $sql .= " t.rowid,";
378  $sql .= " t.tms,";
379  $sql .= " t.fk_product_stock,";
380  $sql .= " t.sellby,"; // deprecated
381  $sql .= " t.eatby,"; // deprecated
382  $sql .= " t.batch,";
383  $sql .= " t.qty,";
384  $sql .= " t.import_key";
385  $sql .= " FROM ".$this->db->prefix().self::$_table_element." as t";
386  $sql .= " WHERE fk_product_stock=".((int) $fk_product_stock);
387 
388  if (!empty($eatby)) {
389  array_push($where, " eatby = '".$this->db->idate($eatby)."'"); // deprecated
390  }
391  if (!empty($sellby)) {
392  array_push($where, " sellby = '".$this->db->idate($sellby)."'"); // deprecated
393  }
394 
395  if (!empty($batch_number)) {
396  $sql .= " AND batch = '".$this->db->escape($batch_number)."'";
397  }
398 
399  if (!empty($where)) {
400  $sql .= " AND (".implode(" OR ", $where).")";
401  }
402 
403  dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
404  $resql = $this->db->query($sql);
405  if ($resql) {
406  if ($this->db->num_rows($resql)) {
407  $obj = $this->db->fetch_object($resql);
408 
409  $this->id = $obj->rowid;
410 
411  $this->tms = $this->db->jdate($obj->tms);
412  $this->fk_product_stock = $obj->fk_product_stock;
413  $this->sellby = $this->db->jdate($obj->sellby);
414  $this->eatby = $this->db->jdate($obj->eatby);
415  $this->batch = $obj->batch;
416  $this->qty = $obj->qty;
417  $this->import_key = $obj->import_key;
418  }
419  $this->db->free($resql);
420 
421  return 1;
422  } else {
423  $this->error = "Error ".$this->db->lasterror();
424  return -1;
425  }
426  }
436  public static function findAll($dbs, $fk_product_stock, $with_qty = 0, $fk_product = 0)
437  {
438  global $conf;
439 
440  $ret = array();
441 
442  $sql = "SELECT";
443  $sql .= " t.rowid,";
444  $sql .= " t.tms,";
445  $sql .= " t.fk_product_stock,";
446  $sql .= " t.sellby as oldsellby,"; // deprecated but may not be migrated into new table
447  $sql .= " t.eatby as oldeatby,"; // deprecated but may not be migrated into new table
448  $sql .= " t.batch,";
449  $sql .= " t.qty,";
450  $sql .= " t.import_key";
451  if ($fk_product > 0) {
452  $sql .= ", pl.rowid as lotid, pl.eatby as eatby, pl.sellby as sellby";
453  // TODO May add extrafields to ?
454  }
455  $sql .= " FROM ".$dbs->prefix()."product_batch as t";
456  if ($fk_product > 0) {
457  $sql .= " LEFT JOIN ".$dbs->prefix()."product_lot as pl ON pl.fk_product = ".((int) $fk_product)." AND pl.batch = t.batch";
458  // TODO May add extrafields to ?
459  }
460  $sql .= " WHERE fk_product_stock=".((int) $fk_product_stock);
461  if ($with_qty) {
462  $sql .= " AND t.qty <> 0";
463  }
464 
465  $sql .= " ORDER BY ";
466  // TODO : use product lifo and fifo when product will implement it
467  if ($fk_product > 0) { $sql .= "pl.eatby ASC, pl.sellby ASC, "; }
468  $sql .= "t.eatby ASC, t.sellby ASC ";
469  $sql .= ", t.qty ".(!empty($conf->global->DO_NOT_TRY_TO_DEFRAGMENT_STOCKS_WAREHOUSE)?'DESC':'ASC'); // Note : qty ASC is important for expedition card, to avoid stock fragmentation
470 
471  dol_syslog("productbatch::findAll", LOG_DEBUG);
472  $resql = $dbs->query($sql);
473  if ($resql) {
474  $num = $dbs->num_rows($resql);
475  $i = 0;
476  while ($i < $num) {
477  $obj = $dbs->fetch_object($resql);
478 
479  $tmp = new Productbatch($dbs);
480  $tmp->id = $obj->rowid;
481  $tmp->lotid = $obj->lotid;
482  $tmp->tms = $dbs->jdate($obj->tms);
483  $tmp->fk_product_stock = $obj->fk_product_stock;
484  $tmp->sellby = $dbs->jdate($obj->sellby ? $obj->sellby : $obj->oldsellby);
485  $tmp->eatby = $dbs->jdate($obj->eatby ? $obj->eatby : $obj->oldeatby);
486  $tmp->batch = $obj->batch;
487  $tmp->qty = $obj->qty;
488  $tmp->import_key = $obj->import_key;
489 
490  $ret[$tmp->batch] = $tmp; // $ret is for a $fk_product_stock and unique key is on $fk_product_stock+batch
491  $i++;
492  }
493  $dbs->free($resql);
494 
495  return $ret;
496  } else {
497  $error = "Error ".$dbs->lasterror();
498  return -1;
499  }
500  }
501 
514  public function findAllForProduct($fk_product, $fk_warehouse = 0, $qty_min = null, $sortfield = null, $sortorder = null)
515  {
516  $productBatchList = array();
517 
518  dol_syslog(__METHOD__.' fk_product='.$fk_product.', fk_warehouse='.$fk_warehouse.', qty_min='.$qty_min.', sortfield='.$sortfield.', sortorder='.$sortorder, LOG_DEBUG);
519 
520  $sql = "SELECT";
521  $sql .= " pl.rowid";
522  $sql .= ", pl.fk_product";
523  $sql .= ", pl.batch";
524  $sql .= ", pl.sellby";
525  $sql .= ", pl.eatby";
526  $sql .= ", pb.qty";
527  $sql .= " FROM ".$this->db->prefix()."product_lot as pl";
528  $sql .= " LEFT JOIN ".$this->db->prefix()."product as p ON p.rowid = pl.fk_product";
529  $sql .= " LEFT JOIN ".$this->db->prefix()."product_batch AS pb ON pl.batch = pb.batch";
530  $sql .= " LEFT JOIN ".$this->db->prefix()."product_stock AS ps ON ps.rowid = pb.fk_product_stock AND ps.fk_product = ".((int) $fk_product);
531  $sql .= " WHERE p.entity IN (".getEntity('product').")";
532  $sql .= " AND pl.fk_product = ".((int) $fk_product);
533  if ($fk_warehouse > 0) {
534  $sql .= " AND ps.fk_entrepot = ".((int) $fk_warehouse);
535  }
536  if ($qty_min !== null) {
537  $sql .= " AND pb.qty > ".((float) price2num($qty_min, 'MS'));
538  }
539  $sql .= $this->db->order($sortfield, $sortorder);
540 
541  $resql = $this->db->query($sql);
542  if ($resql) {
543  while ($obj = $this->db->fetch_object($resql)) {
544  $productBatch = new self($this->db);
545  $productBatch->id = $obj->rowid;
546  $productBatch->fk_product = $obj->fk_product;
547  $productBatch->batch = $obj->batch;
548  $productBatch->eatby = $this->db->jdate($obj->eatby);
549  $productBatch->sellby = $this->db->jdate($obj->sellby);
550  $productBatch->qty = $obj->qty;
551  $productBatchList[] = $productBatch;
552  }
553  $this->db->free($resql);
554 
555  return $productBatchList;
556  } else {
557  dol_syslog(__METHOD__.' Error: '.$this->db->lasterror(), LOG_ERR);
558  return -1;
559  }
560  }
561 }
Parent class of all other business classes (invoices, contracts, proposals, orders,...
Manage record for batch number management.
update($user=null, $notrigger=0)
Update object into database.
find($fk_product_stock=0, $eatby='', $sellby='', $batch_number='')
Find first detail record that match eather eat-by or sell-by or batch within given warehouse.
create($user, $notrigger=0)
Create object into database.
fetch($id)
Load object in memory from the database.
cleanParam()
Clean fields (triming)
createFromClone(User $user, $fromid)
Load an object from its id and create a new one in database.
const BATCH_RULE_SELLBY_EATBY_DATES_FIRST
Batches rules.
static $_table_element
Name of table without prefix where object is stored.
static findAll($dbs, $fk_product_stock, $with_qty=0, $fk_product=0)
Return all batch detail records for a given product and warehouse.
findAllForProduct($fk_product, $fk_warehouse=0, $qty_min=null, $sortfield=null, $sortorder=null)
Return all batch for a product and a warehouse.
__construct($db)
Constructor.
initAsSpecimen()
Initialise object with example values Id must be 0 if object instance is a specimen.
Class to manage Dolibarr users.
Definition: user.class.php:47
if(isModEnabled('facture') &&!empty($user->rights->facture->lire)) if((isModEnabled('fournisseur') &&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD) && $user->hasRight("fournisseur", "facture", "lire"))||(isModEnabled('supplier_invoice') && $user->hasRight("supplier_invoice", "lire"))) if(isModEnabled('don') &&!empty($user->rights->don->lire)) if(isModEnabled('tax') &&!empty($user->rights->tax->charges->lire)) if(isModEnabled('facture') &&isModEnabled('commande') && $user->hasRight("commande", "lire") &&empty($conf->global->WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER)) $resql
Social contributions to pay.
Definition: index.php:745
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
div float
Buy price without taxes.
Definition: style.css.php:913
$conf db
API class for accounts.
Definition: inc.php:41