dolibarr 21.0.0-beta
price_parser.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2015 Ion Agorria <ion@agorria.com>
3 * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
24require_once DOL_DOCUMENT_ROOT.'/core/class/evalmath.class.php';
25require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
26require_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_expression.class.php';
27require_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_global_variable.class.php';
28require_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_global_variable_updater.class.php';
29require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
30
35{
39 protected $db;
43 public $limit = 100;
47 public $error_parser;
51 public $error_expr;
55 public $special_chr = "#";
59 public $separator_chr = ";";
60
66 public function __construct($db)
67 {
68 $this->db = $db;
69 }
70
76 public function translatedError()
77 {
78 global $langs;
79 $langs->load("errors");
80 /*
81 -No arg
82 9, an unexpected error occurred
83 14, division by zero
84 19, expression not found
85 20, empty expression
86
87 -1 Arg
88 1, cannot assign to constant '%s'
89 2, cannot redefine built-in function '%s'
90 3, undefined variable '%s' in function definition
91 4, illegal character '%s'
92 5, unexpected '%s'
93 8, unexpected operator '%s'
94 10, operator '%s' lacks operand
95 11, expecting '%s'
96 17, undefined variable '%s'
97 21, empty result '%s'
98 22, negative result '%s'
99 24, variable '%s' exists but has no value
100
101 -2 Args
102 6, wrong number of arguments (%s given, %s expected)
103 23, unknown or non set variable '%s' after %s
104
105 -internal errors
106 7, internal error
107 12, internal error
108 13, internal error
109 15, internal error
110 16, internal error
111 18, internal error
112 */
113 if (empty($this->error_parser)) {
114 return $langs->trans("ErrorPriceExpressionUnknown", 0); //this is not supposed to happen
115 }
116 list($code, $info) = $this->error_parser;
117 if (in_array($code, array(9, 14, 19, 20))) { //Errors which have 0 arg
118 return $langs->trans("ErrorPriceExpression".$code);
119 } elseif (in_array($code, array(1, 2, 3, 4, 5, 8, 10, 11, 17, 21, 22))) { //Errors which have 1 arg
120 return $langs->trans("ErrorPriceExpression".$code, $info);
121 } elseif (in_array($code, array(6, 23))) { //Errors which have 2 args
122 return $langs->trans("ErrorPriceExpression".$code, $info[0], $info[1]);
123 } elseif (in_array($code, array(7, 12, 13, 15, 16, 18))) { //Internal errors
124 return $langs->trans("ErrorPriceExpressionInternal", $code);
125 } else { //Unknown errors
126 return $langs->trans("ErrorPriceExpressionUnknown", $code);
127 }
128 }
129
138 public function parseExpression($product, $expression, $values)
139 {
140 global $user, $hookmanager, $extrafields;
141
142 $action = 'PARSEEXPRESSION';
143 if ($reshook = $hookmanager->executeHooks('doDynamiPrice', array(
144 'expression' => &$expression,
145 'product' => &$product,
146 'values' => &$values
147 ), $this, $action)) {
148 return $hookmanager->resArray['return'];
149 }
150 //Check if empty
151 $expression = trim($expression);
152 if (empty($expression)) {
153 $this->error_parser = array(20, null);
154 return -2;
155 }
156
157 //Accessible product values by expressions
158 $values = array_merge($values, array(
159 "tva_tx" => $product->tva_tx,
160 "localtax1_tx" => $product->localtax1_tx,
161 "localtax2_tx" => $product->localtax2_tx,
162 "weight" => $product->weight,
163 "length" => $product->length,
164 "surface" => $product->surface,
165 "price_min" => $product->price_min,
166 "cost_price" => $product->cost_price,
167 "pmp" => $product->pmp,
168 ));
169
170 // Retrieve all extrafields if not already not know (should not happen)
171 if (! is_object($extrafields)) {
172 $extrafields = new ExtraFields($this->db);
173 $extrafields->fetch_name_optionals_label($product->table_element);
174 }
175
176 $product->fetch_optionals();
177 if (is_array($extrafields->attributes[$product->table_element]['label'])) {
178 foreach ($extrafields->attributes[$product->table_element]['label'] as $key => $label) {
179 $values["extrafield_".$key] = $product->array_options['options_'.$key];
180 }
181 }
182
183 //Process any pending updaters
184 $price_updaters = new PriceGlobalVariableUpdater($this->db);
185 foreach ($price_updaters->listPendingUpdaters() as $entry) {
186 //Schedule the next update by adding current timestamp (secs) + interval (mins)
187 $entry->update_next_update(dol_now() + ($entry->update_interval * 60), $user);
188 //Do processing
189 $res = $entry->process();
190 //Store any error or clear status if OK
191 $entry->update_status($res < 1 ? $entry->error : '', $user);
192 }
193
194 //Get all global values
195 $price_globals = new PriceGlobalVariable($this->db);
196 foreach ($price_globals->listGlobalVariables() as $entry) {
197 $values["global_".$entry->code] = $entry->value;
198 }
199
200 //Remove internal variables
201 unset($values["supplier_id"]);
202
203 //Prepare the lib, parameters and values
204 $em = new EvalMath();
205 $em->suppress_errors = true; //Don't print errors on page
206 $this->error_expr = null;
207 $last_result = null;
208
209 //Fill each variable in expression from values
210 $expression = str_replace("\n", $this->separator_chr, $expression);
211 foreach ($values as $key => $value) {
212 if ($value === null && strpos($expression, $key) !== false) {
213 $this->error_parser = array(24, $key);
214 return -7;
215 }
216 $expression = str_replace($this->special_chr.$key.$this->special_chr, strval($value), $expression);
217 }
218
219 //Check if there is unfilled variable
220 if (strpos($expression, $this->special_chr) !== false) {
221 $data = explode($this->special_chr, $expression);
222 $variable = $this->special_chr.$data[1];
223 if (isset($data[2])) {
224 $variable .= $this->special_chr;
225 }
226 $this->error_parser = array(23, array($variable, $expression));
227 return -6;
228 }
229
230 //Iterate over each expression split by $separator_chr
231 $expressions = explode($this->separator_chr, $expression);
232 $expressions = array_slice($expressions, 0, $this->limit);
233 foreach ($expressions as $expr) {
234 $expr = trim($expr);
235 if (!empty($expr)) {
236 $last_result = $em->evaluate($expr);
237 $this->error_parser = $em->last_error_code;
238 if ($this->error_parser !== null) { //$em->last_error_code is null if no error happened, so just check if error_parser is not null
239 $this->error_expr = $expr;
240 return -3;
241 }
242 }
243 }
244 $vars = $em->vars();
245 if (empty($vars["price"])) {
246 $vars["price"] = $last_result;
247 }
248 if (!isset($vars["price"])) {
249 $this->error_parser = array(21, $expression);
250 return -4;
251 }
252 if ($vars["price"] < 0) {
253 $this->error_parser = array(22, $expression);
254 return -5;
255 }
256 return $vars["price"];
257 }
258
266 public function parseProduct($product, $extra_values = array())
267 {
268 //Get the expression from db
269 $price_expression = new PriceExpression($this->db);
270 $res = $price_expression->fetch($product->fk_price_expression);
271 if ($res < 1) {
272 $this->error_parser = array(19, null);
273 return -1;
274 }
275
276 //Get the supplier min price
277 $productFournisseur = new ProductFournisseur($this->db);
278 $res = $productFournisseur->find_min_price_product_fournisseur($product->id, 0, 0);
279 if ($res < 0) {
280 $this->error_parser = array(25, null);
281 return -1;
282 } elseif ($res == 0) {
283 $supplier_min_price = 0;
284 $supplier_min_price_with_discount = 0;
285 } else {
286 $supplier_min_price = $productFournisseur->fourn_unitprice;
287 $supplier_min_price_with_discount = $productFournisseur->fourn_unitprice_with_discount;
288 }
289
290 //Accessible values by expressions
291 $extra_values = array_merge($extra_values, array(
292 "supplier_min_price" => $supplier_min_price,
293 "supplier_min_price_with_discount" => $supplier_min_price_with_discount,
294 ));
295
296 //Parse the expression and return the price, if not error occurred check if price is higher than min
297 $result = $this->parseExpression($product, $price_expression->expression, $extra_values);
298 if (empty($this->error_parser)) {
299 if ($result < $product->price_min) {
300 $result = $product->price_min;
301 }
302 }
303 return $result;
304 }
305
313 public function parseProductSupplier($product_supplier, $extra_values = array())
314 {
315 //Get the expression from db
316 $price_expression = new PriceExpression($this->db);
317 $res = $price_expression->fetch($product_supplier->fk_supplier_price_expression);
318 if ($res < 1) {
319 $this->error_parser = array(19, null);
320 return -1;
321 }
322
323 //Get the product data (use ignore_expression to avoid possible recursion)
324 $product_supplier->fetch($product_supplier->id, '', '', '', 1);
325
326 //Accessible values by expressions
327 $extra_values = array_merge($extra_values, array(
328 "supplier_quantity" => $product_supplier->fourn_qty,
329 "supplier_tva_tx" => $product_supplier->fourn_tva_tx,
330 ));
331
332 //Parse the expression and return the price
333 return $this->parseExpression($product_supplier, $price_expression->expression, $extra_values);
334 }
335
344 public function testExpression($product_id, $expression, $extra_values = array())
345 {
346 //Get the product data
347 $product = new Product($this->db);
348 $product->fetch($product_id, '', '', 1);
349
350 //Values for product expressions
351 $extra_values = array_merge($extra_values, array(
352 "supplier_min_price" => 1,
353 "supplier_min_price_with_discount" => 2,
354 ));
355
356 //Values for supplier product expressions
357 $extra_values = array_merge($extra_values, array(
358 "supplier_quantity" => 3,
359 "supplier_tva_tx" => 4,
360 ));
361 return $this->parseExpression($product, $expression, $extra_values);
362 }
363}
Class EvalMath.
Class to manage standard extra fields.
Class for accessing price expression table.
Class for accessing price global variables table.
Class for price global variable updaters table.
Class to parse product price expressions.
parseExpression($product, $expression, $values)
Calculates price based on expression.
parseProduct($product, $extra_values=array())
Calculates product price based on product id and associated expression.
parseProductSupplier($product_supplier, $extra_values=array())
Calculates supplier product price based on product supplier price and associated expression.
__construct($db)
Constructor.
testExpression($product_id, $expression, $extra_values=array())
Tests string expression for validity.
translatedError()
Returns translated error.
Class to manage predefined suppliers products.
Class to manage products or services.
dol_now($mode='auto')
Return date for now.