dolibarr 18.0.6
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 occured
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 {
113 return $langs->trans("ErrorPriceExpressionUnknown", $code);
114 }
115 }
116
125 public function parseExpression($product, $expression, $values)
126 {
127 global $user, $hookmanager, $extrafields;
128
129 $action = 'PARSEEXPRESSION';
130 if ($reshook = $hookmanager->executeHooks('doDynamiPrice', array(
131 'expression' => &$expression,
132 'product' => &$product,
133 'values' => &$values
134 ), $this, $action)) {
135 return $hookmanager->resArray['return'];
136 }
137 //Check if empty
138 $expression = trim($expression);
139 if (empty($expression)) {
140 $this->error_parser = array(20, null);
141 return -2;
142 }
143
144 //Accessible product values by expressions
145 $values = array_merge($values, array(
146 "tva_tx" => $product->tva_tx,
147 "localtax1_tx" => $product->localtax1_tx,
148 "localtax2_tx" => $product->localtax2_tx,
149 "weight" => $product->weight,
150 "length" => $product->length,
151 "surface" => $product->surface,
152 "price_min" => $product->price_min,
153 "cost_price" => $product->cost_price,
154 "pmp" => $product->pmp,
155 ));
156
157 // Retrieve all extrafields if not already not know (should not happen)
158 if (! is_object($extrafields)) {
159 $extrafields = new ExtraFields($this->db);
160 $extrafields->fetch_name_optionals_label($product->table_element);
161 }
162
163 $product->fetch_optionals();
164 if (is_array($extrafields->attributes[$product->table_element]['label'])) {
165 foreach ($extrafields->attributes[$product->table_element]['label'] as $key => $label) {
166 $values["extrafield_".$key] = $product->array_options['options_'.$key];
167 }
168 }
169
170 //Process any pending updaters
171 $price_updaters = new PriceGlobalVariableUpdater($this->db);
172 foreach ($price_updaters->listPendingUpdaters() as $entry) {
173 //Schedule the next update by adding current timestamp (secs) + interval (mins)
174 $entry->update_next_update(dol_now() + ($entry->update_interval * 60), $user);
175 //Do processing
176 $res = $entry->process();
177 //Store any error or clear status if OK
178 $entry->update_status($res < 1 ? $entry->error : '', $user);
179 }
180
181 //Get all global values
182 $price_globals = new PriceGlobalVariable($this->db);
183 foreach ($price_globals->listGlobalVariables() as $entry) {
184 $values["global_".$entry->code] = $entry->value;
185 }
186
187 //Remove internal variables
188 unset($values["supplier_id"]);
189
190 //Prepare the lib, parameters and values
191 $em = new EvalMath();
192 $em->suppress_errors = true; //Don't print errors on page
193 $this->error_expr = null;
194 $last_result = null;
195
196 //Fill each variable in expression from values
197 $expression = str_replace("\n", $this->separator_chr, $expression);
198 foreach ($values as $key => $value) {
199 if ($value === null && strpos($expression, $key) !== false) {
200 $this->error_parser = array(24, $key);
201 return -7;
202 }
203 $expression = str_replace($this->special_chr.$key.$this->special_chr, strval($value), $expression);
204 }
205
206 //Check if there is unfilled variable
207 if (strpos($expression, $this->special_chr) !== false) {
208 $data = explode($this->special_chr, $expression);
209 $variable = $this->special_chr.$data[1];
210 if (isset($data[2])) {
211 $variable .= $this->special_chr;
212 }
213 $this->error_parser = array(23, array($variable, $expression));
214 return -6;
215 }
216
217 //Iterate over each expression splitted by $separator_chr
218 $expressions = explode($this->separator_chr, $expression);
219 $expressions = array_slice($expressions, 0, $this->limit);
220 foreach ($expressions as $expr) {
221 $expr = trim($expr);
222 if (!empty($expr)) {
223 $last_result = $em->evaluate($expr);
224 $this->error_parser = $em->last_error_code;
225 if ($this->error_parser !== null) { //$em->last_error_code is null if no error happened, so just check if error_parser is not null
226 $this->error_expr = $expr;
227 return -3;
228 }
229 }
230 }
231 $vars = $em->vars();
232 if (empty($vars["price"])) {
233 $vars["price"] = $last_result;
234 }
235 if (!isset($vars["price"])) {
236 $this->error_parser = array(21, $expression);
237 return -4;
238 }
239 if ($vars["price"] < 0) {
240 $this->error_parser = array(22, $expression);
241 return -5;
242 }
243 return $vars["price"];
244 }
245
253 public function parseProduct($product, $extra_values = array())
254 {
255 //Get the expression from db
256 $price_expression = new PriceExpression($this->db);
257 $res = $price_expression->fetch($product->fk_price_expression);
258 if ($res < 1) {
259 $this->error_parser = array(19, null);
260 return -1;
261 }
262
263 //Get the supplier min price
264 $productFournisseur = new ProductFournisseur($this->db);
265 $res = $productFournisseur->find_min_price_product_fournisseur($product->id, 0, 0);
266 if ($res < 0) {
267 $this->error_parser = array(25, null);
268 return -1;
269 } elseif ($res == 0) {
270 $supplier_min_price = 0;
271 $supplier_min_price_with_discount = 0;
272 } else {
273 $supplier_min_price = $productFournisseur->fourn_unitprice;
274 $supplier_min_price_with_discount = $productFournisseur->fourn_unitprice_with_discount;
275 }
276
277 //Accessible values by expressions
278 $extra_values = array_merge($extra_values, array(
279 "supplier_min_price" => $supplier_min_price,
280 "supplier_min_price_with_discount" => $supplier_min_price_with_discount,
281 ));
282
283 //Parse the expression and return the price, if not error occurred check if price is higher than min
284 $result = $this->parseExpression($product, $price_expression->expression, $extra_values);
285 if (empty($this->error_parser)) {
286 if ($result < $product->price_min) {
287 $result = $product->price_min;
288 }
289 }
290 return $result;
291 }
292
300 public function parseProductSupplier($product_supplier, $extra_values = array())
301 {
302 //Get the expression from db
303 $price_expression = new PriceExpression($this->db);
304 $res = $price_expression->fetch($product_supplier->fk_supplier_price_expression);
305 if ($res < 1) {
306 $this->error_parser = array(19, null);
307 return -1;
308 }
309
310 //Get the product data (use ignore_expression to avoid possible recursion)
311 $product_supplier->fetch($product_supplier->id, '', '', '', 1);
312
313 //Accessible values by expressions
314 $extra_values = array_merge($extra_values, array(
315 "supplier_quantity" => $product_supplier->fourn_qty,
316 "supplier_tva_tx" => $product_supplier->fourn_tva_tx,
317 ));
318
319 //Parse the expression and return the price
320 return $this->parseExpression($product_supplier, $price_expression->expression, $extra_values);
321 }
322
331 public function testExpression($product_id, $expression, $extra_values = array())
332 {
333 //Get the product data
334 $product = new Product($this->db);
335 $product->fetch($product_id, '', '', 1);
336
337 //Values for product expressions
338 $extra_values = array_merge($extra_values, array(
339 "supplier_min_price" => 1,
340 "supplier_min_price_with_discount" => 2,
341 ));
342
343 //Values for supplier product expressions
344 $extra_values = array_merge($extra_values, array(
345 "supplier_quantity" => 3,
346 "supplier_tva_tx" => 4,
347 ));
348 return $this->parseExpression($product, $expression, $extra_values);
349 }
350}
Class EvalMath.
Class to manage standard extra fields.
Class for accesing price expression table.
Class for accesing 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.