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