dolibarr 24.0.2
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';
25
26
39class Mos extends DolibarrApi
40{
44 public $mo;
45
49 public function __construct()
50 {
51 global $db, $conf;
52 $this->db = $db;
53 $this->mo = new Mo($this->db);
54 }
55
67 public function get($id)
68 {
69 if (!DolibarrApiAccess::$user->hasRight('mrp', 'read')) {
70 throw new RestException(403);
71 }
72
73 $result = $this->mo->fetch($id);
74 if (!$result) {
75 throw new RestException(404, 'MO not found');
76 }
77
78 if (!DolibarrApi::_checkAccessToResource('mrp', $this->mo->id, 'mrp_mo')) {
79 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
80 }
81
82 return $this->_cleanObjectDatas($this->mo);
83 }
84
102 public function getCategories($id, $sortfield = "s.rowid", $sortorder = 'ASC', $limit = 0, $page = 0)
103 {
104 if (!DolibarrApiAccess::$user->hasRight('categorie', 'lire')) {
105 throw new RestException(403);
106 }
107
108 $categories = new Categorie($this->db);
109
110 $result = $categories->getListForItem($id, Categorie::TYPE_MO, $sortfield, $sortorder, $limit, $page);
111
112 if ($result < 0) {
113 throw new RestException(503, 'Error when retrieve category list : ' . implode(',', array_merge(array($categories->error), $categories->errors)));
114 }
115
116 return $result;
117 }
118
119
137 public function index($sortfield = "t.rowid", $sortorder = 'ASC', $limit = 100, $page = 0, $sqlfilters = '', $properties = '')
138 {
139 if (!DolibarrApiAccess::$user->hasRight('mrp', 'read')) {
140 throw new RestException(403);
141 }
142
143 $obj_ret = array();
144 $tmpobject = new Mo($this->db);
145
146 $socid = DolibarrApiAccess::$user->socid ?: 0;
147
148 $restrictonsocid = 1; // Mo::$fields has a 'fk_soc' field
149
150 // If the internal user must only see his customers, force searching by him
151 $search_sale = 0;
152 if ($restrictonsocid && !DolibarrApiAccess::$user->hasRight('societe', 'client', 'voir') && !$socid) {
153 $search_sale = DolibarrApiAccess::$user->id;
154 }
155
156 $sql = "SELECT t.rowid";
157 $sql .= " FROM ".MAIN_DB_PREFIX.$tmpobject->table_element." AS t";
158 $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
159 $sql .= " WHERE 1 = 1";
160 if ($tmpobject->ismultientitymanaged) {
161 $sql .= ' AND t.entity IN ('.getEntity($tmpobject->element).')';
162 }
163 if ($restrictonsocid && $socid) {
164 $sql .= " AND t.fk_soc = ".((int) $socid);
165 }
166 // Search on sale representative
167 if ($search_sale && $search_sale != '-1') {
168 if ($search_sale == -2) {
169 $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM ".MAIN_DB_PREFIX."societe_commerciaux as sc WHERE sc.fk_soc = t.fk_soc)";
170 } elseif ($search_sale > 0) {
171 // t.fk_soc is optional on Mo (a manufacturing order is not necessarily linked to a thirdparty), so a Mo with no
172 // thirdparty is not restricted by sales representative visibility (there is no customer data to protect on it).
173 $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)."))";
174 }
175 }
176 if ($sqlfilters) {
177 $errormessage = '';
178 $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage);
179 if ($errormessage) {
180 throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage);
181 }
182 }
183
184 $sql .= $this->db->order($sortfield, $sortorder);
185 if ($limit) {
186 if ($page < 0) {
187 $page = 0;
188 }
189 $offset = $limit * $page;
190
191 $sql .= $this->db->plimit($limit + 1, $offset);
192 }
193
194 $result = $this->db->query($sql);
195 if ($result) {
196 $i = 0;
197 $num = $this->db->num_rows($result);
198 $min = min($num, ($limit <= 0 ? $num : $limit));
199 while ($i < $min) {
200 $obj = $this->db->fetch_object($result);
201 $tmp_object = new Mo($this->db);
202 if ($tmp_object->fetch($obj->rowid)) {
203 $obj_ret[] = $this->_filterObjectProperties($this->_cleanObjectDatas($tmp_object), $properties);
204 }
205 $i++;
206 }
207 } else {
208 throw new RestException(503, 'Error when retrieve MO list');
209 }
210
211 return $obj_ret;
212 }
213
224 public function post($request_data = null)
225 {
226 if (!DolibarrApiAccess::$user->hasRight('mrp', 'write')) {
227 throw new RestException(403);
228 }
229 // Check mandatory fields
230 $result = $this->_validate($request_data);
231
232 foreach ($request_data as $field => $value) {
233 if ($field === 'caller') {
234 // 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
235 $this->mo->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
236 continue;
237 }
238
239 $this->mo->$field = $this->_checkValForAPI($field, $value, $this->mo);
240 }
241
242 $this->checkRefNumbering();
243
244 $result = $this->mo->create(DolibarrApiAccess::$user);
245 //var_dump($result);exit;
246 if ($result < 0) {
247 throw new RestException(500, "Error creating MO", array_merge(array($this->mo->error), $this->mo->errors));
248 }
249
250 return $this->mo->id;
251 }
252
262 public function put($id, $request_data = null)
263 {
264 if (!DolibarrApiAccess::$user->hasRight('mrp', 'write')) {
265 throw new RestException(403);
266 }
267
268 $result = $this->mo->fetch($id);
269 if (!$result) {
270 throw new RestException(404, 'MO not found');
271 }
272
273 if (!DolibarrApi::_checkAccessToResource('mrp', $this->mo->id, 'mrp_mo')) {
274 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
275 }
276
277 foreach ($request_data as $field => $value) {
278 if ($field == 'id') {
279 continue;
280 }
281 if ($field === 'caller') {
282 // 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
283 $this->mo->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
284 continue;
285 }
286
287 if ($field == 'array_options' && is_array($value)) {
288 foreach ($value as $index => $val) {
289 $this->mo->array_options[$index] = $this->_checkValExtrafieldsForAPI($index, $val, $this->mo);
290 }
291 continue;
292 }
293
294 $this->mo->$field = $this->_checkValForAPI($field, $value, $this->mo);
295 }
296
297 $this->checkRefNumbering();
298
299 if ($this->mo->update(DolibarrApiAccess::$user) > 0) {
300 return $this->get($id);
301 } else {
302 throw new RestException(500, $this->mo->error);
303 }
304 }
305
320 public function validate($id, $notrigger = 0)
321 {
322 if (!DolibarrApiAccess::$user->hasRight('mrp', 'write')) {
323 throw new RestException(403);
324 }
325
326 $result = $this->mo->fetch($id);
327 if (!$result) {
328 throw new RestException(404, 'MO not found');
329 }
330
331 if (!DolibarrApi::_checkAccessToResource('mrp', $this->mo->id, 'mrp_mo')) {
332 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
333 }
334
335 $result = $this->mo->validate(DolibarrApiAccess::$user, $notrigger);
336 if ($result == 0) {
337 throw new RestException(304, 'Error nothing done. May be object is already validated');
338 }
339 if ($result < 0) {
340 throw new RestException(500, 'Error when validating MO: '.$this->mo->error);
341 }
342 $result = $this->mo->fetch($id);
343
344 return $this->_cleanObjectDatas($this->mo);
345 }
346
355 public function delete($id)
356 {
357 if (!DolibarrApiAccess::$user->hasRight('mrp', 'delete')) {
358 throw new RestException(403);
359 }
360 $result = $this->mo->fetch($id);
361 if (!$result) {
362 throw new RestException(404, 'MO not found');
363 }
364
365 if (!DolibarrApi::_checkAccessToResource('mrp', $this->mo->id, 'mrp_mo')) {
366 throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
367 }
368
369 if (!$this->mo->delete(DolibarrApiAccess::$user)) {
370 throw new RestException(500, 'Error when deleting MO : '.$this->mo->error);
371 }
372
373 return array(
374 'success' => array(
375 'code' => 200,
376 'message' => 'MO deleted'
377 )
378 );
379 }
380
381
414 public function produceAndConsumeAll($id, $request_data = null)
415 {
416 global $langs;
417
418 $error = 0;
419
420 if (!DolibarrApiAccess::$user->hasRight('mrp', 'write')) {
421 throw new RestException(403, 'Not enough permission');
422 }
423 $result = $this->mo->fetch($id);
424 if (!$result) {
425 throw new RestException(404, 'MO not found');
426 }
427
428 if ($this->mo->status != Mo::STATUS_VALIDATED && $this->mo->status != Mo::STATUS_INPROGRESS) {
429 throw new RestException(405, 'Error bad status of MO');
430 }
431
432 // Code for consume and produce...
433 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
434 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
435 require_once DOL_DOCUMENT_ROOT.'/mrp/lib/mrp_mo.lib.php';
436
437 $stockmove = new MouvementStock($this->db);
438
439 $labelmovement = '';
440 $codemovement = '';
441 $autoclose = 1;
442 $arraytoconsume = array();
443 $arraytoproduce = array();
444
445 foreach ($request_data as $field => $value) {
446 if ($field == 'inventorylabel') {
447 $labelmovement = $value;
448 }
449 if ($field == 'inventorycode') {
450 $codemovement = $value;
451 }
452 if ($field == 'autoclose') {
453 $autoclose = $value;
454 }
455 if ($field == 'arraytoconsume') {
456 $arraytoconsume = $value;
457 }
458 if ($field == 'arraytoproduce') {
459 $arraytoproduce = $value;
460 }
461 if ($field === 'caller') {
462 // 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
463 $stockmove->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
464 continue;
465 }
466 }
467
468 if (empty($labelmovement)) {
469 throw new RestException(500, "Field inventorylabel not provided");
470 }
471 if (empty($codemovement)) {
472 throw new RestException(500, "Field inventorycode not provided");
473 }
474
475 $consumptioncomplete = true;
476 $productioncomplete = true;
477
478 if (!empty($arraytoconsume) && !empty($arraytoproduce)) {
479 $pos = 0;
480 $arrayofarrayname = array("arraytoconsume", "arraytoproduce");
481 foreach ($arrayofarrayname as $arrayname) {
482 foreach (${$arrayname} as $value) {
483 $tmpproduct = new Product($this->db);
484 if (empty($value["objectid"])) {
485 throw new RestException(500, "Field objectid required in ".$arrayname);
486 }
487 $tmpproduct->fetch($value["objectid"]);
488 if (empty($value["qty"])) {
489 throw new RestException(500, "Field qty required in ".$arrayname);
490 }
491 if ($value["qty"] != 0) {
492 $qtytoprocess = $value["qty"];
493 if (isset($value["fk_warehouse"])) { // If there is a warehouse to set
494 if (!($value["fk_warehouse"] > 0)) { // If there is no warehouse set.
495 $error++;
496 throw new RestException(500, "Field fk_warehouse must be > 0 in ".$arrayname);
497 }
498 if ($tmpproduct->status_batch) {
499 $error++;
500 throw new RestException(500, "Product ".$tmpproduct->ref."must be in batch");
501 }
502 }
503 $idstockmove = 0;
504 if (!$error && $value["fk_warehouse"] > 0) {
505 // Record consumption to do and stock movement
506 $id_product_batch = 0;
507
508 $stockmove->setOrigin($this->mo->element, $this->mo->id);
509
510 // Record the stock movement first: the line below stores its ID, and
511 // llx_mrp_production.fk_stock_movement is a foreign key on llx_stock_mouvement.
512 if ($arrayname == 'arraytoconsume') {
513 $idstockmove = $stockmove->livraison(DolibarrApiAccess::$user, $value["objectid"], $value["fk_warehouse"], $qtytoprocess, 0, $labelmovement, dol_now(), '', '', (string) $tmpproduct->status_batch, $id_product_batch, $codemovement);
514 } else {
515 $idstockmove = $stockmove->reception(DolibarrApiAccess::$user, $value["objectid"], $value["fk_warehouse"], $qtytoprocess, 0, $labelmovement, '', '', (string) $tmpproduct->status_batch, dol_now(), $id_product_batch, $codemovement);
516 }
517 if ($idstockmove < 0) {
518 $error++;
519 throw new RestException(500, $stockmove->error);
520 }
521
522 $moline = new MoLine($this->db);
523 $moline->fk_mo = $this->mo->id;
524 $moline->position = $pos;
525 $moline->fk_product = $value["objectid"];
526 $moline->fk_warehouse = (int) $value["fk_warehouse"];
527 $moline->qty = $qtytoprocess;
528 $moline->batch = (string) $tmpproduct->status_batch;
529 $moline->role = ($arrayname == 'arraytoconsume' ? 'toproduce' : 'toconsume');
530 $moline->fk_mrp_production = 0;
531 $moline->fk_stock_movement = $idstockmove > 0 ? $idstockmove : null;
532 $moline->fk_user_creat = DolibarrApiAccess::$user->id;
533
534 $resultmoline = $moline->create(DolibarrApiAccess::$user);
535 if ($resultmoline <= 0) {
536 $error++;
537 throw new RestException(500, $moline->error ? $moline->error : implode(', ', $moline->errors));
538 }
539 }
540 if (!$error) {
541 // Record consumption done
542 $moline = new MoLine($this->db);
543 $moline->fk_mo = $this->mo->id;
544 $moline->position = $pos;
545 $moline->fk_product = $value["objectid"];
546 $moline->fk_warehouse = $value["fk_warehouse"];
547 $moline->qty = $qtytoprocess;
548 $moline->batch = (string) $tmpproduct->status_batch;
549 if ($arrayname == "arraytoconsume") {
550 $moline->role = 'consumed';
551 } else {
552 $moline->role = 'produced';
553 }
554 $moline->fk_mrp_production = 0;
555 $moline->fk_stock_movement = $idstockmove > 0 ? $idstockmove : null;
556 $moline->fk_user_creat = DolibarrApiAccess::$user->id;
557
558 $resultmoline = $moline->create(DolibarrApiAccess::$user);
559 if ($resultmoline <= 0) {
560 $error++;
561 throw new RestException(500, $moline->error ? $moline->error : implode(', ', $moline->errors));
562 }
563
564 $pos++;
565 }
566 }
567 }
568 }
569 if (!$error) {
570 if ($autoclose <= 0) {
571 $consumptioncomplete = false;
572 $productioncomplete = false;
573 }
574 }
575 } else {
576 $pos = 0;
577 foreach ($this->mo->lines as $line) {
578 if ($line->role == 'toconsume') {
579 $tmpproduct = new Product($this->db);
580 $tmpproduct->fetch($line->fk_product);
581 if ($line->qty != 0) {
582 $qtytoprocess = $line->qty;
583 if (isset($line->fk_warehouse)) { // If there is a warehouse to set
584 if (!($line->fk_warehouse > 0)) { // If there is no warehouse set.
585 $langs->load("errors");
586 $error++;
587 throw new RestException(500, $langs->trans("ErrorFieldRequiredForProduct", $langs->transnoentitiesnoconv("Warehouse"), (string) $tmpproduct->ref));
588 }
589 if ($tmpproduct->status_batch) {
590 $langs->load("errors");
591 $error++;
592 throw new RestException(500, $langs->trans("ErrorFieldRequiredForProduct", $langs->transnoentitiesnoconv("Batch"), (string) $tmpproduct->ref));
593 }
594 }
595 $idstockmove = 0;
596 if (!$error && $line->fk_warehouse > 0) {
597 // Record stock movement
598 $id_product_batch = 0;
599 $stockmove->origin_type = 'mo';
600 $stockmove->origin_id = $this->mo->id;
601 if ($qtytoprocess >= 0) {
602 $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);
603 } else {
604 $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);
605 }
606 if ($idstockmove < 0) {
607 $error++;
608 throw new RestException(500, $stockmove->error);
609 }
610 }
611 if (!$error) {
612 // Record consumption
613 $moline = new MoLine($this->db);
614 $moline->fk_mo = $this->mo->id;
615 $moline->position = $pos;
616 $moline->fk_product = $line->fk_product;
617 $moline->fk_warehouse = $line->fk_warehouse;
618 $moline->qty = $qtytoprocess;
619 $moline->batch = (string) $tmpproduct->status_batch;
620 $moline->role = 'consumed';
621 $moline->fk_mrp_production = $line->id;
622 $moline->fk_stock_movement = $idstockmove;
623 $moline->fk_user_creat = DolibarrApiAccess::$user->id;
624
625 $resultmoline = $moline->create(DolibarrApiAccess::$user);
626 if ($resultmoline <= 0) {
627 $error++;
628 throw new RestException(500, $moline->error);
629 }
630
631 $pos++;
632 }
633 }
634 }
635 }
636 $pos = 0;
637 foreach ($this->mo->lines as $line) {
638 if ($line->role == 'toproduce') {
639 $tmpproduct = new Product($this->db);
640 $tmpproduct->fetch($line->fk_product);
641 if ($line->qty != 0) {
642 $qtytoprocess = $line->qty;
643 if (isset($line->fk_warehouse)) { // If there is a warehouse to set
644 if (!($line->fk_warehouse > 0)) { // If there is no warehouse set.
645 $langs->load("errors");
646 $error++;
647 throw new RestException(500, $langs->trans("ErrorFieldRequiredForProduct", $langs->transnoentitiesnoconv("Warehouse"), (string) $tmpproduct->ref));
648 }
649 if ($tmpproduct->status_batch) {
650 $langs->load("errors");
651 $error++;
652 throw new RestException(500, $langs->trans("ErrorFieldRequiredForProduct", $langs->transnoentitiesnoconv("Batch"), (string) $tmpproduct->ref));
653 }
654 }
655 $idstockmove = 0;
656 if (!$error && $line->fk_warehouse > 0) {
657 // Record stock movement
658 $id_product_batch = 0;
659 $stockmove->origin_type = 'mo';
660 $stockmove->origin_id = $this->mo->id;
661 if ($qtytoprocess >= 0) {
662 // Entering the produced goods into stock is the only movement that may carry
663 // a value: the manufacturing cost of one unit, that is the sum of qty * unit
664 // cost over the lines to consume, divided by the quantity produced. Unit cost
665 // follows the same priority chain as the web UI: cost_price, then pmp, then
666 // the lowest supplier price. Computed here, inside the stock entry branch, so
667 // it can never reach a stock exit.
668 $mfgcost = 0;
669 foreach ($this->mo->lines as $consumedline) {
670 if ($consumedline->role == 'toconsume') {
671 $consumedproduct = new Product($this->db);
672 $consumedproduct->fetch($consumedline->fk_product);
673 $consumedcost = price2num(!empty($consumedproduct->cost_price) ? $consumedproduct->cost_price : $consumedproduct->pmp);
674 if (empty($consumedcost)) {
675 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
676 $productFournisseur = new ProductFournisseur($this->db);
677 if ($productFournisseur->find_min_price_product_fournisseur($consumedline->fk_product, $consumedline->qty) > 0) {
678 $consumedcost = $productFournisseur->fourn_unitprice;
679 }
680 }
681 $mfgcost += price2num(($consumedline->qty * $consumedcost) / ($this->mo->qty > 0 ? $this->mo->qty : 1), 'MU');
682 }
683 }
684 $mfgcost = (float) price2num($mfgcost, 'MU');
685 $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);
686 } else {
687 $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);
688 }
689 if ($idstockmove < 0) {
690 $error++;
691 throw new RestException(500, $stockmove->error);
692 }
693 }
694 if (!$error) {
695 // Record consumption
696 $moline = new MoLine($this->db);
697 $moline->fk_mo = $this->mo->id;
698 $moline->position = $pos;
699 $moline->fk_product = $line->fk_product;
700 $moline->fk_warehouse = $line->fk_warehouse;
701 $moline->qty = $qtytoprocess;
702 $moline->batch = (string) $tmpproduct->status_batch;
703 $moline->role = 'produced';
704 $moline->fk_mrp_production = $line->id;
705 $moline->fk_stock_movement = $idstockmove;
706 $moline->fk_user_creat = DolibarrApiAccess::$user->id;
707
708 $resultmoline = $moline->create(DolibarrApiAccess::$user);
709 if ($resultmoline <= 0) {
710 $error++;
711 throw new RestException(500, $moline->error);
712 }
713
714 $pos++;
715 }
716 }
717 }
718 }
719
720 if (!$error) {
721 if ($autoclose > 0) {
722 foreach ($this->mo->lines as $line) {
723 if ($line->role == 'toconsume') {
724 $arrayoflines = $this->mo->fetchLinesLinked('consumed', $line->id);
725 $alreadyconsumed = 0;
726 foreach ($arrayoflines as $line2) {
727 $alreadyconsumed += $line2['qty'];
728 }
729
730 if ($alreadyconsumed < $line->qty) {
731 $consumptioncomplete = false;
732 }
733 }
734 if ($line->role == 'toproduce') {
735 $arrayoflines = $this->mo->fetchLinesLinked('produced', $line->id);
736 $alreadyproduced = 0;
737 foreach ($arrayoflines as $line2) {
738 $alreadyproduced += $line2['qty'];
739 }
740
741 if ($alreadyproduced < $line->qty) {
742 $productioncomplete = false;
743 }
744 }
745 }
746 } else {
747 $consumptioncomplete = false;
748 $productioncomplete = false;
749 }
750 }
751 }
752
753 // Update status of MO
754 dol_syslog("consumptioncomplete = ".json_encode($consumptioncomplete)." productioncomplete = ".json_encode($productioncomplete));
755 if ($consumptioncomplete && $productioncomplete) {
756 $result = $this->mo->setStatut(Mo::STATUS_PRODUCED, 0, '', 'MRP_MO_PRODUCED');
757 } else {
758 $result = $this->mo->setStatut(Mo::STATUS_INPROGRESS, 0, '', 'MRP_MO_PRODUCED');
759 }
760 if ($result <= 0) {
761 throw new RestException(500, $this->mo->error);
762 }
763
764 return $this->mo->id;
765 }
766
801 public function produceAndConsume($id, $request_data = null)
802 {
803 if (!DolibarrApiAccess::$user->hasRight("mrp", "write")) {
804 throw new RestException(403, 'Not enough permission');
805 }
806 $result = $this->mo->fetch($id);
807 if (!$result) {
808 throw new RestException(404, 'MO not found');
809 }
810
811 if ($this->mo->status != Mo::STATUS_VALIDATED && $this->mo->status != Mo::STATUS_INPROGRESS) {
812 throw new RestException(405, 'Error bad status of MO');
813 }
814
815 // Code for consume and produce...
816 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
817 require_once DOL_DOCUMENT_ROOT.'/product/stock/class/mouvementstock.class.php';
818 require_once DOL_DOCUMENT_ROOT.'/mrp/lib/mrp_mo.lib.php';
819
820 $stockmove = new MouvementStock($this->db);
821
822 $labelmovement = '';
823 $codemovement = '';
824 $autoclose = 1;
825 $arraytoconsume = array();
826 $arraytoproduce = array();
827
828 foreach ($request_data as $field => $value) {
829 if ($field == 'inventorylabel') {
830 $labelmovement = $value;
831 }
832 if ($field == 'inventorycode') {
833 $codemovement = $value;
834 }
835 if ($field == 'autoclose') {
836 $autoclose = $value;
837 }
838 if ($field == 'arraytoconsume') {
839 $arraytoconsume = $value;
840 }
841 if ($field == 'arraytoproduce') {
842 $arraytoproduce = $value;
843 }
844 if ($field === 'caller') {
845 // 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
846 $stockmove->context['caller'] = $request_data['caller'];
847 continue;
848 }
849 }
850
851 if (empty($labelmovement)) {
852 throw new RestException(500, "Field inventorylabel not provided");
853 }
854 if (empty($codemovement)) {
855 throw new RestException(500, "Field inventorycode not provided");
856 }
857
858 $this->db->begin();
859
860 $pos = 0;
861 $arrayofarrayname = array("arraytoconsume", "arraytoproduce");
862 foreach ($arrayofarrayname as $arrayname) {
863 foreach (${$arrayname} as $value) {
864 if (empty($value["objectid"])) {
865 throw new RestException(500, "Field objectid required in " . $arrayname);
866 }
867
868 $molinetoprocess = new MoLine($this->db);
869 $tmpmolineid = $molinetoprocess->fetch($value["objectid"]);
870 if ($tmpmolineid <= 0) {
871 throw new RestException(500, "MoLine with rowid " . $value["objectid"] . " not exist.");
872 }
873
874 $tmpproduct = new Product($this->db);
875 $tmpproduct->fetch($molinetoprocess->fk_product);
876 if ($tmpproduct->status_batch) {
877 throw new RestException(500, "Product " . $tmpproduct->ref . " must be in batch, this API can't handle it currently.");
878 }
879
880 if (empty($value["qty"]) && $value["qty"] != 0) {
881 throw new RestException(500, "Field qty with lower or higher then 0 required in " . $arrayname);
882 }
883 $qtytoprocess = $value["qty"];
884
885 $fk_warehousetoprocess = 0;
886 if ($molinetoprocess->disable_stock_change == false) {
887 if (isset($value["fk_warehouse"])) { // If there is a warehouse to set
888 if (!($value["fk_warehouse"] > 0)) { // If there is no warehouse set.
889 throw new RestException(500, "Field fk_warehouse required in " . $arrayname);
890 }
891 }
892 $fk_warehousetoprocess = (int) $value["fk_warehouse"];
893 }
894
895 $pricetoproduce = 0;
896 if (isset($value["pricetoproduce"])) { // If there is a price to produce set.
897 if ($value["pricetoproduce"] > 0) { // Only use prices grater then 0.
898 $pricetoproduce = $value["pricetoproduce"];
899 }
900 }
901
902 $idstockmove = 0;
903
904 if ($molinetoprocess->disable_stock_change == false) {
905 // Record stock movement
906 $id_product_batch = 0;
907 $stockmove->origin_type = 'mo';
908 $stockmove->origin_id = $this->mo->id;
909 if ($arrayname == "arraytoconsume") {
910 if ($qtytoprocess >= 0) {
911 $idstockmove = $stockmove->livraison(DolibarrApiAccess::$user, $molinetoprocess->fk_product, $fk_warehousetoprocess, $qtytoprocess, 0, $labelmovement, dol_now(), '', '', (string) $tmpproduct->status_batch, $id_product_batch, $codemovement);
912 } else {
913 $idstockmove = $stockmove->reception(DolibarrApiAccess::$user, $molinetoprocess->fk_product, $fk_warehousetoprocess, $qtytoprocess, 0, $labelmovement, '', '', (string) $tmpproduct->status_batch, dol_now(), $id_product_batch, $codemovement);
914 }
915 } else {
916 if ($qtytoprocess >= 0) {
917 $idstockmove = $stockmove->reception(DolibarrApiAccess::$user, $molinetoprocess->fk_product, $fk_warehousetoprocess, $qtytoprocess, $pricetoproduce, $labelmovement, '', '', (string) $tmpproduct->status_batch, dol_now(), $id_product_batch, $codemovement);
918 } else {
919 $idstockmove = $stockmove->livraison(DolibarrApiAccess::$user, $molinetoprocess->fk_product, $fk_warehousetoprocess, $qtytoprocess, 0, $labelmovement, dol_now(), '', '', (string) $tmpproduct->status_batch, $id_product_batch, $codemovement);
920 }
921 }
922 if ($idstockmove <= 0) {
923 throw new RestException(500, $stockmove->error);
924 }
925 }
926
927 // Record consumption
928 $moline = new MoLine($this->db);
929 $moline->fk_mo = $this->mo->id;
930 $moline->position = $pos;
931 $moline->fk_product = $tmpproduct->id;
932 $moline->fk_warehouse = $idstockmove > 0 ? $fk_warehousetoprocess : null;
933 $moline->qty = $qtytoprocess;
934 $moline->batch = '';
935 $moline->fk_mrp_production = $molinetoprocess->id;
936 $moline->fk_stock_movement = $idstockmove > 0 ? $idstockmove : null;
937 $moline->fk_user_creat = DolibarrApiAccess::$user->id;
938
939 if ($arrayname == "arraytoconsume") {
940 $moline->role = 'consumed';
941 } else {
942 $moline->role = 'produced';
943 }
944
945 $resultmoline = $moline->create(DolibarrApiAccess::$user);
946 if ($resultmoline <= 0) {
947 throw new RestException(500, $moline->error);
948 }
949
950 $pos++;
951 }
952 }
953
954 $consumptioncomplete = true;
955 $productioncomplete = true;
956
957 if ($autoclose > 0) {
958 // Refresh Lines after consumptions.
959 $this->mo->fetchLines();
960
961 foreach ($this->mo->lines as $line) {
962 if ($line->role == 'toconsume') {
963 $arrayoflines = $this->mo->fetchLinesLinked('consumed', $line->id);
964 $alreadyconsumed = 0;
965 foreach ($arrayoflines as $line2) {
966 $alreadyconsumed += $line2['qty'];
967 }
968
969 if ($alreadyconsumed < $line->qty) {
970 $consumptioncomplete = false;
971 }
972 }
973 if ($line->role == 'toproduce') {
974 $arrayoflines = $this->mo->fetchLinesLinked('produced', $line->id);
975 $alreadyproduced = 0;
976 foreach ($arrayoflines as $line2) {
977 $alreadyproduced += $line2['qty'];
978 }
979
980 if ($alreadyproduced < $line->qty) {
981 $productioncomplete = false;
982 }
983 }
984 }
985 } else {
986 $consumptioncomplete = false;
987 $productioncomplete = false;
988 }
989
990 // Update status of MO
991 dol_syslog("consumptioncomplete = " . (string) $consumptioncomplete . " productioncomplete = " . (string) $productioncomplete);
992 //var_dump("consumptioncomplete = ".$consumptioncomplete." productioncomplete = ".$productioncomplete);
993 if ($consumptioncomplete && $productioncomplete) {
994 $result = $this->mo->setStatut(Mo::STATUS_PRODUCED, 0, '', 'MRP_MO_PRODUCED');
995 } else {
996 $result = $this->mo->setStatut(Mo::STATUS_INPROGRESS, 0, '', 'MRP_MO_PRODUCED');
997 }
998 if ($result <= 0) {
999 throw new RestException(500, $this->mo->error);
1000 }
1001
1002 $this->db->commit();
1003 return $this->mo->id;
1004 }
1005
1006
1007 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
1017 protected function _cleanObjectDatas($object)
1018 {
1019 // phpcs:enable
1020 $object = parent::_cleanObjectDatas($object);
1021
1022 unset($object->rowid);
1023 unset($object->canvas);
1024
1025 unset($object->name);
1026 unset($object->lastname);
1027 unset($object->firstname);
1028 unset($object->civility_id);
1029 unset($object->statut);
1030 unset($object->state);
1031 unset($object->state_id);
1032 unset($object->state_code);
1033 unset($object->region);
1034 unset($object->region_code);
1035 unset($object->country);
1036 unset($object->country_id);
1037 unset($object->country_code);
1038 unset($object->barcode_type);
1039 unset($object->barcode_type_code);
1040 unset($object->barcode_type_label);
1041 unset($object->barcode_type_coder);
1042 unset($object->total_ht);
1043 unset($object->total_tva);
1044 unset($object->total_localtax1);
1045 unset($object->total_localtax2);
1046 unset($object->total_ttc);
1047 unset($object->fk_account);
1048 unset($object->comments);
1049 unset($object->note);
1050 unset($object->mode_reglement_id);
1051 unset($object->cond_reglement_id);
1052 unset($object->cond_reglement);
1053 unset($object->shipping_method_id);
1054 unset($object->fk_incoterms);
1055 unset($object->label_incoterms);
1056 unset($object->location_incoterms);
1057
1058 // If object has lines, remove $db property
1059 if (isset($object->lines) && is_array($object->lines) && count($object->lines) > 0) {
1060 $nboflines = count($object->lines);
1061 for ($i = 0; $i < $nboflines; $i++) {
1062 $this->_cleanObjectDatas($object->lines[$i]);
1063
1064 unset($object->lines[$i]->lines);
1065 unset($object->lines[$i]->note);
1066 }
1067 }
1068
1069 return $object;
1070 }
1071
1080 private function _validate($data)
1081 {
1082 $myobject = array();
1083 foreach ($this->mo->fields as $field => $propfield) {
1084 if (in_array($field, array('rowid', 'entity', 'date_creation', 'tms', 'fk_user_creat')) || empty($propfield['notnull']) || $propfield['notnull'] != 1) {
1085 continue; // Not a mandatory field
1086 }
1087 if (!isset($data[$field])) {
1088 throw new RestException(400, "$field field missing");
1089 }
1090 $myobject[$field] = $data[$field];
1091 }
1092 return $myobject;
1093 }
1094
1100 private function checkRefNumbering()
1101 {
1102 $ref = substr($this->mo->ref, 1, 4);
1103 if ($this->mo->status > 0 && $ref == 'PROV') {
1104 throw new RestException(400, "Wrong naming scheme '(PROV%)' is only allowed on 'DRAFT' status. For automatic increment use 'auto' on the 'ref' field.");
1105 }
1106
1107 if (strtolower($this->mo->ref) == 'auto') {
1108 if (empty($this->mo->id) && $this->mo->status == 0) {
1109 $this->mo->ref = ''; // 'ref' will auto incremented with '(PROV' + newID + ')'
1110 } else {
1111 $this->mo->fetch_product();
1112 $numref = $this->mo->getNextNumRef($this->mo->product);
1113 $this->mo->ref = $numref;
1114 }
1115 }
1116 }
1117}
$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.
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:
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
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.
print $langs trans("Show") . '< td style="' . $timeColor . '" align="center"> s</td > badge status0 badge status4 badge status3 Error badge status8< td align="center">< span class="badge ' . $badge . '"></span ></td >< td align="center">< a href="#" class="button button-small" onclick="openLogModal(this)" data-req="' . dol_escape_htmltag($reqSafe) . '" data-res="' . dol_escape_htmltag($resSafe) . '" data-err="' . dol_escape_htmltag($errSafe) . '">< span class="fa fa-search-plus"></span ></a ></td ></tr >< tr >< td colspan="' . $colspan . '" class="opacitymedium"></td ></tr ></table ></div ></form > logModal none logModal none s a JSON string
buildzip.php