dolibarr 25.0.0-alpha
api_boms.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2015 Jean-François Ferry <jfefe@aternatik.fr>
3 * Copyright (C) 2019 Maxime Kohlhaas <maxime@atm-consulting.fr>
4 * Copyright (C) 2020-2025 Frédéric France <frederic.france@free.fr>
5 * Copyright (C) 2022 Christian Humpel <christian.humpel@live.com>
6 * Copyright (C) 2025 MDW <mdeweerd@users.noreply.github.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
22use Luracast\Restler\RestException;
23
24require_once DOL_DOCUMENT_ROOT.'/bom/class/bom.class.php';
25require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
26
27
40class Boms extends DolibarrApi
41{
45 public $bom;
46
50 public function __construct()
51 {
52 global $db;
53
54 $this->db = $db;
55 $this->bom = new BOM($this->db);
56 }
57
73 public function get($id)
74 {
75 if (!DolibarrApiAccess::$user->hasRight('bom', 'read')) {
76 throw new RestException(403);
77 }
78
79 $result = $this->bom->fetch($id);
80 if (!$result) {
81 throw new RestException(404, 'BOM not found');
82 }
83
84 if (!DolibarrApi::_checkAccessToResource('bom', $this->bom->id, 'bom_bom')) {
85 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
86 }
87
88 return $this->_cleanObjectDatas($this->bom);
89 }
90
91
111 public function index($sortfield = "t.rowid", $sortorder = 'ASC', $limit = 100, $page = 0, $sqlfilters = '', $properties = '')
112 {
113 if (!DolibarrApiAccess::$user->hasRight('bom', 'read')) {
114 throw new RestException(403);
115 }
116
117 $obj_ret = array();
118 $tmpobject = new BOM($this->db);
119
120 $socid = DolibarrApiAccess::$user->socid ?: '';
121
122 $restrictonsocid = 0; // Set to 1 if there is a field socid in table of object
123
124 // If the internal user must only see his customers, force searching by him
125 $search_sale = 0;
126 if ($restrictonsocid && !DolibarrApiAccess::$user->hasRight('societe', 'client', 'voir') && !$socid) {
127 $search_sale = DolibarrApiAccess::$user->id;
128 }
129
130 $sql = "SELECT t.rowid";
131 $sql .= " FROM ".MAIN_DB_PREFIX.$tmpobject->table_element." AS t";
132 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX.$tmpobject->table_element."_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
133 $sql .= " WHERE 1 = 1";
134 if ($tmpobject->ismultientitymanaged) {
135 $sql .= ' AND t.entity IN ('.getEntity($tmpobject->element).')';
136 }
137 if ($restrictonsocid && $socid) {
138 $sql .= " AND t.fk_soc = ".((int) $socid);
139 }
140 // Search on sale representative
141 if ($search_sale && $search_sale != '-1') {
142 if ($search_sale == -2) {
143 $sql .= " AND ".getSalesRepresentativeSqlFilter('t.fk_soc', 0, 1);
144 } elseif ($search_sale > 0) {
145 $sql .= " AND ".getSalesRepresentativeSqlFilter('t.fk_soc', (int) $search_sale);
146 }
147 }
148 if ($sqlfilters) {
149 $errormessage = '';
150 $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage);
151 if ($errormessage) {
152 throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage);
153 }
154 }
155
156 $sql .= $this->db->order($sortfield, $sortorder);
157 if ($limit) {
158 if ($page < 0) {
159 $page = 0;
160 }
161 $offset = $limit * $page;
162
163 $sql .= $this->db->plimit($limit + 1, $offset);
164 }
165
166 $result = $this->db->query($sql);
167 if ($result) {
168 $i = 0;
169 $num = $this->db->num_rows($result);
170 $min = min($num, ($limit <= 0 ? $num : $limit));
171 while ($i < $min) {
172 $obj = $this->db->fetch_object($result);
173 $bom_static = new BOM($this->db);
174 if ($bom_static->fetch($obj->rowid)) {
175 $obj_ret[] = $this->_filterObjectProperties($this->_cleanObjectDatas($bom_static), $properties);
176 }
177 $i++;
178 }
179 } else {
180 throw new RestException(503, 'Error when retrieve bom list');
181 }
182
183 return $obj_ret;
184 }
185
197 public function post($request_data = null)
198 {
199 if (!DolibarrApiAccess::$user->hasRight('bom', 'write')) {
200 throw new RestException(403);
201 }
202 // Check mandatory fields
203 $result = $this->_validate($request_data);
204
205 foreach ($request_data as $field => $value) {
206 if ($field === 'caller') {
207 // 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
208 $this->bom->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
209 continue;
210 }
211
212 $this->bom->$field = $this->_checkValForAPI($field, $value, $this->bom);
213 }
214
215 $this->checkRefNumbering();
216
217 if (!$this->bom->create(DolibarrApiAccess::$user)) {
218 throw new RestException(500, "Error creating BOM", array_merge(array($this->bom->error), $this->bom->errors));
219 }
220 return $this->bom->id;
221 }
222
238 public function put($id, $request_data = null)
239 {
240 if (!DolibarrApiAccess::$user->hasRight('bom', 'write')) {
241 throw new RestException(403);
242 }
243
244 $result = $this->bom->fetch($id);
245 if (!$result) {
246 throw new RestException(404, 'BOM not found');
247 }
248
249 if (!DolibarrApi::_checkAccessToResource('bom', $this->bom->id, 'bom_bom')) {
250 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
251 }
252
253 foreach ($request_data as $field => $value) {
254 if ($field == 'id') {
255 continue;
256 }
257 if ($field === 'caller') {
258 // 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
259 $this->bom->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
260 continue;
261 }
262
263 if ($field == 'array_options' && is_array($value)) {
264 foreach ($value as $index => $val) {
265 $this->bom->array_options[$index] = $this->_checkValExtrafieldsForAPI($index, $val, $this->bom);
266 }
267 continue;
268 }
269 $this->bom->$field = $this->_checkValForAPI($field, $value, $this->bom);
270 }
271
272 $this->checkRefNumbering();
273
274 if ($this->bom->update(DolibarrApiAccess::$user) > 0) {
275 return $this->get($id);
276 } else {
277 throw new RestException(500, $this->bom->errorsToString());
278 }
279 }
280
295 public function validate($id, $notrigger = 0)
296 {
297 if (!DolibarrApiAccess::$user->hasRight('bom', 'write')) {
298 throw new RestException(403);
299 }
300 $result = $this->bom->fetch($id);
301 if (!$result) {
302 throw new RestException(404, 'Bom not found');
303 }
304
305 $result = $this->bom->validate(DolibarrApiAccess::$user, $notrigger);
306 if ($result == 0) {
307 throw new RestException(304, 'Error nothing done. May be object is already validated');
308 }
309 if ($result < 0) {
310 throw new RestException(500, 'Error when validating BOM: '.$this->bom->errorsToString());
311 }
312 $result = $this->bom->fetch($id);
313
314 return $this->_cleanObjectDatas($this->bom);
315 }
316
329 public function delete($id)
330 {
331 if (!DolibarrApiAccess::$user->hasRight('bom', 'delete')) {
332 throw new RestException(403);
333 }
334 $result = $this->bom->fetch($id);
335 if (!$result) {
336 throw new RestException(404, 'BOM not found');
337 }
338
339 if (!DolibarrApi::_checkAccessToResource('bom', $this->bom->id, 'bom_bom')) {
340 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
341 }
342
343 if (!$this->bom->delete(DolibarrApiAccess::$user)) {
344 throw new RestException(500, 'Error when deleting BOM : '.$this->bom->errorsToString());
345 }
346
347 return array(
348 'success' => array(
349 'code' => 200,
350 'message' => 'BOM deleted'
351 )
352 );
353 }
354
369 public function getLines($id)
370 {
371 if (!DolibarrApiAccess::$user->hasRight('bom', 'read')) {
372 throw new RestException(403);
373 }
374
375 $result = $this->bom->fetch($id);
376 if (!$result) {
377 throw new RestException(404, 'BOM not found');
378 }
379
380 if (!DolibarrApi::_checkAccessToResource('bom_bom', $this->bom->id)) {
381 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
382 }
383 $this->bom->getLinesArray();
384 $result = array();
385 foreach ($this->bom->lines as $line) {
386 array_push($result, $this->_cleanObjectDatas($line));
387 }
388 return $result;
389 }
390
407 public function postLine($id, $request_data = null)
408 {
409 if (!DolibarrApiAccess::$user->hasRight('bom', 'write')) {
410 throw new RestException(403);
411 }
412
413 $result = $this->bom->fetch($id);
414 if (!$result) {
415 throw new RestException(404, 'BOM not found');
416 }
417
418 if (!DolibarrApi::_checkAccessToResource('bom_bom', $this->bom->id)) {
419 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
420 }
421
422 $request_data = (object) $request_data;
423
424 $updateRes = $this->bom->addLine(
425 $request_data->fk_product,
426 $request_data->qty,
427 $request_data->qty_frozen,
428 $request_data->disable_stock_change,
429 $request_data->efficiency,
430 $request_data->position,
431 $request_data->fk_bom_child,
432 $request_data->import_key,
433 $request_data->fk_unit,
434 $request_data->array_options,
435 $request_data->fk_default_workstation
436 );
437
438 if ($updateRes > 0) {
439 return $updateRes;
440 } else {
441 throw new RestException(500, $this->bom->errorsToString());
442 }
443 }
444
461 public function putLine($id, $lineid, $request_data = null)
462 {
463 if (!DolibarrApiAccess::$user->hasRight('bom', 'write')) {
464 throw new RestException(403);
465 }
466
467 $result = $this->bom->fetch($id);
468 if (!$result) {
469 throw new RestException(404, 'BOM not found');
470 }
471
472 if (!DolibarrApi::_checkAccessToResource('bom_bom', $this->bom->id)) {
473 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
474 }
475
476 // BOM::updateLine() sets fk_bom to this BOM on whatever line it is given, so a line of
477 // another BOM must be refused here or it would be moved into this one.
478 $bomline = new BOMLine($this->db);
479 if ($bomline->fetch($lineid) <= 0) {
480 throw new RestException(404, 'BOM line not found');
481 }
482 if ($bomline->fk_bom != $this->bom->id) {
483 throw new RestException(403, 'Line does not belong to this BOM');
484 }
485
486 $request_data = (object) $request_data;
487
488 $updateRes = $this->bom->updateLine(
489 $lineid,
490 $request_data->qty,
491 $request_data->qty_frozen,
492 $request_data->disable_stock_change,
493 $request_data->efficiency,
494 $request_data->position,
495 $request_data->import_key,
496 $request_data->fk_unit,
497 $request_data->array_options,
498 $request_data->fk_default_workstation
499 );
500
501 if ($updateRes > 0) {
502 $result = $this->get($id);
503 unset($result->line);
504 return $this->_cleanObjectDatas($result);
505 }
506 return false;
507 }
508
526 public function deleteLine($id, $lineid)
527 {
528 if (!DolibarrApiAccess::$user->hasRight('bom', 'write')) {
529 throw new RestException(403);
530 }
531
532 $result = $this->bom->fetch($id);
533 if (!$result) {
534 throw new RestException(404, 'BOM not found');
535 }
536
537 if (!DolibarrApi::_checkAccessToResource('bom_bom', $this->bom->id)) {
538 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
539 }
540
541 //Check the rowid is a line of current bom object
542 $lineIdIsFromObject = false;
543 foreach ($this->bom->lines as $bl) {
544 if ($bl->id == $lineid) {
545 $lineIdIsFromObject = true;
546 break;
547 }
548 }
549 if (!$lineIdIsFromObject) {
550 throw new RestException(500, 'Line to delete (rowid: '.$lineid.') is not a line of BOM (id: '.$this->bom->id.')');
551 }
552
553 $updateRes = $this->bom->deleteLine(DolibarrApiAccess::$user, $lineid);
554 if ($updateRes > 0) {
555 return array(
556 'success' => array(
557 'code' => 200,
558 'message' => 'line ' .$lineid. ' deleted'
559 )
560 );
561 } else {
562 throw new RestException(500, $this->bom->errorsToString());
563 }
564 }
565
566 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
576 protected function _cleanObjectDatas($object)
577 {
578 // phpcs:enable
579 $object = parent::_cleanObjectDatas($object);
580
581 unset($object->rowid);
582 unset($object->canvas);
583
584 unset($object->name);
585 unset($object->lastname);
586 unset($object->firstname);
587 unset($object->civility_id);
588 unset($object->statut);
589 unset($object->state);
590 unset($object->region_id);
591 unset($object->state_id);
592 unset($object->state_code);
593 unset($object->region);
594 unset($object->region_code);
595 unset($object->country);
596 unset($object->country_id);
597 unset($object->country_code);
598 unset($object->barcode_type);
599 unset($object->barcode_type_code);
600 unset($object->barcode_type_label);
601 unset($object->barcode_type_coder);
602 unset($object->demand_reason_id);
603 unset($object->transport_mode_id);
604 unset($object->shipping_method);
605 unset($object->civility_code);
606 unset($object->actiontypecode);
607 unset($object->product);
608
609 unset($object->total_ht);
610 unset($object->total_tva);
611 unset($object->total_localtax1);
612 unset($object->total_localtax2);
613 unset($object->total_ttc);
614
615 unset($object->user);
616
617 unset($object->totalpaid);
618 unset($object->totalpaid_multicurrency);
619 unset($object->deposit_percent);
620 unset($object->cond_reglement_supplier_id);
621
622 unset($object->fk_account);
623 unset($object->comments);
624 unset($object->note);
625 unset($object->mode_reglement_id);
626 unset($object->cond_reglement_id);
627 unset($object->cond_reglement);
628 unset($object->shipping_method_id);
629 unset($object->fk_incoterms);
630 unset($object->label_incoterms);
631 unset($object->location_incoterms);
632 unset($object->multicurrency_code);
633 unset($object->multicurrency_tx);
634 unset($object->multicurrency_total_ht);
635 unset($object->multicurrency_total_ttc);
636 unset($object->multicurrency_total_tva);
637 unset($object->multicurrency_total_localtax1);
638 unset($object->multicurrency_total_localtax2);
639
640
641 // If object has lines, remove $db property
642 if (isset($object->lines) && is_array($object->lines) && count($object->lines) > 0) {
643 $nboflines = count($object->lines);
644 for ($i = 0; $i < $nboflines; $i++) {
645 $this->_cleanObjectDatas($object->lines[$i]);
646
647 unset($object->lines[$i]->lines);
648 unset($object->lines[$i]->note);
649 }
650 }
651
652 return $object;
653 }
654
663 private function _validate($data)
664 {
665 if ($data === null) {
666 $data = array();
667 }
668 $myobject = array();
669 foreach ($this->bom->fields as $field => $propfield) {
670 if (in_array($field, array('rowid', 'entity', 'date_creation', 'tms', 'fk_user_creat')) || empty($propfield['notnull']) || $propfield['notnull'] != 1) {
671 continue; // Not a mandatory field
672 }
673 if (!isset($data[$field])) {
674 throw new RestException(400, "$field field missing");
675 }
676 $myobject[$field] = $data[$field];
677 }
678 return $myobject;
679 }
680
686 private function checkRefNumbering()
687 {
688 $ref = substr($this->bom->ref, 1, 4);
689 if ($this->bom->status > BOM::STATUS_DRAFT && $ref == 'PROV') {
690 throw new RestException(400, "Wrong naming scheme '(PROV%)' is only allowed on 'DRAFT' status. For automatic increment use 'auto' on the 'ref' field.");
691 }
692
693 if (strtolower($this->bom->ref) == 'auto') {
694 if (empty($this->bom->id) && $this->bom->status == BOM::STATUS_DRAFT) {
695 $this->bom->ref = ''; // 'ref' will auto incremented with '(PROV' + newID + ')'
696 } else {
697 $res = $this->bom->fetch_product();
698 if ($res > 0 && $this->bom->product instanceof Product) {
699 $numref = $this->bom->getNextNumRef($this->bom->product); // @phan-suppress-current-line PhanTypeMismatchArgumentNullable
700 $this->bom->ref = $numref;
701 } else {
702 throw new RestException(400, "Error when generating automatic increment on the 'ref' field.");
703 }
704 }
705 }
706 }
707}
$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 BOM.
Definition bom.class.php:42
Class for BOMLine.
validate($id, $notrigger=0)
Validate BOM.
checkRefNumbering()
Validate the ref field and get the next Number if it's necessary.
put($id, $request_data=null)
Update bom.
_cleanObjectDatas($object)
Clean sensible object datas @phpstan-template T.
putLine($id, $lineid, $request_data=null)
Update a line to given BOM.
post($request_data=null)
Create bom object.
getLines($id)
Get lines of an BOM.
index($sortfield="t.rowid", $sortorder='ASC', $limit=100, $page=0, $sqlfilters='', $properties='')
List boms.
__construct()
Constructor.
deleteLine($id, $lineid)
Delete a line to given BOM.
postLine($id, $request_data=null)
Add a line to given BOM.
_validate($data)
Validate fields before create or update object.
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.
_checkValForAPI($field, $value, $object)
Check and convert a string depending on its type/name.
static _checkAccessToResource($resource, $resource_id=0, $dbtablename='', $feature2='', $dbt_keyfield='fk_soc', $dbt_select='rowid', $parenttableforentity='')
Check access by user to a given resource.
Class to manage products or services.
forgeSQLFromUniversalSearchCriteria($filter, &$errorstr='', $noand=0, $nopar=0, $noerror=0, $forbiddenfields=array())
forgeSQLFromUniversalSearchCriteria
sanitizeVal($out='', $check='alphanohtml', $filter=null, $options=null)
Return a sanitized or empty value after checking value against a rule.