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