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