dolibarr 24.0.0-beta
api_warehouses.class.php
1<?php
2/* Copyright (C) 2016 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2025 MDW <mdeweerd@users.noreply.github.com>
4 * Copyright (C) 2025 William Mead <william@m34d.com>
5 * Copyright (C) 2025 Frédéric France <frederic.france@free.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
21use Luracast\Restler\RestException;
22
23require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php';
24require_once DOL_DOCUMENT_ROOT.'/product/class/api_products.class.php';
25
35{
39 public static $FIELDS = array(
40 'label',
41 );
42
46 public $warehouse;
47
51 public function __construct()
52 {
53 global $db;
54 $this->db = $db;
55 $this->warehouse = new Entrepot($this->db);
56 }
57
74 public function get($id)
75 {
76 if (!DolibarrApiAccess::$user->hasRight('stock', 'lire')) {
77 throw new RestException(403);
78 }
79 if ($id == 0) {
80 throw new RestException(400, 'No warehouse with id=0 can exist');
81 }
82 $result = $this->warehouse->fetch($id);
83 if (!$result) {
84 throw new RestException(404, 'warehouse not found');
85 }
86
87 if (!DolibarrApi::_checkAccessToResource('stock', $this->warehouse->id, 'entrepot')) {
88 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
89 }
90
91 return $this->_cleanObjectDatas($this->warehouse);
92 }
93
120 public function index($sortfield = "t.rowid", $sortorder = 'ASC', $limit = 100, $page = 0, $category = 0, $sqlfilters = '', $properties = '', $pagination_data = false)
121 {
122 $obj_ret = array();
123
124 if (!DolibarrApiAccess::$user->hasRight('stock', 'lire')) {
125 throw new RestException(403);
126 }
127
128 $sql = "SELECT t.rowid";
129 $sql .= " FROM ".MAIN_DB_PREFIX."entrepot AS t LEFT JOIN ".MAIN_DB_PREFIX."entrepot_extrafields AS ef ON (ef.fk_object = t.rowid)"; // Modification VMR Global Solutions to include extrafields as search parameters in the API GET call, so we will be able to filter on extrafields
130 if ($category > 0) {
131 $sql .= ", ".$this->db->prefix()."categorie_warehouse as c";
132 }
133 $sql .= ' WHERE t.entity IN ('.getEntity('stock').')';
134 // Select warehouses of given category
135 if ($category > 0) {
136 $sql .= " AND c.fk_categorie = ".((int) $category);
137 $sql .= " AND c.fk_warehouse = t.rowid ";
138 }
139 // Add sql filters
140 if ($sqlfilters) {
141 $errormessage = '';
142 $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage);
143 if ($errormessage) {
144 throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage);
145 }
146 }
147
148 //this query will return total warehouses with the filters given
149 $sqlTotals = str_replace('SELECT t.rowid', 'SELECT count(t.rowid) as total', $sql);
150
151 $sql .= $this->db->order($sortfield, $sortorder);
152 if ($limit) {
153 if ($page < 0) {
154 $page = 0;
155 }
156 $offset = $limit * $page;
157
158 $sql .= $this->db->plimit($limit + 1, $offset);
159 }
160
161 $result = $this->db->query($sql);
162 if ($result) {
163 $i = 0;
164 $num = $this->db->num_rows($result);
165 $min = min($num, ($limit <= 0 ? $num : $limit));
166 while ($i < $min) {
167 $obj = $this->db->fetch_object($result);
168 $warehouse_static = new Entrepot($this->db);
169 if ($warehouse_static->fetch($obj->rowid)) {
170 $obj_ret[] = $this->_filterObjectProperties($this->_cleanObjectDatas($warehouse_static), $properties);
171 }
172 $i++;
173 }
174 } else {
175 throw new RestException(503, 'Error when retrieve warehouse list : '.$this->db->lasterror());
176 }
177
178 //if $pagination_data is true the response will contain element data with all values and element pagination with pagination data(total,page,limit)
179 if ($pagination_data) {
180 $totalsResult = $this->db->query($sqlTotals);
181 $total = $this->db->fetch_object($totalsResult)->total;
182
183 $tmp = $obj_ret;
184 $obj_ret = [];
185
186 $obj_ret['data'] = $tmp;
187 $obj_ret['pagination'] = [
188 'total' => (int) $total,
189 'page' => $page, //count starts from 0
190 'page_count' => ceil((int) $total / $limit),
191 'limit' => $limit
192 ];
193 }
194
195 return $obj_ret;
196 }
197
198
216 public function post($request_data = null)
217 {
218 if (!DolibarrApiAccess::$user->hasRight('stock', 'creer')) {
219 throw new RestException(403);
220 }
221
222 // Check mandatory fields
223 $result = $this->_validate($request_data);
224
225 foreach ($request_data as $field => $value) {
226 if ($field === 'caller') {
227 // Add a mention of caller so on trigger called after action, we can filter to avoid a loop if we try to sync back again with the caller
228 $this->warehouse->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
229 continue;
230 }
231
232 if ($field == 'id' || $field == 'warehouse_id') {
233 throw new RestException(400, 'Creating with id field is forbidden');
234 }
235 if ($field == 'entity' && $value != $this->warehouse->entity) {
236 throw new RestException(403, 'Changing entity of a user using the APIs is not possible');
237 }
238
239 $this->warehouse->$field = $this->_checkValForAPI($field, $value, $this->warehouse);
240 }
241 if ($this->warehouse->create(DolibarrApiAccess::$user) < 0) {
242 throw new RestException(500, "Error creating warehouse", array_merge(array($this->warehouse->error), $this->warehouse->errors));
243 }
244 return $this->warehouse->id;
245 }
246
266 public function put($id, $request_data = null)
267 {
268 if (!DolibarrApiAccess::$user->hasRight('stock', 'creer')) {
269 throw new RestException(403);
270 }
271 if ($id == 0) {
272 throw new RestException(400, 'No warehouse with id=0 can exist');
273 }
274 $result = $this->warehouse->fetch($id);
275 if (!$result) {
276 throw new RestException(404, 'warehouse not found');
277 }
278
279 if (!DolibarrApi::_checkAccessToResource('stock', $this->warehouse->id, 'entrepot')) {
280 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
281 }
282
283 foreach ($request_data as $field => $value) {
284 if ($field == 'id' || $field == 'warehouse_id') {
285 throw new RestException(400, 'Updating with id field is forbidden');
286 }
287 if ($field == 'entity' && $value != $this->warehouse->entity) {
288 throw new RestException(403, 'Changing entity of a user using the APIs is not possible');
289 }
290 if ($field == 'ref') {
291 throw new RestException(400, 'Deprecated, use label, not ref');
292 }
293
294 if ($field === 'caller') {
295 // Add a mention of caller so on trigger called after action, we can filter to avoid a loop if we try to sync back again with the caller
296 $this->warehouse->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
297 continue;
298 }
299
300 if ($field == 'array_options' && is_array($value)) {
301 foreach ($value as $index => $val) {
302 $this->warehouse->array_options[$index] = $this->_checkValExtrafieldsForAPI($index, $val, $this->warehouse);
303 }
304 continue;
305 }
306
307 $this->warehouse->$field = $this->_checkValForAPI($field, $value, $this->warehouse);
308 }
309
310 $updateresult = $this->warehouse->update($id, DolibarrApiAccess::$user);
311 if ($updateresult > 0) {
312 return $this->get($id);
313 } else {
314 throw new RestException(500, $this->warehouse->error);
315 }
316 }
317
336 public function delete($id)
337 {
338 if (!DolibarrApiAccess::$user->hasRight('stock', 'supprimer')) {
339 throw new RestException(403);
340 }
341 if ($id == 0) {
342 throw new RestException(400, 'No warehouse with id=0 can exist');
343 }
344 $result = $this->warehouse->fetch($id);
345 if (!$result) {
346 throw new RestException(404, 'warehouse not found');
347 }
348
349 if (!DolibarrApi::_checkAccessToResource('stock', $this->warehouse->id, 'entrepot')) {
350 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
351 }
352
353 if (!$this->warehouse->delete(DolibarrApiAccess::$user)) {
354 throw new RestException(500, 'error when delete warehouse');
355 }
356
357 return array(
358 'success' => array(
359 'code' => 200,
360 'message' => 'Warehouse deleted'
361 )
362 );
363 }
364
396 public function listProducts($id = 0, $sortfield = "t.rowid", $sortorder = 'ASC', $limit = 100, $page = 0, $includestockdata = 0, $includesubproducts = false, $includeparentid = false, $includetrans = false, $properties = '', $pagination_data = false)
397 {
398 if (!DolibarrApiAccess::$user->hasRight('stock', 'lire')) {
399 throw new RestException(403);
400 }
401 if ((int) $id == 0) {
402 throw new RestException(400, 'No warehouse with id=0 can exist');
403 }
404 $existsresult = $this->warehouse->fetch($id);
405 if (!$existsresult) {
406 throw new RestException(404, 'warehouse not found');
407 }
408 $obj_ret = array();
409
410 $sql = "SELECT t.rowid FROM ".MAIN_DB_PREFIX."product_stock as ps";
411 $sql.= " INNER JOIN ".MAIN_DB_PREFIX."product as t ON ps.fk_product = t.rowid";
412 $sql.= " WHERE ps.fk_entrepot =".((int) $id);
413 $sql.= " AND t.entity IN (".getEntity('stock').")";
414
415 //this query will return total warehouses with the filters given
416 $sqlTotals = str_replace('SELECT t.rowid', 'SELECT count(t.rowid) as total', $sql);
417
418 $sql .= $this->db->order($sortfield, $sortorder);
419 if ($limit) {
420 if ($page < 0) {
421 $page = 0;
422 }
423 $offset = $limit * $page;
424
425 $sql .= $this->db->plimit($limit + 1, $offset);
426 }
427
428 $result = $this->db->query($sql);
429 if ($result) {
430 $i = 0;
431 $num = $this->db->num_rows($result);
432 while ($i < $num) {
433 $obj = $this->db->fetch_object($result);
434 $api_products_static = new Products();
435 if ($api_products_static->get($obj->rowid, $includestockdata, $includesubproducts, $includeparentid, $includetrans)) {
436 $obj_ret[] = $this->_filterObjectProperties($api_products_static->product, $properties);
437 }
438 $i++;
439 }
440 } else {
441 throw new RestException(500, 'Error when retrieve warehouse product list : '.$this->db->lasterror());
442 }
443
444 //if $pagination_data is true the response will contain element data with all values and element pagination with pagination data(total,page,limit)
445 if ($pagination_data) {
446 $totalsResult = $this->db->query($sqlTotals);
447 $total = $this->db->fetch_object($totalsResult)->total;
448
449 $tmp = $obj_ret;
450 $obj_ret = [];
451
452 $obj_ret['data'] = $tmp;
453 $obj_ret['pagination'] = [
454 'total' => (int) $total,
455 'page' => $page, //count starts from 0
456 'page_count' => ceil((int) $total / $limit),
457 'limit' => $limit
458 ];
459 }
460
461 return $obj_ret;
462 }
463
464 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
474 protected function _cleanObjectDatas($object)
475 {
476 // phpcs:enable
477 $object = parent::_cleanObjectDatas($object);
478
479 unset($object->actiontypecode);
480 unset($object->canvas);
481 unset($object->civility_code);
482 unset($object->civility_id);
483 unset($object->cond_reglement_id);
484 unset($object->cond_reglement_supplier_id);
485 unset($object->contact_id);
486 unset($object->contacts_ids);
487 unset($object->contacts_ids_internal);
488 unset($object->country_code);
489 unset($object->date_cloture);
490 unset($object->date_validation);
491 unset($object->demand_reason_id);
492 unset($object->deposit_percent);
493 unset($object->extraparams);
494 unset($object->firstname);
495 unset($object->fk_account);
496 unset($object->fk_multicurrency);
497 unset($object->fk_user_creat);
498 unset($object->fk_user_modif);
499 unset($object->last_main_doc);
500 unset($object->lastname);
501 unset($object->libelle);
502 unset($object->lines);
503 unset($object->linkedObjectsIds);
504 unset($object->mode_reglement_id);
505 unset($object->module);
506 unset($object->multicurrency_code);
507 unset($object->multicurrency_total_ht);
508 unset($object->multicurrency_total_localtax1);
509 unset($object->multicurrency_total_localtax2);
510 unset($object->multicurrency_total_ttc);
511 unset($object->multicurrency_total_tva);
512 unset($object->multicurrency_tx);
513 unset($object->name);
514 unset($object->nb_rights);
515 unset($object->note_private);
516 unset($object->note_public);
517 unset($object->origin_id);
518 unset($object->origin_type);
519 unset($object->product);
520 unset($object->ref_ext);
521 unset($object->region_id);
522 unset($object->retained_warranty_fk_cond_reglement);
523 unset($object->shipping_method);
524 unset($object->shipping_method_id);
525 unset($object->specimen);
526 unset($object->state_id);
527 unset($object->tms);
528 unset($object->total_ht);
529 unset($object->total_localtax1);
530 unset($object->total_localtax2);
531 unset($object->total_ttc);
532 unset($object->total_tva);
533 unset($object->totalpaid);
534 unset($object->totalpaid_multicurrency);
535 unset($object->transport_mode_id);
536 unset($object->TRIGGER_PREFIX);
537 unset($object->user);
538 unset($object->user_closing_id);
539 unset($object->user_modification_id);
540 unset($object->user_validation_id);
541
542 return $object;
543 }
544
545
554 private function _validate($data)
555 {
556 if ($data === null) {
557 $data = array();
558 }
559 $warehouse = array();
560 foreach (Warehouses::$FIELDS as $field) {
561 if (!isset($data[$field])) {
562 throw new RestException(400, "$field field missing");
563 }
564 $warehouse[$field] = $data[$field];
565 }
566 return $warehouse;
567 }
568}
$id
Support class for third parties, contacts, members, users or resources.
Definition account.php:47
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
Class for API REST v1.
Definition api.class.php:35
_checkValExtrafieldsForAPI($field, $value, $object)
Check and convert a string depending on its type/name.
_filterObjectProperties($object, $properties)
Filter properties that will be returned on object.
static _checkAccessToResource($resource, $resource_id=0, $dbtablename='', $feature2='', $dbt_keyfield='fk_soc', $dbt_select='rowid')
Check access by user to a given resource.
_checkValForAPI($field, $value, $object)
Check and convert a string depending on its type/name.
Class to manage warehouses.
_validate($data)
Validate fields before create or update object.
_cleanObjectDatas($object)
Clean sensible object datas @phpstan-template T.
__construct()
Constructor.
put($id, $request_data=null)
Update a warehouse.
post($request_data=null)
Create a warehouse.
listProducts($id=0, $sortfield="t.rowid", $sortorder='ASC', $limit=100, $page=0, $includestockdata=0, $includesubproducts=false, $includeparentid=false, $includetrans=false, $properties='', $pagination_data=false)
List products in a warehouse.
index($sortfield="t.rowid", $sortorder='ASC', $limit=100, $page=0, $category=0, $sqlfilters='', $properties='', $pagination_data=false)
List warehouses.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
sanitizeVal($out='', $check='alphanohtml', $filter=null, $options=null)
Return a sanitized or empty value after checking value against a rule.