dolibarr 23.0.4
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';
24
25
38class Mos extends DolibarrApi
39{
43 public $mo;
44
48 public function __construct()
49 {
50 global $db, $conf;
51 $this->db = $db;
52 $this->mo = new Mo($this->db);
53 }
54
66 public function get($id)
67 {
68 if (!DolibarrApiAccess::$user->hasRight('mrp', 'read')) {
69 throw new RestException(403);
70 }
71
72 $result = $this->mo->fetch($id);
73 if (!$result) {
74 throw new RestException(404, 'MO not found');
75 }
76
77 if (!DolibarrApi::_checkAccessToResource('mrp', $this->mo->id, 'mrp_mo')) {
78 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
79 }
80
81 return $this->_cleanObjectDatas($this->mo);
82 }
83
84
102 public function index($sortfield = "t.rowid", $sortorder = 'ASC', $limit = 100, $page = 0, $sqlfilters = '', $properties = '')
103 {
104 if (!DolibarrApiAccess::$user->hasRight('mrp', 'read')) {
105 throw new RestException(403);
106 }
107
108 $obj_ret = array();
109 $tmpobject = new Mo($this->db);
110
111 $socid = DolibarrApiAccess::$user->socid ?: 0;
112
113 $restrictonsocid = 1; // Mo::$fields has a 'fk_soc' field
114
115 // If the internal user must only see his customers, force searching by him
116 $search_sale = 0;
117 if ($restrictonsocid && !DolibarrApiAccess::$user->hasRight('societe', 'client', 'voir') && !$socid) {
118 $search_sale = DolibarrApiAccess::$user->id;
119 }
120
121 $sql = "SELECT t.rowid";
122 $sql .= " FROM ".MAIN_DB_PREFIX.$tmpobject->table_element." AS t";
123 $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
124 $sql .= " WHERE 1 = 1";
125 if ($tmpobject->ismultientitymanaged) {
126 $sql .= ' AND t.entity IN ('.getEntity($tmpobject->element).')';
127 }
128 if ($restrictonsocid && $socid) {
129 $sql .= " AND t.fk_soc = ".((int) $socid);
130 }
131 // Search on sale representative
132 if ($search_sale && $search_sale != '-1') {
133 if ($search_sale == -2) {
134 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = t.fk_soc)";
135 } elseif ($search_sale > 0) {
136 // t.fk_soc is optional on Mo (a manufacturing order is not necessarily linked to a thirdparty), so a Mo with no
137 // thirdparty is not restricted by sales representative visibility (there is no customer data to protect on it).
138 $sql .= " AND (t.fk_soc IS NULL OR EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = t.fk_soc AND sc.fk_user = ".((int) $search_sale)."))";
139 }
140 }
141 if ($sqlfilters) {
142 $errormessage = '';
143 $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage);
144 if ($errormessage) {
145 throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage);
146 }
147 }
148
149 $sql .= $this->db->order($sortfield, $sortorder);
150 if ($limit) {
151 if ($page < 0) {
152 $page = 0;
153 }
154 $offset = $limit * $page;
155
156 $sql .= $this->db->plimit($limit + 1, $offset);
157 }
158
159 $result = $this->db->query($sql);
160 if ($result) {
161 $i = 0;
162 $num = $this->db->num_rows($result);
163 $min = min($num, ($limit <= 0 ? $num : $limit));
164 while ($i < $min) {
165 $obj = $this->db->fetch_object($result);
166 $tmp_object = new Mo($this->db);
167 if ($tmp_object->fetch($obj->rowid)) {
168 $obj_ret[] = $this->_filterObjectProperties($this->_cleanObjectDatas($tmp_object), $properties);
169 }
170 $i++;
171 }
172 } else {
173 throw new RestException(503, 'Error when retrieve MO list');
174 }
175
176 return $obj_ret;
177 }
178
189 public function post($request_data = null)
190 {
191 if (!DolibarrApiAccess::$user->hasRight('mrp', 'write')) {
192 throw new RestException(403);
193 }
194 // Check mandatory fields
195 $result = $this->_validate($request_data);
196
197 foreach ($request_data as $field => $value) {
198 if ($field === 'caller') {
199 // 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
200 $this->mo->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
201 continue;
202 }
203
204 $this->mo->$field = $this->_checkValForAPI($field, $value, $this->mo);
205 }
206
207 $this->checkRefNumbering();
208
209 $result = $this->mo->create(DolibarrApiAccess::$user);
210 //var_dump($result);exit;
211 if ($result < 0) {
212 throw new RestException(500, "Error creating MO", array_merge(array($this->mo->error), $this->mo->errors));
213 }
214
215 return $this->mo->id;
216 }
217
227 public function put($id, $request_data = null)
228 {
229 if (!DolibarrApiAccess::$user->hasRight('mrp', 'write')) {
230 throw new RestException(403);
231 }
232
233 $result = $this->mo->fetch($id);
234 if (!$result) {
235 throw new RestException(404, 'MO not found');
236 }
237
238 if (!DolibarrApi::_checkAccessToResource('mrp', $this->mo->id, 'mrp_mo')) {
239 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
240 }
241
242 foreach ($request_data as $field => $value) {
243 if ($field == 'id') {
244 continue;
245 }
246 if ($field === 'caller') {
247 // 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
248 $this->mo->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
249 continue;
250 }
251
252 if ($field == 'array_options' && is_array($value)) {
253 foreach ($value as $index => $val) {
254 $this->mo->array_options[$index] = $this->_checkValForAPI($field, $val, $this->mo);
255 }
256 continue;
257 }
258
259 $this->mo->$field = $this->_checkValForAPI($field, $value, $this->mo);
260 }
261
262 $this->checkRefNumbering();
263
264 if ($this->mo->update(DolibarrApiAccess::$user) > 0) {
265 return $this->get($id);
266 } else {
267 throw new RestException(500, $this->mo->error);
268 }
269 }
270
285 public function validate($id, $notrigger = 0)
286 {
287 if (!DolibarrApiAccess::$user->hasRight('mrp', 'write')) {
288 throw new RestException(403);
289 }
290
291 $result = $this->mo->fetch($id);
292 if (!$result) {
293 throw new RestException(404, 'MO not found');
294 }
295
296 if (!DolibarrApi::_checkAccessToResource('mrp', $this->mo->id, 'mrp_mo')) {
297 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
298 }
299
300 $result = $this->mo->validate(DolibarrApiAccess::$user, $notrigger);
301 if ($result == 0) {
302 throw new RestException(304, 'Error nothing done. May be object is already validated');
303 }
304 if ($result < 0) {
305 throw new RestException(500, 'Error when validating MO: '.$this->mo->error);
306 }
307 $result = $this->mo->fetch($id);
308
309 return $this->_cleanObjectDatas($this->mo);
310 }
311
320 public function delete($id)
321 {
322 if (!DolibarrApiAccess::$user->hasRight('mrp', 'delete')) {
323 throw new RestException(403);
324 }
325 $result = $this->mo->fetch($id);
326 if (!$result) {
327 throw new RestException(404, 'MO not found');
328 }
329
330 if (!DolibarrApi::_checkAccessToResource('mrp', $this->mo->id, 'mrp_mo')) {
331 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
332 }
333
334 if (!$this->mo->delete(DolibarrApiAccess::$user)) {
335 throw new RestException(500, 'Error when deleting MO : '.$this->mo->error);
336 }
337
338 return array(
339 'success' => array(
340 'code' => 200,
341 'message' => 'MO deleted'
342 )
343 );
344 }
345
346
379 public function produceAndConsumeAll($id, $request_data = null)
380 {
381 global $langs;
382
383 $error = 0;
384
385 if (!DolibarrApiAccess::$user->hasRight('mrp', 'write')) {
386 throw new RestException(403, 'Not enough permission');
387 }
388 $result = $this->mo->fetch($id);
389 if (!$result) {
390 throw new RestException(404, 'MO not found');
391 }
392
393 if ($this->mo->status != Mo::STATUS_VALIDATED && $this->mo->status != Mo::STATUS_INPROGRESS) {
394 throw new RestException(405, 'Error bad status of MO');
395 }
396
397 // Code for consume and produce...
398 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
399 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
400 require_once DOL_DOCUMENT_ROOT.'/mrp/lib/mrp_mo.lib.php';
401
402 $stockmove = new MouvementStock($this->db);
403
404 $labelmovement = '';
405 $codemovement = '';
406 $autoclose = 1;
407 $arraytoconsume = array();
408 $arraytoproduce = array();
409
410 foreach ($request_data as $field => $value) {
411 if ($field == 'inventorylabel') {
412 $labelmovement = $value;
413 }
414 if ($field == 'inventorycode') {
415 $codemovement = $value;
416 }
417 if ($field == 'autoclose') {
418 $autoclose = $value;
419 }
420 if ($field == 'arraytoconsume') {
421 $arraytoconsume = $value;
422 }
423 if ($field == 'arraytoproduce') {
424 $arraytoproduce = $value;
425 }
426 if ($field === 'caller') {
427 // 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
428 $stockmove->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
429 continue;
430 }
431 }
432
433 if (empty($labelmovement)) {
434 throw new RestException(500, "Field inventorylabel not provided");
435 }
436 if (empty($codemovement)) {
437 throw new RestException(500, "Field inventorycode not provided");
438 }
439
440 $consumptioncomplete = true;
441 $productioncomplete = true;
442
443 if (!empty($arraytoconsume) && !empty($arraytoproduce)) {
444 $pos = 0;
445 $arrayofarrayname = array("arraytoconsume","arraytoproduce");
446 foreach ($arrayofarrayname as $arrayname) {
447 foreach (${$arrayname} as $value) {
448 $tmpproduct = new Product($this->db);
449 if (empty($value["objectid"])) {
450 throw new RestException(500, "Field objectid required in ".$arrayname);
451 }
452 $tmpproduct->fetch($value["qty"]);
453 if (empty($value["qty"])) {
454 throw new RestException(500, "Field qty required in ".$arrayname);
455 }
456 if ($value["qty"] != 0) {
457 $qtytoprocess = $value["qty"];
458 if (isset($value["fk_warehouse"])) { // If there is a warehouse to set
459 if (!($value["fk_warehouse"] > 0)) { // If there is no warehouse set.
460 $error++;
461 throw new RestException(500, "Field fk_warehouse must be > 0 in ".$arrayname);
462 }
463 if ($tmpproduct->status_batch) {
464 $error++;
465 throw new RestException(500, "Product ".$tmpproduct->ref."must be in batch");
466 }
467 }
468 $idstockmove = 0;
469 if (!$error && $value["fk_warehouse"] > 0) {
470 // Record consumption to do and stock movement
471 $id_product_batch = 0;
472
473 $stockmove->setOrigin($this->mo->element, $this->mo->id);
474
475 if ($arrayname == 'arraytoconsume') {
476 $moline = new MoLine($this->db);
477 $moline->fk_mo = $this->mo->id;
478 $moline->position = $pos;
479 $moline->fk_product = $value["objectid"];
480 $moline->fk_warehouse = (int) $value["fk_warehouse"];
481 $moline->qty = $qtytoprocess;
482 $moline->batch = (string) $tmpproduct->status_batch;
483 $moline->role = 'toproduce';
484 $moline->fk_mrp_production = 0;
485 $moline->fk_stock_movement = $idstockmove;
486 $moline->fk_user_creat = DolibarrApiAccess::$user->id;
487
488 $resultmoline = $moline->create(DolibarrApiAccess::$user);
489 if ($resultmoline <= 0) {
490 $error++;
491 throw new RestException(500, $moline->error);
492 }
493 $idstockmove = $stockmove->livraison(DolibarrApiAccess::$user, $value["objectid"], $value["fk_warehouse"], $qtytoprocess, 0, $labelmovement, dol_now(), '', '', (string) $tmpproduct->status_batch, $id_product_batch, $codemovement);
494 } else {
495 $moline = new MoLine($this->db);
496 $moline->fk_mo = $this->mo->id;
497 $moline->position = $pos;
498 $moline->fk_product = $value["objectid"];
499 $moline->fk_warehouse = $value["fk_warehouse"];
500 $moline->qty = $qtytoprocess;
501 $moline->batch = (string) $tmpproduct->status_batch;
502 $moline->role = 'toconsume';
503 $moline->fk_mrp_production = 0;
504 $moline->fk_stock_movement = $idstockmove;
505 $moline->fk_user_creat = DolibarrApiAccess::$user->id;
506
507 $resultmoline = $moline->create(DolibarrApiAccess::$user);
508 if ($resultmoline <= 0) {
509 $error++;
510 throw new RestException(500, $moline->error);
511 }
512 $idstockmove = $stockmove->reception(DolibarrApiAccess::$user, $value["objectid"], $value["fk_warehouse"], $qtytoprocess, 0, $labelmovement, '', '', (string) $tmpproduct->status_batch, dol_now(), $id_product_batch, $codemovement);
513 }
514 if ($idstockmove < 0) {
515 $error++;
516 throw new RestException(500, $stockmove->error);
517 }
518 }
519 if (!$error) {
520 // Record consumption done
521 $moline = new MoLine($this->db);
522 $moline->fk_mo = $this->mo->id;
523 $moline->position = $pos;
524 $moline->fk_product = $value["objectid"];
525 $moline->fk_warehouse = $value["fk_warehouse"];
526 $moline->qty = $qtytoprocess;
527 $moline->batch = (string) $tmpproduct->status_batch;
528 if ($arrayname == "arraytoconsume") {
529 $moline->role = 'consumed';
530 } else {
531 $moline->role = 'produced';
532 }
533 $moline->fk_mrp_production = 0;
534 $moline->fk_stock_movement = $idstockmove;
535 $moline->fk_user_creat = DolibarrApiAccess::$user->id;
536
537 $resultmoline = $moline->create(DolibarrApiAccess::$user);
538 if ($resultmoline <= 0) {
539 $error++;
540 throw new RestException(500, $moline->error);
541 }
542
543 $pos++;
544 }
545 }
546 }
547 }
548 if (!$error) {
549 if ($autoclose <= 0) {
550 $consumptioncomplete = false;
551 $productioncomplete = false;
552 }
553 }
554 } else {
555 $pos = 0;
556 foreach ($this->mo->lines as $line) {
557 if ($line->role == 'toconsume') {
558 $tmpproduct = new Product($this->db);
559 $tmpproduct->fetch($line->fk_product);
560 if ($line->qty != 0) {
561 $qtytoprocess = $line->qty;
562 if (isset($line->fk_warehouse)) { // If there is a warehouse to set
563 if (!($line->fk_warehouse > 0)) { // If there is no warehouse set.
564 $langs->load("errors");
565 $error++;
566 throw new RestException(500, $langs->trans("ErrorFieldRequiredForProduct", $langs->transnoentitiesnoconv("Warehouse"), $tmpproduct->ref));
567 }
568 if ($tmpproduct->status_batch) {
569 $langs->load("errors");
570 $error++;
571 throw new RestException(500, $langs->trans("ErrorFieldRequiredForProduct", $langs->transnoentitiesnoconv("Batch"), $tmpproduct->ref));
572 }
573 }
574 $idstockmove = 0;
575 if (!$error && $line->fk_warehouse > 0) {
576 // Record stock movement
577 $id_product_batch = 0;
578 $stockmove->origin_type = 'mo';
579 $stockmove->origin_id = $this->mo->id;
580 if ($qtytoprocess >= 0) {
581 $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);
582 } else {
583 $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);
584 }
585 if ($idstockmove < 0) {
586 $error++;
587 throw new RestException(500, $stockmove->error);
588 }
589 }
590 if (!$error) {
591 // Record consumption
592 $moline = new MoLine($this->db);
593 $moline->fk_mo = $this->mo->id;
594 $moline->position = $pos;
595 $moline->fk_product = $line->fk_product;
596 $moline->fk_warehouse = $line->fk_warehouse;
597 $moline->qty = $qtytoprocess;
598 $moline->batch = (string) $tmpproduct->status_batch;
599 $moline->role = 'consumed';
600 $moline->fk_mrp_production = $line->id;
601 $moline->fk_stock_movement = $idstockmove;
602 $moline->fk_user_creat = DolibarrApiAccess::$user->id;
603
604 $resultmoline = $moline->create(DolibarrApiAccess::$user);
605 if ($resultmoline <= 0) {
606 $error++;
607 throw new RestException(500, $moline->error);
608 }
609
610 $pos++;
611 }
612 }
613 }
614 }
615 $pos = 0;
616 foreach ($this->mo->lines as $line) {
617 if ($line->role == 'toproduce') {
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"), $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"), $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->reception(DolibarrApiAccess::$user, $line->fk_product, (int) $line->fk_warehouse, $qtytoprocess, 0, $labelmovement, '', '', (string) $tmpproduct->status_batch, dol_now(), $id_product_batch, $codemovement);
642 } else {
643 $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);
644 }
645 if ($idstockmove < 0) {
646 $error++;
647 throw new RestException(500, $stockmove->error);
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 = 'produced';
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->error);
668 }
669
670 $pos++;
671 }
672 }
673 }
674 }
675
676 if (!$error) {
677 if ($autoclose > 0) {
678 foreach ($this->mo->lines as $line) {
679 if ($line->role == 'toconsume') {
680 $arrayoflines = $this->mo->fetchLinesLinked('consumed', $line->id);
681 $alreadyconsumed = 0;
682 foreach ($arrayoflines as $line2) {
683 $alreadyconsumed += $line2['qty'];
684 }
685
686 if ($alreadyconsumed < $line->qty) {
687 $consumptioncomplete = false;
688 }
689 }
690 if ($line->role == 'toproduce') {
691 $arrayoflines = $this->mo->fetchLinesLinked('produced', $line->id);
692 $alreadyproduced = 0;
693 foreach ($arrayoflines as $line2) {
694 $alreadyproduced += $line2['qty'];
695 }
696
697 if ($alreadyproduced < $line->qty) {
698 $productioncomplete = false;
699 }
700 }
701 }
702 } else {
703 $consumptioncomplete = false;
704 $productioncomplete = false;
705 }
706 }
707 }
708
709 // Update status of MO
710 dol_syslog("consumptioncomplete = ".json_encode($consumptioncomplete)." productioncomplete = ".json_encode($productioncomplete));
711 if ($consumptioncomplete && $productioncomplete) {
712 $result = $this->mo->setStatut(Mo::STATUS_PRODUCED, 0, '', 'MRP_MO_PRODUCED');
713 } else {
714 $result = $this->mo->setStatut(Mo::STATUS_INPROGRESS, 0, '', 'MRP_MO_PRODUCED');
715 }
716 if ($result <= 0) {
717 throw new RestException(500, $this->mo->error);
718 }
719
720 return $this->mo->id;
721 }
722
757 public function produceAndConsume($id, $request_data = null)
758 {
759 if (!DolibarrApiAccess::$user->hasRight("mrp", "write")) {
760 throw new RestException(403, 'Not enough permission');
761 }
762 $result = $this->mo->fetch($id);
763 if (!$result) {
764 throw new RestException(404, 'MO not found');
765 }
766
767 if ($this->mo->status != Mo::STATUS_VALIDATED && $this->mo->status != Mo::STATUS_INPROGRESS) {
768 throw new RestException(405, 'Error bad status of MO');
769 }
770
771 // Code for consume and produce...
772 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
773 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
774 require_once DOL_DOCUMENT_ROOT.'/mrp/lib/mrp_mo.lib.php';
775
776 $stockmove = new MouvementStock($this->db);
777
778 $labelmovement = '';
779 $codemovement = '';
780 $autoclose = 1;
781 $arraytoconsume = array();
782 $arraytoproduce = array();
783
784 foreach ($request_data as $field => $value) {
785 if ($field == 'inventorylabel') {
786 $labelmovement = $value;
787 }
788 if ($field == 'inventorycode') {
789 $codemovement = $value;
790 }
791 if ($field == 'autoclose') {
792 $autoclose = $value;
793 }
794 if ($field == 'arraytoconsume') {
795 $arraytoconsume = $value;
796 }
797 if ($field == 'arraytoproduce') {
798 $arraytoproduce = $value;
799 }
800 if ($field === 'caller') {
801 // 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
802 $stockmove->context['caller'] = $request_data['caller'];
803 continue;
804 }
805 }
806
807 if (empty($labelmovement)) {
808 throw new RestException(500, "Field inventorylabel not provided");
809 }
810 if (empty($codemovement)) {
811 throw new RestException(500, "Field inventorycode not provided");
812 }
813
814 $this->db->begin();
815
816 $pos = 0;
817 $arrayofarrayname = array("arraytoconsume","arraytoproduce");
818 foreach ($arrayofarrayname as $arrayname) {
819 foreach (${$arrayname} as $value) {
820 if (empty($value["objectid"])) {
821 throw new RestException(500, "Field objectid required in " . $arrayname);
822 }
823
824 $molinetoprocess = new MoLine($this->db);
825 $tmpmolineid = $molinetoprocess->fetch($value["objectid"]);
826 if ($tmpmolineid <= 0) {
827 throw new RestException(500, "MoLine with rowid " . $value["objectid"] . " not exist.");
828 }
829
830 $tmpproduct = new Product($this->db);
831 $tmpproduct->fetch($molinetoprocess->fk_product);
832 if ($tmpproduct->status_batch) {
833 throw new RestException(500, "Product " . $tmpproduct->ref . " must be in batch, this API can't handle it currently.");
834 }
835
836 if (empty($value["qty"]) && $value["qty"] != 0) {
837 throw new RestException(500, "Field qty with lower or higher then 0 required in " . $arrayname);
838 }
839 $qtytoprocess = $value["qty"];
840
841 $fk_warehousetoprocess = 0;
842 if ($molinetoprocess->disable_stock_change == false) {
843 if (isset($value["fk_warehouse"])) { // If there is a warehouse to set
844 if (!($value["fk_warehouse"] > 0)) { // If there is no warehouse set.
845 throw new RestException(500, "Field fk_warehouse required in " . $arrayname);
846 }
847 }
848 $fk_warehousetoprocess = (int) $value["fk_warehouse"];
849 }
850
851 $pricetoproduce = 0;
852 if (isset($value["pricetoproduce"])) { // If there is a price to produce set.
853 if ($value["pricetoproduce"] > 0) { // Only use prices grater then 0.
854 $pricetoproduce = $value["pricetoproduce"];
855 }
856 }
857
858 $idstockmove = 0;
859
860 if ($molinetoprocess->disable_stock_change == false) {
861 // Record stock movement
862 $id_product_batch = 0;
863 $stockmove->origin_type = 'mo';
864 $stockmove->origin_id = $this->mo->id;
865 if ($arrayname == "arraytoconsume") {
866 if ($qtytoprocess >= 0) {
867 $idstockmove = $stockmove->livraison(DolibarrApiAccess::$user, $molinetoprocess->fk_product, $fk_warehousetoprocess, $qtytoprocess, 0, $labelmovement, dol_now(), '', '', (string) $tmpproduct->status_batch, $id_product_batch, $codemovement);
868 } else {
869 $idstockmove = $stockmove->reception(DolibarrApiAccess::$user, $molinetoprocess->fk_product, $fk_warehousetoprocess, $qtytoprocess, 0, $labelmovement, '', '', (string) $tmpproduct->status_batch, dol_now(), $id_product_batch, $codemovement);
870 }
871 } else {
872 if ($qtytoprocess >= 0) {
873 $idstockmove = $stockmove->reception(DolibarrApiAccess::$user, $molinetoprocess->fk_product, $fk_warehousetoprocess, $qtytoprocess, $pricetoproduce, $labelmovement, '', '', (string) $tmpproduct->status_batch, dol_now(), $id_product_batch, $codemovement);
874 } else {
875 $idstockmove = $stockmove->livraison(DolibarrApiAccess::$user, $molinetoprocess->fk_product, $fk_warehousetoprocess, $qtytoprocess, 0, $labelmovement, dol_now(), '', '', (string) $tmpproduct->status_batch, $id_product_batch, $codemovement);
876 }
877 }
878 if ($idstockmove <= 0) {
879 throw new RestException(500, $stockmove->error);
880 }
881 }
882
883 // Record consumption
884 $moline = new MoLine($this->db);
885 $moline->fk_mo = $this->mo->id;
886 $moline->position = $pos;
887 $moline->fk_product = $tmpproduct->id;
888 $moline->fk_warehouse = $idstockmove > 0 ? $fk_warehousetoprocess : null;
889 $moline->qty = $qtytoprocess;
890 $moline->batch = '';
891 $moline->fk_mrp_production = $molinetoprocess->id;
892 $moline->fk_stock_movement = $idstockmove > 0 ? $idstockmove : null;
893 $moline->fk_user_creat = DolibarrApiAccess::$user->id;
894
895 if ($arrayname == "arraytoconsume") {
896 $moline->role = 'consumed';
897 } else {
898 $moline->role = 'produced';
899 }
900
901 $resultmoline = $moline->create(DolibarrApiAccess::$user);
902 if ($resultmoline <= 0) {
903 throw new RestException(500, $moline->error);
904 }
905
906 $pos++;
907 }
908 }
909
910 $consumptioncomplete = true;
911 $productioncomplete = true;
912
913 if ($autoclose > 0) {
914 // Refresh Lines after consumptions.
915 $this->mo->fetchLines();
916
917 foreach ($this->mo->lines as $line) {
918 if ($line->role == 'toconsume') {
919 $arrayoflines = $this->mo->fetchLinesLinked('consumed', $line->id);
920 $alreadyconsumed = 0;
921 foreach ($arrayoflines as $line2) {
922 $alreadyconsumed += $line2['qty'];
923 }
924
925 if ($alreadyconsumed < $line->qty) {
926 $consumptioncomplete = false;
927 }
928 }
929 if ($line->role == 'toproduce') {
930 $arrayoflines = $this->mo->fetchLinesLinked('produced', $line->id);
931 $alreadyproduced = 0;
932 foreach ($arrayoflines as $line2) {
933 $alreadyproduced += $line2['qty'];
934 }
935
936 if ($alreadyproduced < $line->qty) {
937 $productioncomplete = false;
938 }
939 }
940 }
941 } else {
942 $consumptioncomplete = false;
943 $productioncomplete = false;
944 }
945
946 // Update status of MO
947 dol_syslog("consumptioncomplete = " . (string) $consumptioncomplete . " productioncomplete = " . (string) $productioncomplete);
948 //var_dump("consumptioncomplete = ".$consumptioncomplete." productioncomplete = ".$productioncomplete);
949 if ($consumptioncomplete && $productioncomplete) {
950 $result = $this->mo->setStatut(Mo::STATUS_PRODUCED, 0, '', 'MRP_MO_PRODUCED');
951 } else {
952 $result = $this->mo->setStatut(Mo::STATUS_INPROGRESS, 0, '', 'MRP_MO_PRODUCED');
953 }
954 if ($result <= 0) {
955 throw new RestException(500, $this->mo->error);
956 }
957
958 $this->db->commit();
959 return $this->mo->id;
960 }
961
962
963 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
973 protected function _cleanObjectDatas($object)
974 {
975 // phpcs:enable
976 $object = parent::_cleanObjectDatas($object);
977
978 unset($object->rowid);
979 unset($object->canvas);
980
981 unset($object->name);
982 unset($object->lastname);
983 unset($object->firstname);
984 unset($object->civility_id);
985 unset($object->statut);
986 unset($object->state);
987 unset($object->state_id);
988 unset($object->state_code);
989 unset($object->region);
990 unset($object->region_code);
991 unset($object->country);
992 unset($object->country_id);
993 unset($object->country_code);
994 unset($object->barcode_type);
995 unset($object->barcode_type_code);
996 unset($object->barcode_type_label);
997 unset($object->barcode_type_coder);
998 unset($object->total_ht);
999 unset($object->total_tva);
1000 unset($object->total_localtax1);
1001 unset($object->total_localtax2);
1002 unset($object->total_ttc);
1003 unset($object->fk_account);
1004 unset($object->comments);
1005 unset($object->note);
1006 unset($object->mode_reglement_id);
1007 unset($object->cond_reglement_id);
1008 unset($object->cond_reglement);
1009 unset($object->shipping_method_id);
1010 unset($object->fk_incoterms);
1011 unset($object->label_incoterms);
1012 unset($object->location_incoterms);
1013
1014 // If object has lines, remove $db property
1015 if (isset($object->lines) && is_array($object->lines) && count($object->lines) > 0) {
1016 $nboflines = count($object->lines);
1017 for ($i = 0; $i < $nboflines; $i++) {
1018 $this->_cleanObjectDatas($object->lines[$i]);
1019
1020 unset($object->lines[$i]->lines);
1021 unset($object->lines[$i]->note);
1022 }
1023 }
1024
1025 return $object;
1026 }
1027
1036 private function _validate($data)
1037 {
1038 $myobject = array();
1039 foreach ($this->mo->fields as $field => $propfield) {
1040 if (in_array($field, array('rowid', 'entity', 'date_creation', 'tms', 'fk_user_creat')) || empty($propfield['notnull']) || $propfield['notnull'] != 1) {
1041 continue; // Not a mandatory field
1042 }
1043 if (!isset($data[$field])) {
1044 throw new RestException(400, "$field field missing");
1045 }
1046 $myobject[$field] = $data[$field];
1047 }
1048 return $myobject;
1049 }
1050
1056 private function checkRefNumbering()
1057 {
1058 $ref = substr($this->mo->ref, 1, 4);
1059 if ($this->mo->status > 0 && $ref == 'PROV') {
1060 throw new RestException(400, "Wrong naming scheme '(PROV%)' is only allowed on 'DRAFT' status. For automatic increment use 'auto' on the 'ref' field.");
1061 }
1062
1063 if (strtolower($this->mo->ref) == 'auto') {
1064 if (empty($this->mo->id) && $this->mo->status == 0) {
1065 $this->mo->ref = ''; // 'ref' will auto incremented with '(PROV' + newID + ')'
1066 } else {
1067 $this->mo->fetch_product();
1068 $numref = $this->mo->getNextNumRef($this->mo->product);
1069 $this->mo->ref = $numref;
1070 }
1071 }
1072 }
1073}
$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:33
_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.
Definition api.class.php:98
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.
put($id, $request_data=null)
Update 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 products or services.
dol_now($mode='gmt')
Return date for now.
forgeSQLFromUniversalSearchCriteria($filter, &$errorstr='', $noand=0, $nopar=0, $noerror=0)
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.