dolibarr 25.0.0-alpha
api_mos.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) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
5 * Copyright (C) 2024-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.'/mrp/class/mo.class.php';
24require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
25require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
26
27
40class Mos extends DolibarrApi
41{
45 public $mo;
46
50 public function __construct()
51 {
52 global $db, $conf;
53 $this->db = $db;
54 $this->mo = new Mo($this->db);
55 }
56
68 public function get($id)
69 {
70 if (!DolibarrApiAccess::$user->hasRight('mrp', 'read')) {
71 throw new RestException(403);
72 }
73
74 $result = $this->mo->fetch($id);
75 if (!$result) {
76 throw new RestException(404, 'MO not found');
77 }
78
79 if (!DolibarrApi::_checkAccessToResource('mrp', $this->mo->id, 'mrp_mo')) {
80 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
81 }
82
83 return $this->_cleanObjectDatas($this->mo);
84 }
85
103 public function getCategories($id, $sortfield = "s.rowid", $sortorder = 'ASC', $limit = 0, $page = 0)
104 {
105 if (!DolibarrApiAccess::$user->hasRight('categorie', 'lire')) {
106 throw new RestException(403);
107 }
108
109 $categories = new Categorie($this->db);
110
111 $result = $categories->getListForItem($id, Categorie::TYPE_MO, $sortfield, $sortorder, $limit, $page);
112
113 if ($result < 0) {
114 throw new RestException(503, 'Error when retrieve category list : ' . implode(',', array_merge(array($categories->error), $categories->errors)));
115 }
116
117 return $result;
118 }
119
120
138 public function index($sortfield = "t.rowid", $sortorder = 'ASC', $limit = 100, $page = 0, $sqlfilters = '', $properties = '')
139 {
140 if (!DolibarrApiAccess::$user->hasRight('mrp', 'read')) {
141 throw new RestException(403);
142 }
143
144 $obj_ret = array();
145 $tmpobject = new Mo($this->db);
146
147 $socid = DolibarrApiAccess::$user->socid ?: 0;
148
149 $restrictonsocid = 1; // Mo::$fields has a 'fk_soc' field
150
151 // If the internal user must only see his customers, force searching by him
152 $search_sale = 0;
153 if ($restrictonsocid && !DolibarrApiAccess::$user->hasRight('societe', 'client', 'voir') && !$socid) {
154 $search_sale = DolibarrApiAccess::$user->id;
155 }
156
157 $sql = "SELECT t.rowid";
158 $sql .= " FROM ".MAIN_DB_PREFIX.$tmpobject->table_element." AS t";
159 $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
160 $sql .= " WHERE 1 = 1";
161 if ($tmpobject->ismultientitymanaged) {
162 $sql .= ' AND t.entity IN ('.getEntity($tmpobject->element).')';
163 }
164 if ($restrictonsocid && $socid) {
165 $sql .= " AND t.fk_soc = ".((int) $socid);
166 }
167 // Search on sale representative
168 if ($search_sale && $search_sale != '-1') {
169 if ($search_sale == -2) {
170 $sql .= " AND ".getSalesRepresentativeSqlFilter('t.fk_soc', 0, 1);
171 } elseif ($search_sale > 0) {
172 // t.fk_soc is optional on Mo (a manufacturing order is not necessarily linked to a thirdparty), so a Mo with no
173 // thirdparty is not restricted by sales representative visibility (there is no customer data to protect on it).
174 $sql .= " AND ".getSalesRepresentativeSqlFilter('t.fk_soc', (int) $search_sale, 0, 1);
175 }
176 }
177 if ($sqlfilters) {
178 $errormessage = '';
179 $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage);
180 if ($errormessage) {
181 throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage);
182 }
183 }
184
185 $sql .= $this->db->order($sortfield, $sortorder);
186 if ($limit) {
187 if ($page < 0) {
188 $page = 0;
189 }
190 $offset = $limit * $page;
191
192 $sql .= $this->db->plimit($limit + 1, $offset);
193 }
194
195 $result = $this->db->query($sql);
196 if ($result) {
197 $i = 0;
198 $num = $this->db->num_rows($result);
199 $min = min($num, ($limit <= 0 ? $num : $limit));
200 while ($i < $min) {
201 $obj = $this->db->fetch_object($result);
202 $tmp_object = new Mo($this->db);
203 if ($tmp_object->fetch($obj->rowid)) {
204 $obj_ret[] = $this->_filterObjectProperties($this->_cleanObjectDatas($tmp_object), $properties);
205 }
206 $i++;
207 }
208 } else {
209 throw new RestException(503, 'Error when retrieve MO list');
210 }
211
212 return $obj_ret;
213 }
214
225 public function post($request_data = null)
226 {
227 if (!DolibarrApiAccess::$user->hasRight('mrp', 'write')) {
228 throw new RestException(403);
229 }
230 // Check mandatory fields
231 $result = $this->_validate($request_data);
232
233 foreach ($request_data as $field => $value) {
234 if ($field === 'caller') {
235 // 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
236 $this->mo->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
237 continue;
238 }
239
240 $this->mo->$field = $this->_checkValForAPI($field, $value, $this->mo);
241 }
242
243 $this->checkRefNumbering();
244
245 $result = $this->mo->create(DolibarrApiAccess::$user);
246 //var_dump($result);exit;
247 if ($result < 0) {
248 throw new RestException(500, "Error creating MO", array_merge(array($this->mo->error), $this->mo->errors));
249 }
250
251 return $this->mo->id;
252 }
253
263 public function put($id, $request_data = null)
264 {
265 if (!DolibarrApiAccess::$user->hasRight('mrp', 'write')) {
266 throw new RestException(403);
267 }
268
269 $result = $this->mo->fetch($id);
270 if (!$result) {
271 throw new RestException(404, 'MO not found');
272 }
273
274 if (!DolibarrApi::_checkAccessToResource('mrp', $this->mo->id, 'mrp_mo')) {
275 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
276 }
277
278 foreach ($request_data as $field => $value) {
279 if ($field == 'id') {
280 continue;
281 }
282 if ($field === 'caller') {
283 // 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
284 $this->mo->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
285 continue;
286 }
287
288 if ($field == 'array_options' && is_array($value)) {
289 foreach ($value as $index => $val) {
290 $this->mo->array_options[$index] = $this->_checkValExtrafieldsForAPI($index, $val, $this->mo);
291 }
292 continue;
293 }
294
295 $this->mo->$field = $this->_checkValForAPI($field, $value, $this->mo);
296 }
297
298 $this->checkRefNumbering();
299
300 if ($this->mo->update(DolibarrApiAccess::$user) > 0) {
301 return $this->get($id);
302 } else {
303 throw new RestException(500, $this->mo->errorsToString());
304 }
305 }
306
321 public function validate($id, $notrigger = 0)
322 {
323 if (!DolibarrApiAccess::$user->hasRight('mrp', 'write')) {
324 throw new RestException(403);
325 }
326
327 $result = $this->mo->fetch($id);
328 if (!$result) {
329 throw new RestException(404, 'MO not found');
330 }
331
332 if (!DolibarrApi::_checkAccessToResource('mrp', $this->mo->id, 'mrp_mo')) {
333 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
334 }
335
336 $result = $this->mo->validate(DolibarrApiAccess::$user, $notrigger);
337 if ($result == 0) {
338 throw new RestException(304, 'Error nothing done. May be object is already validated');
339 }
340 if ($result < 0) {
341 throw new RestException(500, 'Error when validating MO: '.$this->mo->errorsToString());
342 }
343 $result = $this->mo->fetch($id);
344
345 return $this->_cleanObjectDatas($this->mo);
346 }
347
362 public function confirmProduced($id, $notrigger = 0)
363 {
364 if (!DolibarrApiAccess::$user->hasRight('mrp', 'write')) {
365 throw new RestException(403);
366 }
367
368 $result = $this->mo->fetch($id);
369 if (!$result) {
370 throw new RestException(404, 'MO not found');
371 }
372
373 if (!DolibarrApi::_checkAccessToResource('mrp', $this->mo->id, 'mrp_mo')) {
374 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
375 }
376
377 $result = $this->mo->setStatut($this->mo::STATUS_PRODUCED, 0, '', 'MRP_MO_PRODUCED');
378 if ($result < 0) {
379 throw new RestException(500, 'Error when setting MO Produced: '.$this->mo->errorsToString());
380 }
381 $result = $this->mo->fetch($id);
382
383 return $this->_cleanObjectDatas($this->mo);
384 }
385
394 public function delete($id)
395 {
396 if (!DolibarrApiAccess::$user->hasRight('mrp', 'delete')) {
397 throw new RestException(403);
398 }
399 $result = $this->mo->fetch($id);
400 if (!$result) {
401 throw new RestException(404, 'MO not found');
402 }
403
404 if (!DolibarrApi::_checkAccessToResource('mrp', $this->mo->id, 'mrp_mo')) {
405 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
406 }
407
408 if (!$this->mo->delete(DolibarrApiAccess::$user)) {
409 throw new RestException(500, 'Error when deleting MO : '.$this->mo->errorsToString());
410 }
411
412 return array(
413 'success' => array(
414 'code' => 200,
415 'message' => 'MO deleted'
416 )
417 );
418 }
419
420
453 public function produceAndConsumeAll($id, $request_data = null)
454 {
455 global $langs;
456
457 $error = 0;
458
459 if (!DolibarrApiAccess::$user->hasRight('mrp', 'write')) {
460 throw new RestException(403, 'Not enough permission');
461 }
462 $result = $this->mo->fetch($id);
463 if (!$result) {
464 throw new RestException(404, 'MO not found');
465 }
466
467 if ($this->mo->status != Mo::STATUS_VALIDATED && $this->mo->status != Mo::STATUS_INPROGRESS) {
468 throw new RestException(405, 'Error bad status of MO');
469 }
470
471 // Code for consume and produce...
472 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
473 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
474 require_once DOL_DOCUMENT_ROOT.'/mrp/lib/mrp_mo.lib.php';
475
476 $stockmove = new MouvementStock($this->db);
477
478 $labelmovement = '';
479 $codemovement = '';
480 $autoclose = 1;
481 $arraytoconsume = array();
482 $arraytoproduce = array();
483
484 foreach ($request_data as $field => $value) {
485 if ($field == 'inventorylabel') {
486 $labelmovement = $value;
487 }
488 if ($field == 'inventorycode') {
489 $codemovement = $value;
490 }
491 if ($field == 'autoclose') {
492 $autoclose = $value;
493 }
494 if ($field == 'arraytoconsume') {
495 $arraytoconsume = $value;
496 }
497 if ($field == 'arraytoproduce') {
498 $arraytoproduce = $value;
499 }
500 if ($field === 'caller') {
501 // 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
502 $stockmove->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
503 continue;
504 }
505 }
506
507 if (empty($labelmovement)) {
508 throw new RestException(500, "Field inventorylabel not provided");
509 }
510 if (empty($codemovement)) {
511 throw new RestException(500, "Field inventorycode not provided");
512 }
513
514 $consumptioncomplete = true;
515 $productioncomplete = true;
516
517 if (!empty($arraytoconsume) && !empty($arraytoproduce)) {
518 $pos = 0;
519 $arrayofarrayname = array("arraytoconsume", "arraytoproduce");
520 foreach ($arrayofarrayname as $arrayname) {
521 foreach (${$arrayname} as $value) {
522 $tmpproduct = new Product($this->db);
523 if (empty($value["objectid"])) {
524 throw new RestException(500, "Field objectid required in ".$arrayname);
525 }
526 $tmpproduct->fetch($value["objectid"]);
527 if (empty($value["qty"])) {
528 throw new RestException(500, "Field qty required in ".$arrayname);
529 }
530 if ($value["qty"] != 0) {
531 $qtytoprocess = $value["qty"];
532 if (isset($value["fk_warehouse"])) { // If there is a warehouse to set
533 if (!($value["fk_warehouse"] > 0)) { // If there is no warehouse set.
534 $error++;
535 throw new RestException(500, "Field fk_warehouse must be > 0 in ".$arrayname);
536 }
537 if ($tmpproduct->status_batch) {
538 $error++;
539 throw new RestException(500, "Product ".$tmpproduct->ref."must be in batch");
540 }
541 }
542 $idstockmove = 0;
543 if (!$error && $value["fk_warehouse"] > 0) {
544 // Record consumption to do and stock movement
545 $id_product_batch = 0;
546
547 $stockmove->setOrigin($this->mo->element, $this->mo->id);
548
549 // Record the stock movement first: the line below stores its ID, and
550 // llx_mrp_production.fk_stock_movement is a foreign key on llx_stock_mouvement.
551 if ($arrayname == 'arraytoconsume') {
552 $idstockmove = $stockmove->livraison(DolibarrApiAccess::$user, $value["objectid"], $value["fk_warehouse"], $qtytoprocess, 0, $labelmovement, dol_now(), '', '', (string) $tmpproduct->status_batch, $id_product_batch, $codemovement);
553 } else {
554 $idstockmove = $stockmove->reception(DolibarrApiAccess::$user, $value["objectid"], $value["fk_warehouse"], $qtytoprocess, 0, $labelmovement, '', '', (string) $tmpproduct->status_batch, dol_now(), $id_product_batch, $codemovement);
555 }
556 if ($idstockmove < 0) {
557 $error++;
558 throw new RestException(500, $stockmove->errorsToString());
559 }
560
561 $moline = new MoLine($this->db);
562 $moline->fk_mo = $this->mo->id;
563 $moline->position = $pos;
564 $moline->fk_product = $value["objectid"];
565 $moline->fk_warehouse = (int) $value["fk_warehouse"];
566 $moline->qty = $qtytoprocess;
567 $moline->batch = (string) $tmpproduct->status_batch;
568 $moline->role = ($arrayname == 'arraytoconsume' ? 'toproduce' : 'toconsume');
569 $moline->fk_mrp_production = 0;
570 $moline->fk_stock_movement = $idstockmove > 0 ? $idstockmove : null;
571 $moline->fk_user_creat = DolibarrApiAccess::$user->id;
572
573 $resultmoline = $moline->create(DolibarrApiAccess::$user);
574 if ($resultmoline <= 0) {
575 $error++;
576 throw new RestException(500, $moline->errorsToString());
577 }
578 }
579 if (!$error) {
580 // Record consumption done
581 $moline = new MoLine($this->db);
582 $moline->fk_mo = $this->mo->id;
583 $moline->position = $pos;
584 $moline->fk_product = $value["objectid"];
585 $moline->fk_warehouse = $value["fk_warehouse"];
586 $moline->qty = $qtytoprocess;
587 $moline->batch = (string) $tmpproduct->status_batch;
588 if ($arrayname == "arraytoconsume") {
589 $moline->role = 'consumed';
590 } else {
591 $moline->role = 'produced';
592 }
593 $moline->fk_mrp_production = 0;
594 $moline->fk_stock_movement = $idstockmove > 0 ? $idstockmove : null;
595 $moline->fk_user_creat = DolibarrApiAccess::$user->id;
596
597 $resultmoline = $moline->create(DolibarrApiAccess::$user);
598 if ($resultmoline <= 0) {
599 $error++;
600 throw new RestException(500, $moline->errorsToString());
601 }
602
603 $pos++;
604 }
605 }
606 }
607 }
608 if (!$error) {
609 if ($autoclose <= 0) {
610 $consumptioncomplete = false;
611 $productioncomplete = false;
612 }
613 }
614 } else {
615 $pos = 0;
616 foreach ($this->mo->lines as $line) {
617 if ($line->role == 'toconsume') {
618 $tmpproduct = new Product($this->db);
619 $tmpproduct->fetch($line->fk_product);
620 if ($line->qty != 0) {
621 $qtytoprocess = $line->qty;
622 if (isset($line->fk_warehouse)) { // If there is a warehouse to set
623 if (!($line->fk_warehouse > 0)) { // If there is no warehouse set.
624 $langs->load("errors");
625 $error++;
626 throw new RestException(500, $langs->trans("ErrorFieldRequiredForProduct", $langs->transnoentitiesnoconv("Warehouse"), (string) $tmpproduct->ref));
627 }
628 if ($tmpproduct->status_batch) {
629 $langs->load("errors");
630 $error++;
631 throw new RestException(500, $langs->trans("ErrorFieldRequiredForProduct", $langs->transnoentitiesnoconv("Batch"), (string) $tmpproduct->ref));
632 }
633 }
634 $idstockmove = 0;
635 if (!$error && $line->fk_warehouse > 0) {
636 // Record stock movement
637 $id_product_batch = 0;
638 $stockmove->origin_type = 'mo';
639 $stockmove->origin_id = $this->mo->id;
640 if ($qtytoprocess >= 0) {
641 $idstockmove = $stockmove->livraison(DolibarrApiAccess::$user, $line->fk_product, (int) $line->fk_warehouse, $qtytoprocess, 0, $labelmovement, dol_now(), '', '', (string) $tmpproduct->status_batch, $id_product_batch, $codemovement);
642 } else {
643 $idstockmove = $stockmove->reception(DolibarrApiAccess::$user, $line->fk_product, (int) $line->fk_warehouse, $qtytoprocess, 0, $labelmovement, '', '', (string) $tmpproduct->status_batch, dol_now(), $id_product_batch, $codemovement);
644 }
645 if ($idstockmove < 0) {
646 $error++;
647 throw new RestException(500, $stockmove->errorsToString());
648 }
649 }
650 if (!$error) {
651 // Record consumption
652 $moline = new MoLine($this->db);
653 $moline->fk_mo = $this->mo->id;
654 $moline->position = $pos;
655 $moline->fk_product = $line->fk_product;
656 $moline->fk_warehouse = $line->fk_warehouse;
657 $moline->qty = $qtytoprocess;
658 $moline->batch = (string) $tmpproduct->status_batch;
659 $moline->role = 'consumed';
660 $moline->fk_mrp_production = $line->id;
661 $moline->fk_stock_movement = $idstockmove;
662 $moline->fk_user_creat = DolibarrApiAccess::$user->id;
663
664 $resultmoline = $moline->create(DolibarrApiAccess::$user);
665 if ($resultmoline <= 0) {
666 $error++;
667 throw new RestException(500, $moline->errorsToString());
668 }
669
670 $pos++;
671 }
672 }
673 }
674 }
675 $pos = 0;
676 foreach ($this->mo->lines as $line) {
677 if ($line->role == 'toproduce') {
678 $tmpproduct = new Product($this->db);
679 $tmpproduct->fetch($line->fk_product);
680 if ($line->qty != 0) {
681 $qtytoprocess = $line->qty;
682 if (isset($line->fk_warehouse)) { // If there is a warehouse to set
683 if (!($line->fk_warehouse > 0)) { // If there is no warehouse set.
684 $langs->load("errors");
685 $error++;
686 throw new RestException(500, $langs->trans("ErrorFieldRequiredForProduct", $langs->transnoentitiesnoconv("Warehouse"), (string) $tmpproduct->ref));
687 }
688 if ($tmpproduct->status_batch) {
689 $langs->load("errors");
690 $error++;
691 throw new RestException(500, $langs->trans("ErrorFieldRequiredForProduct", $langs->transnoentitiesnoconv("Batch"), (string) $tmpproduct->ref));
692 }
693 }
694 $idstockmove = 0;
695 if (!$error && $line->fk_warehouse > 0) {
696 // Record stock movement
697 $id_product_batch = 0;
698 $stockmove->origin_type = 'mo';
699 $stockmove->origin_id = $this->mo->id;
700 if ($qtytoprocess >= 0) {
701 // Entering the produced goods into stock is the only movement that may carry
702 // a value: the manufacturing cost of one unit, that is the sum of qty * unit
703 // cost over the lines to consume, divided by the quantity produced. Unit cost
704 // follows the same priority chain as the web UI: cost_price, then pmp, then
705 // the lowest supplier price. Computed here, inside the stock entry branch, so
706 // it can never reach a stock exit.
707 $mfgcost = 0;
708 foreach ($this->mo->lines as $consumedline) {
709 if ($consumedline->role == 'toconsume') {
710 $consumedproduct = new Product($this->db);
711 $consumedproduct->fetch($consumedline->fk_product);
712 $consumedcost = price2num(!empty($consumedproduct->cost_price) ? $consumedproduct->cost_price : $consumedproduct->pmp);
713 if (empty($consumedcost)) {
714 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
715 $productFournisseur = new ProductFournisseur($this->db);
716 if ($productFournisseur->find_min_price_product_fournisseur($consumedline->fk_product, $consumedline->qty) > 0) {
717 $consumedcost = $productFournisseur->fourn_unitprice;
718 }
719 }
720 $mfgcost += price2num(($consumedline->qty * $consumedcost) / ($this->mo->qty > 0 ? $this->mo->qty : 1), 'MU');
721 }
722 }
723 $mfgcost = (float) price2num($mfgcost, 'MU');
724 $idstockmove = $stockmove->reception(DolibarrApiAccess::$user, $line->fk_product, (int) $line->fk_warehouse, $qtytoprocess, $mfgcost, $labelmovement, '', '', (string) $tmpproduct->status_batch, dol_now(), $id_product_batch, $codemovement);
725 } else {
726 $idstockmove = $stockmove->livraison(DolibarrApiAccess::$user, $line->fk_product, (int) $line->fk_warehouse, $qtytoprocess, 0, $labelmovement, dol_now(), '', '', (string) $tmpproduct->status_batch, $id_product_batch, $codemovement);
727 }
728 if ($idstockmove < 0) {
729 $error++;
730 throw new RestException(500, $stockmove->errorsToString());
731 }
732 }
733 if (!$error) {
734 // Record consumption
735 $moline = new MoLine($this->db);
736 $moline->fk_mo = $this->mo->id;
737 $moline->position = $pos;
738 $moline->fk_product = $line->fk_product;
739 $moline->fk_warehouse = $line->fk_warehouse;
740 $moline->qty = $qtytoprocess;
741 $moline->batch = (string) $tmpproduct->status_batch;
742 $moline->role = 'produced';
743 $moline->fk_mrp_production = $line->id;
744 $moline->fk_stock_movement = $idstockmove;
745 $moline->fk_user_creat = DolibarrApiAccess::$user->id;
746
747 $resultmoline = $moline->create(DolibarrApiAccess::$user);
748 if ($resultmoline <= 0) {
749 $error++;
750 throw new RestException(500, $moline->errorsToString());
751 }
752
753 $pos++;
754 }
755 }
756 }
757 }
758
759 if (!$error) {
760 if ($autoclose > 0) {
761 foreach ($this->mo->lines as $line) {
762 if ($line->role == 'toconsume') {
763 $arrayoflines = $this->mo->fetchLinesLinked('consumed', $line->id);
764 $alreadyconsumed = 0;
765 foreach ($arrayoflines as $line2) {
766 $alreadyconsumed += $line2['qty'];
767 }
768
769 if ($alreadyconsumed < $line->qty) {
770 $consumptioncomplete = false;
771 }
772 }
773 if ($line->role == 'toproduce') {
774 $arrayoflines = $this->mo->fetchLinesLinked('produced', $line->id);
775 $alreadyproduced = 0;
776 foreach ($arrayoflines as $line2) {
777 $alreadyproduced += $line2['qty'];
778 }
779
780 if ($alreadyproduced < $line->qty) {
781 $productioncomplete = false;
782 }
783 }
784 }
785 } else {
786 $consumptioncomplete = false;
787 $productioncomplete = false;
788 }
789 }
790 }
791
792 // Update status of MO
793 dol_syslog("consumptioncomplete = ".json_encode($consumptioncomplete)." productioncomplete = ".json_encode($productioncomplete));
794 if ($consumptioncomplete && $productioncomplete) {
795 $result = $this->mo->setStatut(Mo::STATUS_PRODUCED, 0, '', 'MRP_MO_PRODUCED');
796 } else {
797 $result = $this->mo->setStatut(Mo::STATUS_INPROGRESS, 0, '', 'MRP_MO_PRODUCED');
798 }
799 if ($result <= 0) {
800 throw new RestException(500, $this->mo->errorsToString());
801 }
802
803 return $this->mo->id;
804 }
805
840 public function produceAndConsume($id, $request_data = null)
841 {
842 if (!DolibarrApiAccess::$user->hasRight("mrp", "write")) {
843 throw new RestException(403, 'Not enough permission');
844 }
845 $result = $this->mo->fetch($id);
846 if (!$result) {
847 throw new RestException(404, 'MO not found');
848 }
849
850 if ($this->mo->status != Mo::STATUS_VALIDATED && $this->mo->status != Mo::STATUS_INPROGRESS) {
851 throw new RestException(405, 'Error bad status of MO');
852 }
853
854 // Code for consume and produce...
855 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
856 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
857 require_once DOL_DOCUMENT_ROOT.'/mrp/lib/mrp_mo.lib.php';
858
859 $stockmove = new MouvementStock($this->db);
860
861 $labelmovement = '';
862 $codemovement = '';
863 $autoclose = 1;
864 $arraytoconsume = array();
865 $arraytoproduce = array();
866
867 foreach ($request_data as $field => $value) {
868 if ($field == 'inventorylabel') {
869 $labelmovement = $value;
870 }
871 if ($field == 'inventorycode') {
872 $codemovement = $value;
873 }
874 if ($field == 'autoclose') {
875 $autoclose = $value;
876 }
877 if ($field == 'arraytoconsume') {
878 $arraytoconsume = $value;
879 }
880 if ($field == 'arraytoproduce') {
881 $arraytoproduce = $value;
882 }
883 if ($field === 'caller') {
884 // 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
885 $stockmove->context['caller'] = $request_data['caller'];
886 continue;
887 }
888 }
889
890 if (empty($labelmovement)) {
891 throw new RestException(500, "Field inventorylabel not provided");
892 }
893 if (empty($codemovement)) {
894 throw new RestException(500, "Field inventorycode not provided");
895 }
896
897 $this->db->begin();
898
899 $pos = 0;
900 $arrayofarrayname = array("arraytoconsume", "arraytoproduce");
901 foreach ($arrayofarrayname as $arrayname) {
902 foreach (${$arrayname} as $value) {
903 if (empty($value["objectid"])) {
904 throw new RestException(500, "Field objectid required in " . $arrayname);
905 }
906
907 $molinetoprocess = new MoLine($this->db);
908 $tmpmolineid = $molinetoprocess->fetch($value["objectid"]);
909 if ($tmpmolineid <= 0) {
910 throw new RestException(500, "MoLine with rowid " . $value["objectid"] . " not exist.");
911 }
912
913 $tmpproduct = new Product($this->db);
914 $tmpproduct->fetch($molinetoprocess->fk_product);
915 if ($tmpproduct->status_batch) {
916 throw new RestException(500, "Product " . $tmpproduct->ref . " must be in batch, this API can't handle it currently.");
917 }
918
919 if (empty($value["qty"]) && $value["qty"] != 0) {
920 throw new RestException(500, "Field qty with lower or higher then 0 required in " . $arrayname);
921 }
922 $qtytoprocess = $value["qty"];
923
924 $fk_warehousetoprocess = 0;
925 if ($molinetoprocess->disable_stock_change == false) {
926 if (isset($value["fk_warehouse"])) { // If there is a warehouse to set
927 if (!($value["fk_warehouse"] > 0)) { // If there is no warehouse set.
928 throw new RestException(500, "Field fk_warehouse required in " . $arrayname);
929 }
930 }
931 $fk_warehousetoprocess = (int) $value["fk_warehouse"];
932 }
933
934 $pricetoproduce = 0;
935 if (isset($value["pricetoproduce"])) { // If there is a price to produce set.
936 if ($value["pricetoproduce"] > 0) { // Only use prices grater then 0.
937 $pricetoproduce = $value["pricetoproduce"];
938 }
939 }
940
941 $idstockmove = 0;
942
943 if ($molinetoprocess->disable_stock_change == false) {
944 // Record stock movement
945 $id_product_batch = 0;
946 $stockmove->origin_type = 'mo';
947 $stockmove->origin_id = $this->mo->id;
948 if ($arrayname == "arraytoconsume") {
949 if ($qtytoprocess >= 0) {
950 $idstockmove = $stockmove->livraison(DolibarrApiAccess::$user, $molinetoprocess->fk_product, $fk_warehousetoprocess, $qtytoprocess, 0, $labelmovement, dol_now(), '', '', (string) $tmpproduct->status_batch, $id_product_batch, $codemovement);
951 } else {
952 $idstockmove = $stockmove->reception(DolibarrApiAccess::$user, $molinetoprocess->fk_product, $fk_warehousetoprocess, $qtytoprocess, 0, $labelmovement, '', '', (string) $tmpproduct->status_batch, dol_now(), $id_product_batch, $codemovement);
953 }
954 } else {
955 if ($qtytoprocess >= 0) {
956 $idstockmove = $stockmove->reception(DolibarrApiAccess::$user, $molinetoprocess->fk_product, $fk_warehousetoprocess, $qtytoprocess, $pricetoproduce, $labelmovement, '', '', (string) $tmpproduct->status_batch, dol_now(), $id_product_batch, $codemovement);
957 } else {
958 $idstockmove = $stockmove->livraison(DolibarrApiAccess::$user, $molinetoprocess->fk_product, $fk_warehousetoprocess, $qtytoprocess, 0, $labelmovement, dol_now(), '', '', (string) $tmpproduct->status_batch, $id_product_batch, $codemovement);
959 }
960 }
961 if ($idstockmove <= 0) {
962 throw new RestException(500, $stockmove->errorsToString());
963 }
964 }
965
966 // Record consumption
967 $moline = new MoLine($this->db);
968 $moline->fk_mo = $this->mo->id;
969 $moline->position = $pos;
970 $moline->fk_product = $tmpproduct->id;
971 $moline->fk_warehouse = $idstockmove > 0 ? $fk_warehousetoprocess : null;
972 $moline->qty = $qtytoprocess;
973 $moline->batch = '';
974 $moline->fk_mrp_production = $molinetoprocess->id;
975 $moline->fk_stock_movement = $idstockmove > 0 ? $idstockmove : null;
976 $moline->fk_user_creat = DolibarrApiAccess::$user->id;
977
978 if ($arrayname == "arraytoconsume") {
979 $moline->role = 'consumed';
980 } else {
981 $moline->role = 'produced';
982 }
983
984 $resultmoline = $moline->create(DolibarrApiAccess::$user);
985 if ($resultmoline <= 0) {
986 throw new RestException(500, $moline->errorsToString());
987 }
988
989 $pos++;
990 }
991 }
992
993 $consumptioncomplete = true;
994 $productioncomplete = true;
995
996 if ($autoclose > 0) {
997 // Refresh Lines after consumptions.
998 $this->mo->fetchLines();
999
1000 foreach ($this->mo->lines as $line) {
1001 if ($line->role == 'toconsume') {
1002 $arrayoflines = $this->mo->fetchLinesLinked('consumed', $line->id);
1003 $alreadyconsumed = 0;
1004 foreach ($arrayoflines as $line2) {
1005 $alreadyconsumed += $line2['qty'];
1006 }
1007
1008 if ($alreadyconsumed < $line->qty) {
1009 $consumptioncomplete = false;
1010 }
1011 }
1012 if ($line->role == 'toproduce') {
1013 $arrayoflines = $this->mo->fetchLinesLinked('produced', $line->id);
1014 $alreadyproduced = 0;
1015 foreach ($arrayoflines as $line2) {
1016 $alreadyproduced += $line2['qty'];
1017 }
1018
1019 if ($alreadyproduced < $line->qty) {
1020 $productioncomplete = false;
1021 }
1022 }
1023 }
1024 } else {
1025 $consumptioncomplete = false;
1026 $productioncomplete = false;
1027 }
1028
1029 // Update status of MO
1030 dol_syslog("consumptioncomplete = " . (string) $consumptioncomplete . " productioncomplete = " . (string) $productioncomplete);
1031 //var_dump("consumptioncomplete = ".$consumptioncomplete." productioncomplete = ".$productioncomplete);
1032 if ($consumptioncomplete && $productioncomplete) {
1033 $result = $this->mo->setStatut(Mo::STATUS_PRODUCED, 0, '', 'MRP_MO_PRODUCED');
1034 } else {
1035 $result = $this->mo->setStatut(Mo::STATUS_INPROGRESS, 0, '', 'MRP_MO_PRODUCED');
1036 }
1037 if ($result <= 0) {
1038 throw new RestException(500, $this->mo->errorsToString());
1039 }
1040
1041 $this->db->commit();
1042 return $this->mo->id;
1043 }
1044
1045
1046 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
1056 protected function _cleanObjectDatas($object)
1057 {
1058 // phpcs:enable
1059 $object = parent::_cleanObjectDatas($object);
1060
1061 unset($object->rowid);
1062 unset($object->canvas);
1063
1064 unset($object->name);
1065 unset($object->lastname);
1066 unset($object->firstname);
1067 unset($object->civility_id);
1068 unset($object->statut);
1069 unset($object->state);
1070 unset($object->state_id);
1071 unset($object->state_code);
1072 unset($object->region);
1073 unset($object->region_code);
1074 unset($object->country);
1075 unset($object->country_id);
1076 unset($object->country_code);
1077 unset($object->barcode_type);
1078 unset($object->barcode_type_code);
1079 unset($object->barcode_type_label);
1080 unset($object->barcode_type_coder);
1081 unset($object->total_ht);
1082 unset($object->total_tva);
1083 unset($object->total_localtax1);
1084 unset($object->total_localtax2);
1085 unset($object->total_ttc);
1086 unset($object->fk_account);
1087 unset($object->comments);
1088 unset($object->note);
1089 unset($object->mode_reglement_id);
1090 unset($object->cond_reglement_id);
1091 unset($object->cond_reglement);
1092 unset($object->shipping_method_id);
1093 unset($object->fk_incoterms);
1094 unset($object->label_incoterms);
1095 unset($object->location_incoterms);
1096
1097 // If object has lines, remove $db property
1098 if (isset($object->lines) && is_array($object->lines) && count($object->lines) > 0) {
1099 $nboflines = count($object->lines);
1100 for ($i = 0; $i < $nboflines; $i++) {
1101 $this->_cleanObjectDatas($object->lines[$i]);
1102
1103 unset($object->lines[$i]->lines);
1104 unset($object->lines[$i]->note);
1105 }
1106 }
1107
1108 return $object;
1109 }
1110
1119 private function _validate($data)
1120 {
1121 $myobject = array();
1122 foreach ($this->mo->fields as $field => $propfield) {
1123 if (in_array($field, array('rowid', 'entity', 'date_creation', 'tms', 'fk_user_creat')) || empty($propfield['notnull']) || $propfield['notnull'] != 1) {
1124 continue; // Not a mandatory field
1125 }
1126 if (!isset($data[$field])) {
1127 throw new RestException(400, "$field field missing");
1128 }
1129 $myobject[$field] = $data[$field];
1130 }
1131 return $myobject;
1132 }
1133
1139 private function checkRefNumbering()
1140 {
1141 $ref = substr($this->mo->ref, 1, 4);
1142 if ($this->mo->status > 0 && $ref == 'PROV') {
1143 throw new RestException(400, "Wrong naming scheme '(PROV%)' is only allowed on 'DRAFT' status. For automatic increment use 'auto' on the 'ref' field.");
1144 }
1145
1146 if (strtolower($this->mo->ref) == 'auto') {
1147 if (empty($this->mo->id) && $this->mo->status == 0) {
1148 $this->mo->ref = ''; // 'ref' will auto incremented with '(PROV' + newID + ')'
1149 } else {
1150 $this->mo->fetch_product();
1151 $numref = $this->mo->getNextNumRef($this->mo->product);
1152 $this->mo->ref = $numref;
1153 }
1154 }
1155 }
1156}
$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 to manage categories.
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 for Mo.
Definition mo.class.php:35
Class MoLine.
produceAndConsume($id, $request_data=null)
Produce and consume.
__construct()
Constructor.
index($sortfield="t.rowid", $sortorder='ASC', $limit=100, $page=0, $sqlfilters='', $properties='')
List Mos.
confirmProduced($id, $notrigger=0)
Close=Confirm Produced MO.
put($id, $request_data=null)
Update MO.
getCategories($id, $sortfield="s.rowid", $sortorder='ASC', $limit=0, $page=0)
Get categories for a MO.
post($request_data=null)
Create MO object.
_cleanObjectDatas($object)
Clean sensible object datas @phpstan-template T.
produceAndConsumeAll($id, $request_data=null)
Produce and consume all.
validate($id, $notrigger=0)
Validate MO.
checkRefNumbering()
Validate the ref field and get the next Number if it's necessary.
_validate($data)
Validate fields before creating or updating an object.
Class to manage stock movements.
Class to manage predefined suppliers products.
Class to manage products or services.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
dol_now($mode='gmt')
Return date for now.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
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.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.