dolibarr  20.0.0-alpha
interface_20_modWorkflow_WorkflowManager.class.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (C) 2010 Regis Houssin <regis.houssin@inodbox.com>
3  * Copyright (C) 2011-2017 Laurent Destailleur <eldy@users.sourceforge.net>
4  * Copyright (C) 2014 Marcos GarcĂ­a <marcosgdf@gmail.com>
5  * Copyright (C) 2022 Ferran Marcet <fmarcet@2byte.es>
6  * Copyright (C) 2023 Alexandre Janniaux <alexandre.janniaux@gmail.com>
7  * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program. If not, see <https://www.gnu.org/licenses/>.
21  */
22 
29 require_once DOL_DOCUMENT_ROOT.'/core/triggers/dolibarrtriggers.class.php';
30 
31 
37 {
43  public function __construct($db)
44  {
45  $this->db = $db;
46 
47  $this->name = preg_replace('/^Interface/i', '', get_class($this));
48  $this->family = "core";
49  $this->description = "Triggers of this module allows to manage workflows";
50  $this->version = self::VERSIONS['prod'];
51  $this->picto = 'technic';
52  }
53 
65  public function runTrigger($action, $object, User $user, Translate $langs, Conf $conf)
66  {
67  if (empty($conf->workflow) || empty($conf->workflow->enabled)) {
68  return 0; // Module not active, we do nothing
69  }
70 
71  $ret = 0;
72 
73  // Proposals to order
74  if ($action == 'PROPAL_CLOSE_SIGNED') {
75  dol_syslog("Trigger '".$this->name."' for action '$action' launched by ".__FILE__.". id=".$object->id);
76  if (isModEnabled('order') && getDolGlobalString('WORKFLOW_PROPAL_AUTOCREATE_ORDER')) {
77  $object->fetchObjectLinked();
78  if (!empty($object->linkedObjectsIds['commande'])) {
79  if (empty($object->context['closedfromonlinesignature'])) {
80  $langs->load("orders");
81  setEventMessages($langs->trans("OrderExists"), null, 'warnings');
82  }
83  return $ret;
84  }
85 
86  include_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
87  $newobject = new Commande($this->db);
88 
89  $newobject->context['createfrompropal'] = 'createfrompropal';
90  $newobject->context['origin'] = $object->element;
91  $newobject->context['origin_id'] = $object->id;
92 
93  $ret = $newobject->createFromProposal($object, $user);
94  if ($ret < 0) {
95  $this->setErrorsFromObject($newobject);
96  }
97 
98  $object->clearObjectLinkedCache();
99 
100  return (int) $ret;
101  }
102  }
103 
104  // Order to invoice
105  if ($action == 'ORDER_CLOSE') {
106  dol_syslog("Trigger '".$this->name."' for action '$action' launched by ".__FILE__.". id=".$object->id);
107  if (isModEnabled('invoice') && getDolGlobalString('WORKFLOW_ORDER_AUTOCREATE_INVOICE')) {
108  include_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
109  $newobject = new Facture($this->db);
110 
111  $newobject->context['createfromorder'] = 'createfromorder';
112  $newobject->context['origin'] = $object->element;
113  $newobject->context['origin_id'] = $object->id;
114 
115  $ret = $newobject->createFromOrder($object, $user);
116  if ($ret < 0) {
117  $this->setErrorsFromObject($newobject);
118  } else {
119  if (empty($object->fk_account) && !empty($object->thirdparty->fk_account) && !getDolGlobalInt('BANK_ASK_PAYMENT_BANK_DURING_ORDER')) {
120  $res = $newobject->setBankAccount($object->thirdparty->fk_account, true, $user);
121  if ($ret < 0) {
122  $this->setErrorsFromObject($newobject);
123  }
124  }
125  }
126 
127  $object->clearObjectLinkedCache();
128 
129  return $ret;
130  }
131  }
132 
133  // Order classify billed proposal
134  if ($action == 'ORDER_CLASSIFY_BILLED') {
135  dol_syslog("Trigger '".$this->name."' for action '$action' launched by ".__FILE__.". id=".$object->id);
136  if (isModEnabled("propal") && !empty($conf->workflow->enabled) && getDolGlobalString('WORKFLOW_ORDER_CLASSIFY_BILLED_PROPAL')) {
137  $object->fetchObjectLinked('', 'propal', $object->id, $object->element);
138  if (!empty($object->linkedObjects)) {
139  $totalonlinkedelements = 0;
140  foreach ($object->linkedObjects['propal'] as $element) {
141  if ($element->statut == Propal::STATUS_SIGNED || $element->statut == Propal::STATUS_BILLED) {
142  $totalonlinkedelements += $element->total_ht;
143  }
144  }
145  dol_syslog("Amount of linked proposals = ".$totalonlinkedelements.", of order = ".$object->total_ht.", egality is ".json_encode($totalonlinkedelements == $object->total_ht));
146  if ($this->shouldClassify($conf, $totalonlinkedelements, $object->total_ht)) {
147  foreach ($object->linkedObjects['propal'] as $element) {
148  $ret = $element->classifyBilled($user);
149  }
150  }
151  }
152  return $ret;
153  }
154  }
155 
156  // classify billed order & billed propososal
157  if ($action == 'BILL_VALIDATE') {
158  dol_syslog("Trigger '".$this->name."' for action '$action' launched by ".__FILE__.". id=".$object->id);
159 
160  // First classify billed the order to allow the proposal classify process
161  if (isModEnabled('order') && !empty($conf->workflow->enabled) && getDolGlobalString('WORKFLOW_INVOICE_AMOUNT_CLASSIFY_BILLED_ORDER')) {
162  $object->fetchObjectLinked('', 'commande', $object->id, $object->element);
163  if (!empty($object->linkedObjects)) {
164  $totalonlinkedelements = 0;
165  foreach ($object->linkedObjects['commande'] as $element) {
166  if ($element->statut == Commande::STATUS_VALIDATED || $element->statut == Commande::STATUS_SHIPMENTONPROCESS || $element->statut == Commande::STATUS_CLOSED) {
167  $totalonlinkedelements += $element->total_ht;
168  }
169  }
170  dol_syslog("Amount of linked orders = ".$totalonlinkedelements.", of invoice = ".$object->total_ht.", egality is ".json_encode($totalonlinkedelements == $object->total_ht));
171  if ($this->shouldClassify($conf, $totalonlinkedelements, $object->total_ht)) {
172  foreach ($object->linkedObjects['commande'] as $element) {
173  $ret = $element->classifyBilled($user);
174  }
175  }
176  }
177  }
178 
179  // Second classify billed the proposal.
180  if (isModEnabled("propal") && !empty($conf->workflow->enabled) && getDolGlobalString('WORKFLOW_INVOICE_CLASSIFY_BILLED_PROPAL')) {
181  $object->fetchObjectLinked('', 'propal', $object->id, $object->element);
182  if (!empty($object->linkedObjects)) {
183  $totalonlinkedelements = 0;
184  foreach ($object->linkedObjects['propal'] as $element) {
185  if ($element->statut == Propal::STATUS_SIGNED || $element->statut == Propal::STATUS_BILLED) {
186  $totalonlinkedelements += $element->total_ht;
187  }
188  }
189  dol_syslog("Amount of linked proposals = ".$totalonlinkedelements.", of invoice = ".$object->total_ht.", egality is ".json_encode($totalonlinkedelements == $object->total_ht));
190  if ($this->shouldClassify($conf, $totalonlinkedelements, $object->total_ht)) {
191  foreach ($object->linkedObjects['propal'] as $element) {
192  $ret = $element->classifyBilled($user);
193  }
194  }
195  }
196  }
197 
198  // Set shipment to "Closed" if WORKFLOW_SHIPPING_CLASSIFY_CLOSED_INVOICE is set (deprecated, has been replaced with WORKFLOW_SHIPPING_CLASSIFY_BILLED_INVOICE instead))
199  if (isModEnabled("shipping") && !empty($conf->workflow->enabled) && getDolGlobalString('WORKFLOW_SHIPPING_CLASSIFY_CLOSED_INVOICE')) {
200  $object->fetchObjectLinked('', 'shipping', $object->id, $object->element);
201  if (!empty($object->linkedObjects)) {
202  $totalonlinkedelements = 0;
203  foreach ($object->linkedObjects['shipping'] as $element) {
204  if ($element->statut == Expedition::STATUS_VALIDATED) {
205  $totalonlinkedelements += $element->total_ht;
206  }
207  }
208  dol_syslog("Amount of linked shipment = ".$totalonlinkedelements.", of invoice = ".$object->total_ht.", egality is ".json_encode($totalonlinkedelements == $object->total_ht), LOG_DEBUG);
209  if ($totalonlinkedelements == $object->total_ht) {
210  foreach ($object->linkedObjects['shipping'] as $element) {
211  $ret = $element->setClosed();
212  if ($ret < 0) {
213  return (int) $ret;
214  }
215  }
216  }
217  }
218  }
219 
220  if (isModEnabled("shipping") && !empty($conf->workflow->enabled) && getDolGlobalString('WORKFLOW_SHIPPING_CLASSIFY_BILLED_INVOICE')) {
221  $object->fetchObjectLinked('', 'shipping', $object->id, $object->element);
222  if (!empty($object->linkedObjects)) {
223  $totalonlinkedelements = 0;
224  foreach ($object->linkedObjects['shipping'] as $element) {
225  if ($element->statut == Expedition::STATUS_VALIDATED || $element->statut == Expedition::STATUS_CLOSED) {
226  $totalonlinkedelements += $element->total_ht;
227  }
228  }
229  dol_syslog("Amount of linked shipment = ".$totalonlinkedelements.", of invoice = ".$object->total_ht.", egality is ".json_encode($totalonlinkedelements == $object->total_ht), LOG_DEBUG);
230  if ($totalonlinkedelements == $object->total_ht) {
231  foreach ($object->linkedObjects['shipping'] as $element) {
232  $ret = $element->setBilled();
233  if ($ret < 0) {
234  return (int) $ret;
235  }
236  }
237  }
238  }
239  }
240 
241  // First classify billed the order to allow the proposal classify process
242  if (isModEnabled('order') && isModEnabled('workflow') && getDolGlobalString('WORKFLOW_SUM_INVOICES_AMOUNT_CLASSIFY_BILLED_ORDER')) {
243  $object->fetchObjectLinked('', 'commande', $object->id, $object->element);
244  if (!empty($object->linkedObjects['commande']) && count($object->linkedObjects['commande']) == 1) { // If the invoice has only 1 source order
245  $orderLinked = reset($object->linkedObjects['commande']);
246  $orderLinked->fetchObjectLinked($orderLinked->id, '', $orderLinked->element);
247  if (count($orderLinked->linkedObjects['facture']) >= 1) {
248  $totalHTInvoices = 0;
249  $areAllInvoicesValidated = true;
250  foreach ($orderLinked->linkedObjects['facture'] as $key => $invoice) {
251  if ($invoice->statut == Facture::STATUS_VALIDATED || $object->id == $invoice->id) {
252  $totalHTInvoices += (float) $invoice->total_ht;
253  } else {
254  $areAllInvoicesValidated = false;
255  break;
256  }
257  }
258  if ($areAllInvoicesValidated) {
259  $isSameTotal = (price2num($totalHTInvoices, 'MT') == price2num($orderLinked->total_ht, 'MT'));
260  dol_syslog("Amount of linked invoices = ".$totalHTInvoices.", of order = ".$orderLinked->total_ht.", isSameTotal = ".(string) $isSameTotal, LOG_DEBUG);
261  if ($isSameTotal) {
262  $ret = $orderLinked->classifyBilled($user);
263  if ($ret < 0) {
264  return $ret;
265  }
266  }
267  }
268  }
269  }
270  }
271  return $ret;
272  }
273 
274  // classify billed order & billed proposal
275  if ($action == 'BILL_SUPPLIER_VALIDATE') {
276  dol_syslog("Trigger '".$this->name."' for action '$action' launched by ".__FILE__.". id=".$object->id);
277 
278  // Firstly, we set to purchase order to "Billed" if WORKFLOW_INVOICE_AMOUNT_CLASSIFY_BILLED_SUPPLIER_ORDER is set.
279  // After we will set proposals
280  if ((isModEnabled("supplier_order") || isModEnabled("supplier_invoice")) && getDolGlobalString('WORKFLOW_INVOICE_AMOUNT_CLASSIFY_BILLED_SUPPLIER_ORDER')) {
281  $object->fetchObjectLinked('', 'order_supplier', $object->id, $object->element);
282  if (!empty($object->linkedObjects)) {
283  $totalonlinkedelements = 0;
284  foreach ($object->linkedObjects['order_supplier'] as $element) {
286  $totalonlinkedelements += $element->total_ht;
287  }
288  }
289  dol_syslog("Amount of linked orders = ".$totalonlinkedelements.", of invoice = ".$object->total_ht.", egality is ".json_encode($totalonlinkedelements == $object->total_ht));
290  if ($this->shouldClassify($conf, $totalonlinkedelements, $object->total_ht)) {
291  foreach ($object->linkedObjects['order_supplier'] as $element) {
292  $ret = $element->classifyBilled($user);
293  if ($ret < 0) {
294  return $ret;
295  }
296  }
297  }
298  }
299  }
300 
301  // Secondly, we set to linked Proposal to "Billed" if WORKFLOW_INVOICE_CLASSIFY_BILLED_SUPPLIER_PROPOSAL is set.
302  if (isModEnabled('supplier_proposal') && getDolGlobalString('WORKFLOW_INVOICE_CLASSIFY_BILLED_SUPPLIER_PROPOSAL')) {
303  $object->fetchObjectLinked('', 'supplier_proposal', $object->id, $object->element);
304  if (!empty($object->linkedObjects)) {
305  $totalonlinkedelements = 0;
306  foreach ($object->linkedObjects['supplier_proposal'] as $element) {
307  if ($element->statut == SupplierProposal::STATUS_SIGNED || $element->statut == SupplierProposal::STATUS_CLOSE) {
308  $totalonlinkedelements += $element->total_ht;
309  }
310  }
311  dol_syslog("Amount of linked supplier proposals = ".$totalonlinkedelements.", of supplier invoice = ".$object->total_ht.", egality is ".json_encode($totalonlinkedelements == $object->total_ht));
312  if ($this->shouldClassify($conf, $totalonlinkedelements, $object->total_ht)) {
313  foreach ($object->linkedObjects['supplier_proposal'] as $element) {
314  $ret = $element->classifyBilled($user);
315  if ($ret < 0) {
316  return $ret;
317  }
318  }
319  }
320  }
321  }
322 
323  // Set reception to "Closed" if WORKFLOW_RECEPTION_CLASSIFY_CLOSED_INVOICE is set (deprecated, WORKFLOW_RECEPTION_CLASSIFY_BILLED_INVOICE instead))
324  /*
325  if (isModEnabled("reception") && !empty($conf->workflow->enabled) && !empty($conf->global->WORKFLOW_RECEPTION_CLASSIFY_CLOSED_INVOICE)) {
326  $object->fetchObjectLinked('', 'reception', $object->id, $object->element);
327  if (!empty($object->linkedObjects)) {
328  $totalonlinkedelements = 0;
329  foreach ($object->linkedObjects['reception'] as $element) {
330  if ($element->statut == Reception::STATUS_VALIDATED || $element->statut == Reception::STATUS_CLOSED) {
331  $totalonlinkedelements += $element->total_ht;
332  }
333  }
334  dol_syslog("Amount of linked reception = ".$totalonlinkedelements.", of invoice = ".$object->total_ht.", egality is ".($totalonlinkedelements == $object->total_ht), LOG_DEBUG);
335  if ($totalonlinkedelements == $object->total_ht) {
336  foreach ($object->linkedObjects['reception'] as $element) {
337  $ret = $element->setClosed();
338  if ($ret < 0) {
339  return $ret;
340  }
341  }
342  }
343  }
344  }
345  */
346 
347  // Then set reception to "Billed" if WORKFLOW_RECEPTION_CLASSIFY_BILLED_INVOICE is set
348  if (isModEnabled("reception") && !empty($conf->workflow->enabled) && getDolGlobalString('WORKFLOW_RECEPTION_CLASSIFY_BILLED_INVOICE')) {
349  $object->fetchObjectLinked('', 'reception', $object->id, $object->element);
350  if (!empty($object->linkedObjects)) {
351  $totalonlinkedelements = 0;
352  foreach ($object->linkedObjects['reception'] as $element) {
353  if ($element->statut == Reception::STATUS_VALIDATED || $element->statut == Reception::STATUS_CLOSED) {
354  $totalonlinkedelements += $element->total_ht;
355  }
356  }
357  dol_syslog("Amount of linked reception = ".$totalonlinkedelements.", of invoice = ".$object->total_ht.", egality is ".json_encode($totalonlinkedelements == $object->total_ht), LOG_DEBUG);
358  if ($totalonlinkedelements == $object->total_ht) {
359  foreach ($object->linkedObjects['reception'] as $element) {
360  $ret = $element->setBilled();
361  if ($ret < 0) {
362  return $ret;
363  }
364  }
365  }
366  }
367  }
368 
369  return $ret;
370  }
371 
372  // Invoice classify billed order
373  if ($action == 'BILL_PAYED') {
374  dol_syslog("Trigger '".$this->name."' for action '$action' launched by ".__FILE__.". id=".$object->id);
375 
376  if (isModEnabled('order') && getDolGlobalString('WORKFLOW_INVOICE_CLASSIFY_BILLED_ORDER')) {
377  $object->fetchObjectLinked('', 'commande', $object->id, $object->element);
378  if (!empty($object->linkedObjects)) {
379  $totalonlinkedelements = 0;
380  foreach ($object->linkedObjects['commande'] as $element) {
381  if ($element->statut == Commande::STATUS_VALIDATED || $element->statut == Commande::STATUS_SHIPMENTONPROCESS || $element->statut == Commande::STATUS_CLOSED) {
382  $totalonlinkedelements += $element->total_ht;
383  }
384  }
385  dol_syslog("Amount of linked orders = ".$totalonlinkedelements.", of invoice = ".$object->total_ht.", egality is ".json_encode($totalonlinkedelements == $object->total_ht));
386  if ($this->shouldClassify($conf, $totalonlinkedelements, $object->total_ht)) {
387  foreach ($object->linkedObjects['commande'] as $element) {
388  $ret = $element->classifyBilled($user);
389  }
390  }
391  }
392  return $ret;
393  }
394  }
395 
396  // If we validate or close a shipment
397  if (($action == 'SHIPPING_VALIDATE') || ($action == 'SHIPPING_CLOSED')) {
398  dol_syslog("Trigger '".$this->name."' for action '$action' launched by ".__FILE__.". id=".$object->id);
399 
400  if (isModEnabled('order') && isModEnabled("shipping") && !empty($conf->workflow->enabled) &&
401  (
402  (getDolGlobalString('WORKFLOW_ORDER_CLASSIFY_SHIPPED_SHIPPING') && ($action == 'SHIPPING_VALIDATE')) ||
403  (getDolGlobalString('WORKFLOW_ORDER_CLASSIFY_SHIPPED_SHIPPING_CLOSED') && ($action == 'SHIPPING_CLOSED'))
404  )
405  ) {
406  $qtyshipped = array();
407  $qtyordred = array();
408 
409  // The original sale order is id in $object->origin_id
410  // Find all shipments on sale order origin
411 
412  if (in_array($object->origin, array('order', 'commande')) && $object->origin_id > 0) {
413  require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
414  $order = new Commande($this->db);
415  $ret = $order->fetch($object->origin_id);
416  if ($ret < 0) {
417  $this->setErrorsFromObject($order);
418  return $ret;
419  }
420  $ret = $order->fetchObjectLinked($order->id, 'commande', null, 'shipping');
421  if ($ret < 0) {
422  $this->setErrorsFromObject($order);
423  return $ret;
424  }
425  //Build array of quantity shipped by product for an order
426  if (is_array($order->linkedObjects) && count($order->linkedObjects) > 0) {
427  foreach ($order->linkedObjects as $type => $shipping_array) {
428  if ($type != 'shipping' || !is_array($shipping_array) || count($shipping_array) == 0) {
429  continue;
430  }
432  foreach ($shipping_array as $shipping) {
433  if ($shipping->status <= 0 || !is_array($shipping->lines) || count($shipping->lines) == 0) {
434  continue;
435  }
436 
437  foreach ($shipping->lines as $shippingline) {
438  if (isset($qtyshipped[$shippingline->fk_product])) {
439  $qtyshipped[$shippingline->fk_product] += $shippingline->qty;
440  } else {
441  $qtyshipped[$shippingline->fk_product] = $shippingline->qty;
442  }
443  }
444  }
445  }
446  }
447 
448  //Build array of quantity ordered to be shipped
449  if (is_array($order->lines) && count($order->lines) > 0) {
450  foreach ($order->lines as $orderline) {
451  // Exclude lines not qualified for shipment, similar code is found into calcAndSetStatusDispatch() for vendors
452  if (!getDolGlobalString('STOCK_SUPPORTS_SERVICES') && $orderline->product_type > 0) {
453  continue;
454  }
455  if (isset($qtyordred[$shippingline->fk_product])) {
456  $qtyordred[$orderline->fk_product] += $orderline->qty;
457  } else {
458  $qtyordred[$orderline->fk_product] = $orderline->qty;
459  }
460  }
461  }
462  //dol_syslog(var_export($qtyordred,true),LOG_DEBUG);
463  //dol_syslog(var_export($qtyshipped,true),LOG_DEBUG);
464  //Compare array
465  $diff_array = array_diff_assoc($qtyordred, $qtyshipped);
466  if (count($diff_array) == 0) {
467  //No diff => mean everything is shipped
468  $ret = $order->setStatut(Commande::STATUS_CLOSED, $object->origin_id, $object->origin, 'ORDER_CLOSE');
469  if ($ret < 0) {
470  $this->setErrorsFromObject($order);
471  return $ret;
472  }
473  }
474  }
475  }
476  }
477 
478  // If we validate or close a shipment
479  if (($action == 'RECEPTION_VALIDATE') || ($action == 'RECEPTION_CLOSED')) {
480  dol_syslog("Trigger '".$this->name."' for action '$action' launched by ".__FILE__.". id=".$object->id);
481 
482  if ((isModEnabled("fournisseur") || isModEnabled("supplier_order")) && isModEnabled("reception") && isModEnabled('workflow') &&
483  (
484  (getDolGlobalString('WORKFLOW_ORDER_CLASSIFY_RECEIVED_RECEPTION') && ($action == 'RECEPTION_VALIDATE')) ||
485  (getDolGlobalString('WORKFLOW_ORDER_CLASSIFY_RECEIVED_RECEPTION_CLOSED') && ($action == 'RECEPTION_CLOSED'))
486  )
487  ) {
488  $qtyshipped = array();
489  $qtyordred = array();
490 
491  // The original purchase order is id in $object->origin_id
492  // Find all reception on purchase order origin
493 
494  if (in_array($object->origin, array('order_supplier', 'supplier_order', 'commandeFournisseur')) && $object->origin_id > 0) {
495  require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.commande.class.php';
496  $order = new CommandeFournisseur($this->db);
497  $ret = $order->fetch($object->origin_id);
498  if ($ret < 0) {
499  $this->setErrorsFromObject($order);
500  return $ret;
501  }
502  $ret = $order->fetchObjectLinked($order->id, $order->element, null, 'reception');
503  if ($ret < 0) {
504  $this->setErrorsFromObject($order);
505  return $ret;
506  }
507 
508  // Build array of quantity received by product for a purchase order
509  if (is_array($order->linkedObjects) && count($order->linkedObjects) > 0) {
510  foreach ($order->linkedObjects as $type => $shipping_array) {
511  if ($type != 'reception' || !is_array($shipping_array) || count($shipping_array) == 0) {
512  continue;
513  }
514 
515  foreach ($shipping_array as $shipping) {
516  if (!is_array($shipping->lines) || count($shipping->lines) == 0) {
517  continue;
518  }
519 
520  foreach ($shipping->lines as $shippingline) {
521  $qtyshipped[$shippingline->fk_product] += $shippingline->qty;
522  }
523  }
524  }
525  }
526 
527  // Build array of quantity ordered to be received
528  if (is_array($order->lines) && count($order->lines) > 0) {
529  foreach ($order->lines as $orderline) {
530  // Exclude lines not qualified for shipment, similar code is found into calcAndSetStatusDispatch() for vendors
531  if (!getDolGlobalString('STOCK_SUPPORTS_SERVICES') && $orderline->product_type > 0) {
532  continue;
533  }
534  $qtyordred[$orderline->fk_product] += $orderline->qty;
535  }
536  }
537  //dol_syslog(var_export($qtyordred,true),LOG_DEBUG);
538  //dol_syslog(var_export($qtyshipped,true),LOG_DEBUG);
539  //Compare array
540  $diff_array = array_diff_assoc($qtyordred, $qtyshipped);
541  if (count($diff_array) == 0) {
542  //No diff => mean everything is received
543  $ret = $order->setStatut(CommandeFournisseur::STATUS_RECEIVED_COMPLETELY, null, null, 'SUPPLIER_ORDER_CLOSE');
544  if ($ret < 0) {
545  $this->setErrorsFromObject($order);
546  return $ret;
547  }
548  }
549  }
550  }
551  }
552 
553  if ($action == 'TICKET_CREATE') {
554  dol_syslog("Trigger '".$this->name."' for action '$action' launched by ".__FILE__.". id=".$object->id);
555  // Auto link ticket to contract
556  if (isModEnabled('contract') && isModEnabled('ticket') && isModEnabled('workflow') && getDolGlobalString('WORKFLOW_TICKET_LINK_CONTRACT') && getDolGlobalString('TICKET_PRODUCT_CATEGORY') && !empty($object->fk_soc)) {
557  $societe = new Societe($this->db);
558  $company_ids = (!getDolGlobalString('WORKFLOW_TICKET_USE_PARENT_COMPANY_CONTRACTS')) ? [$object->fk_soc] : $societe->getParentsForCompany($object->fk_soc, [$object->fk_soc]);
559 
560  $contrat = new Contrat($this->db);
561  $number_contracts_found = 0;
562  foreach ($company_ids as $company_id) {
563  $contrat->socid = $company_id;
564  $list = $contrat->getListOfContracts('all', array(Contrat::STATUS_DRAFT, Contrat::STATUS_VALIDATED), array(getDolGlobalString('TICKET_PRODUCT_CATEGORY')), array(ContratLigne::STATUS_INITIAL, ContratLigne::STATUS_OPEN));
565  if (!is_array($list) || empty($list)) {
566  continue;
567  }
568  $number_contracts_found = count($list);
569  if ($number_contracts_found == 0) {
570  continue;
571  }
572 
573  foreach ($list as $linked_contract) {
574  $object->setContract($linked_contract->id);
575  // don't set '$contractid' so it is not used when creating an intervention.
576  }
577 
578  if ($number_contracts_found > 1 && !defined('NOLOGIN')) {
579  setEventMessages($langs->trans('TicketManyContractsLinked'), null, 'warnings');
580  }
581  break;
582  }
583  if ($number_contracts_found == 0) {
584  if (empty(NOLOGIN)) {
585  setEventMessages($langs->trans('TicketNoContractFoundToLink'), null, 'mesgs');
586  }
587  }
588  }
589  // Automatically create intervention
590  if (isModEnabled('intervention') && isModEnabled('ticket') && !empty($conf->workflow->enabled) && getDolGlobalString('WORKFLOW_TICKET_CREATE_INTERVENTION')) {
591  $fichinter = new Fichinter($this->db);
592  $fichinter->socid = (int) $object->fk_soc;
593  $fichinter->fk_project = (int) $object->fk_project;
594  $fichinter->fk_contrat = (int) $object->fk_contract;
595  $fichinter->author = $user->id;
596  $fichinter->model_pdf = (getDolGlobalString('FICHEINTER_ADDON_PDF')) ? $conf->global->FICHEINTER_ADDON_PDF : 'soleil';
597  $fichinter->origin = $object->element;
598  $fichinter->origin_id = $object->id;
599 
600  // Extrafields
601  $extrafields = new ExtraFields($this->db);
602  $extrafields->fetch_name_optionals_label($fichinter->table_element);
603  $array_options = $extrafields->getOptionalsFromPost($fichinter->table_element);
604  $fichinter->array_options = $array_options;
605 
606  $id = $fichinter->create($user);
607  if ($id <= 0) {
608  setEventMessages($fichinter->error, null, 'errors');
609  }
610  }
611  }
612  return 0;
613  }
614 
625  private function shouldClassify($conf, $totalonlinkedelements, $object_total_ht)
626  {
627  // if the configuration allows unmatching amounts, allow classification anyway
628  if (getDolGlobalString('WORKFLOW_CLASSIFY_IF_AMOUNTS_ARE_DIFFERENTS')) {
629  return true;
630  }
631  // if the amount are same, allow classification, else deny
632  return (price2num($totalonlinkedelements, 'MT') == price2num($object_total_ht, 'MT'));
633  }
634 }
if($user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
Definition: card.php:58
Class to manage predefined suppliers products.
const STATUS_RECEIVED_PARTIALLY
Received partially.
const STATUS_RECEIVED_COMPLETELY
Received completely.
const STATUS_ORDERSENT
Order sent, shipment on process.
Class to manage customers orders.
const STATUS_SHIPMENTONPROCESS
Shipment on process.
const STATUS_CLOSED
Closed (Sent, billed or not)
const STATUS_VALIDATED
Validated status.
Class to stock current configuration.
Definition: conf.class.php:34
Class to manage contracts.
Class that all triggers must inherit.
runTrigger($action, $object, User $user, Translate $langs, Conf $conf)
Function called when a Dolibarr business event is done.
setErrorsFromObject(CommonObject $object)
setErrorsFromObject
const STATUS_CLOSED
Closed status.
const STATUS_VALIDATED
Validated status.
Class to manage standard extra fields.
Class to manage invoices.
const STATUS_VALIDATED
Validated (need to be paid)
Class to manage interventions.
shouldClassify($conf, $totalonlinkedelements, $object_total_ht)
const STATUS_SIGNED
Signed quote.
const STATUS_BILLED
Billed or processed quote.
Class to manage third parties objects (customers, suppliers, prospects...)
const STATUS_SIGNED
Signed quote.
const STATUS_CLOSE
Billed or closed/processed quote.
Class to manage translations.
Class to manage Dolibarr users.
Definition: user.class.php:50
print *****$script_file(".$version.") pid cd cd cd description as description
Only used if Module[ID]Desc translation string is not found.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0)
Set event messages in dol_events session object.
getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
div float
Buy price without taxes.
Definition: style.css.php:959
$conf db name
Only used if Module[ID]Name translation string is not found.
Definition: repair.php:125