diff --git a/htdocs/core/class/html.form.class.php b/htdocs/core/class/html.form.class.php
old mode 100644
new mode 100755
index b70820463b0..14337c1cd2b
--- a/htdocs/core/class/html.form.class.php
+++ b/htdocs/core/class/html.form.class.php
@@ -1906,7 +1906,7 @@ class Form
$sql = "SELECT p.rowid, p.label, p.ref, p.price, p.duration,";
$sql.= " pfp.ref_fourn, pfp.rowid as idprodfournprice, pfp.price as fprice, pfp.quantity, pfp.remise_percent, pfp.remise, pfp.unitprice,";
- $sql.= " s.nom as name";
+ $sql.= " pfp.fk_price_expression, pfp.fk_product, pfp.tva_tx, s.nom as name";
$sql.= " FROM ".MAIN_DB_PREFIX."product as p";
$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."product_fournisseur_price as pfp ON p.rowid = pfp.fk_product";
if ($socid) $sql.= " AND pfp.fk_soc = ".$socid;
@@ -1943,6 +1943,7 @@ class Form
$result=$this->db->query($sql);
if ($result)
{
+ require_once DOL_DOCUMENT_ROOT.'/product/class/priceparser.class.php';
$num = $this->db->num_rows($result);
@@ -1987,6 +1988,17 @@ class Form
{
$outqty=$objp->quantity;
$outdiscount=$objp->remise_percent;
+ if (!empty($objp->fk_price_expression)) {
+ $priceparser = new PriceParser($this->db);
+ $price_result = $priceparser->parse_product_supplier($objp->fk_product, $objp->fk_price_expression, $objp->quantity, $objp->tva_tx);
+ if ($price_result >= 0) {
+ $objp->fprice = $price_result;
+ if ($objp->quantity >= 1)
+ {
+ $objp->unitprice = $objp->fprice / $objp->quantity;
+ }
+ }
+ }
if ($objp->quantity == 1)
{
$opt.= price($objp->fprice,1,$langs,0,0,-1,$conf->currency)."/";
@@ -2075,7 +2087,7 @@ class Form
$sql = "SELECT p.rowid, p.label, p.ref, p.price, p.duration,";
$sql.= " pfp.ref_fourn, pfp.rowid as idprodfournprice, pfp.price as fprice, pfp.quantity, pfp.unitprice,";
- $sql.= " s.nom as name";
+ $sql.= " pfp.fk_price_expression, pfp.fk_product, pfp.tva_tx, s.nom as name";
$sql.= " FROM ".MAIN_DB_PREFIX."product as p";
$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."product_fournisseur_price as pfp ON p.rowid = pfp.fk_product";
$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON pfp.fk_soc = s.rowid";
@@ -2100,6 +2112,7 @@ class Form
}
else
{
+ require_once DOL_DOCUMENT_ROOT.'/product/class/priceparser.class.php';
$form.= '';
$i = 0;
@@ -2114,6 +2127,17 @@ class Form
}
$opt.= '>'.$objp->name.' - '.$objp->ref_fourn.' - ';
+ if (!empty($objp->fk_price_expression)) {
+ $priceparser = new PriceParser($this->db);
+ $price_result = $priceparser->parse_product_supplier($objp->fk_product, $objp->fk_price_expression, $objp->quantity, $objp->tva_tx);
+ if ($price_result >= 0) {
+ $objp->fprice = $price_result;
+ if ($objp->quantity >= 1)
+ {
+ $objp->unitprice = $objp->fprice / $objp->quantity;
+ }
+ }
+ }
if ($objp->quantity == 1)
{
$opt.= price($objp->fprice,1,$langs,0,0,-1,$conf->currency)."/";
diff --git a/htdocs/core/modules/modDinamicPrices.class.php b/htdocs/core/modules/modDinamicPrices.class.php
new file mode 100755
index 00000000000..392fc1bae52
--- /dev/null
+++ b/htdocs/core/modules/modDinamicPrices.class.php
@@ -0,0 +1,122 @@
+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+/**
+ * \defgroup produit Module dinamic prices
+ * \brief Module to manage dinamic prices in products
+ * \file htdocs/core/modules/modDinamicPrices.class.php
+ * \ingroup produit
+ * \brief File to describe module to manage dinamic prices in products
+ */
+include_once DOL_DOCUMENT_ROOT .'/core/modules/DolibarrModules.class.php';
+
+
+/**
+ * Class descriptor of DinamicPrices module
+ */
+class modDinamicPrices extends DolibarrModules
+{
+
+ /**
+ * Constructor. Define names, constants, directories, boxes, permissions
+ *
+ * @param DoliDB $db Database handler
+ */
+ function __construct($db)
+ {
+ $this->db = $db;
+ $this->numero = 2200;
+
+ $this->family = "products";
+ // Module label (no space allowed), used if translation string 'ModuleXXXName' not found (where XXX is value of numeric property 'numero' of module)
+ $this->name = preg_replace('/^mod/i','',get_class($this));
+ $this->description = "Enable the usage of math expressions for prices";
+ $this->version = 'experimental'; // 'experimental' or 'dolibarr' or version
+ // Key used in llx_const table to save module status enabled/disabled (where MYMODULE is value of property name of module in uppercase)
+ $this->const_name = 'MAIN_MODULE_'.strtoupper($this->name);
+ // Where to store the module in setup page (0=common,1=interface,2=others,3=very specific)
+ $this->special = 0;
+ // Name of image file used for this module.
+ $this->picto='technic';
+
+ // Data directories to create when module is enabled
+ $this->dirs = array();
+
+ // Config pages
+ //-------------
+ //$this->config_page_url = array("dinamicprices.php@dinamicprices");
+
+ // Dependancies
+ //-------------
+ $this->depends = array();
+ $this->requiredby = array();
+ $this->langfiles = array("other");
+
+ // Constantes
+ //-----------
+ $this->const = array();
+
+ // New pages on tabs
+ // -----------------
+ $this->tabs = array();
+
+ // Boxes
+ //------
+ $this->boxes = array();
+
+ // Permissions
+ //------------
+ $this->rights = array();
+ $this->rights_class = 'dinamicprices';
+ $r=0;
+ }
+
+
+ /**
+ * Function called when module is enabled.
+ * The init function add constants, boxes, permissions and menus (defined in constructor) into Dolibarr database.
+ * It also creates data directories
+ *
+ * @param string $options Options when enabling module ('', 'noboxes')
+ * @return int 1 if OK, 0 if KO
+ */
+ function init($options='')
+ {
+ // Prevent pb of modules not correctly disabled
+ //$this->remove($options);
+
+ $sql = array();
+
+ return $this->_init($sql,$options);
+ }
+
+ /**
+ * Function called when module is disabled.
+ * Remove from database constants, boxes and permissions from Dolibarr database.
+ * Data directories are not deleted
+ *
+ * @param string $options Options when enabling module ('', 'noboxes')
+ * @return int 1 if OK, 0 if KO
+ */
+ function remove($options='')
+ {
+ $sql = array();
+
+ return $this->_remove($sql,$options);
+ }
+
+}
diff --git a/htdocs/fourn/ajax/getSupplierPrices.php b/htdocs/fourn/ajax/getSupplierPrices.php
old mode 100644
new mode 100755
index a6695dc694d..0424c74cb60
--- a/htdocs/fourn/ajax/getSupplierPrices.php
+++ b/htdocs/fourn/ajax/getSupplierPrices.php
@@ -48,7 +48,7 @@ if (! empty($idprod))
$sql = "SELECT p.rowid, p.label, p.ref, p.price, p.duration,";
$sql.= " pfp.ref_fourn,";
$sql.= " pfp.rowid as idprodfournprice, pfp.price as fprice, pfp.remise_percent, pfp.quantity, pfp.unitprice, pfp.charges, pfp.unitcharges,";
- $sql.= " s.nom as name";
+ $sql.= " pfp.fk_price_expression, pfp.tva_tx, s.nom as name";
$sql.= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price as pfp";
$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."product as p ON p.rowid = pfp.fk_product";
$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid = pfp.fk_soc";
@@ -66,11 +66,24 @@ if (! empty($idprod))
if ($num)
{
+ require_once DOL_DOCUMENT_ROOT.'/product/class/priceparser.class.php';
$i = 0;
while ($i < $num)
{
$objp = $db->fetch_object($result);
+ if (!empty($objp->fk_price_expression)) {
+ $priceparser = new PriceParser($db);
+ $price_result = $priceparser->parse_product_supplier($idprod, $objp->fk_price_expression, $objp->quantity, $objp->tva_tx);
+ if ($price_result >= 0) {
+ $objp->fprice = $price_result;
+ if ($objp->quantity >= 1)
+ {
+ $objp->unitprice = $objp->fprice / $objp->quantity;
+ }
+ }
+ }
+
$price = $objp->fprice * (1 - $objp->remise_percent / 100);
$unitprice = $objp->unitprice * (1 - $objp->remise_percent / 100);
diff --git a/htdocs/fourn/class/fournisseur.product.class.php b/htdocs/fourn/class/fournisseur.product.class.php
old mode 100644
new mode 100755
index 75ac674e877..f5fbf96f0ea
--- a/htdocs/fourn/class/fournisseur.product.class.php
+++ b/htdocs/fourn/class/fournisseur.product.class.php
@@ -27,6 +27,7 @@
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.class.php';
+require_once DOL_DOCUMENT_ROOT.'/product/class/priceparser.class.php';
/**
@@ -55,6 +56,8 @@ class ProductFournisseur extends Product
var $fourn_unitprice;
var $fourn_tva_npr;
+ var $fk_price_expression;
+
/**
* Constructor
@@ -320,13 +323,14 @@ class ProductFournisseur extends Product
/**
* Loads the price information of a provider
*
- * @param int $rowid Line id
- * @return int < 0 if KO, 0 if OK but not found, > 0 if OK
+ * @param int $rowid Line id
+ * @param int $ignore_expression Ignores the math expression for calculating price and uses the db value instead
+ * @return int < 0 if KO, 0 if OK but not found, > 0 if OK
*/
- function fetch_product_fournisseur_price($rowid)
+ function fetch_product_fournisseur_price($rowid, $ignore_expression = 0)
{
$sql = "SELECT pfp.rowid, pfp.price, pfp.quantity, pfp.unitprice, pfp.remise_percent, pfp.remise, pfp.tva_tx, pfp.fk_availability,";
- $sql.= " pfp.fk_soc, pfp.ref_fourn, pfp.fk_product, pfp.charges, pfp.unitcharges"; // , pfp.recuperableonly as fourn_tva_npr"; FIXME this field not exist in llx_product_fournisseur_price
+ $sql.= " pfp.fk_soc, pfp.ref_fourn, pfp.fk_product, pfp.charges, pfp.unitcharges, pfp.fk_price_expression"; // , pfp.recuperableonly as fourn_tva_npr"; FIXME this field not exist in llx_product_fournisseur_price
$sql.= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price as pfp";
$sql.= " WHERE pfp.rowid = ".$rowid;
@@ -351,6 +355,25 @@ class ProductFournisseur extends Product
$this->fk_product = $obj->fk_product;
$this->fk_availability = $obj->fk_availability;
//$this->fourn_tva_npr = $obj->fourn_tva_npr; // FIXME this field not exist in llx_product_fournisseur_price
+ $this->fk_price_expression = $obj->fk_price_expression;
+
+ if (empty($ignore_expression) && !empty($this->fk_price_expression)) {
+ $priceparser = new PriceParser($this->db);
+ $price_result = $priceparser->parse_product_supplier($this->fk_product, $this->fk_price_expression, $this->fourn_qty, $this->fourn_tva_tx);
+ if ($price_result >= 0) {
+ $this->fourn_price = $price_result;
+ //recalculation of unitprice, as probably the price changed...
+ if ($this->fourn_qty!=0)
+ {
+ $this->fourn_unitprice = price2num($this->fourn_price/$this->fourn_qty,'MU');
+ }
+ else
+ {
+ $this->fourn_unitprice="";
+ }
+ }
+ }
+
return 1;
}
else
@@ -379,7 +402,7 @@ class ProductFournisseur extends Product
global $conf;
$sql = "SELECT s.nom as supplier_name, s.rowid as fourn_id,";
- $sql.= " pfp.rowid as product_fourn_pri_id, pfp.ref_fourn, pfp.fk_product as product_fourn_id,";
+ $sql.= " pfp.rowid as product_fourn_pri_id, pfp.ref_fourn, pfp.fk_product as product_fourn_id, pfp.fk_price_expression,";
$sql.= " pfp.price, pfp.quantity, pfp.unitprice, pfp.remise_percent, pfp.remise, pfp.tva_tx, pfp.fk_availability, pfp.charges, pfp.unitcharges, pfp.info_bits";
$sql.= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price as pfp";
$sql.= ", ".MAIN_DB_PREFIX."societe as s";
@@ -416,6 +439,16 @@ class ProductFournisseur extends Product
$prodfourn->fk_availability = $record["fk_availability"];
$prodfourn->id = $prodid;
$prodfourn->fourn_tva_npr = $record["info_bits"];
+ $prodfourn->fk_price_expression = $record["fk_price_expression"];
+
+ if (!empty($prodfourn->fk_price_expression)) {
+ $priceparser = new PriceParser($this->db);
+ $price_result = $priceparser->parse_product_supplier($prodid, $prodfourn->fk_price_expression, $prodfourn->fourn_qty, $prodfourn->fourn_tva_tx);
+ if ($price_result >= 0) {
+ $prodfourn->fourn_price = $price_result;
+ $prodfourn->fourn_unitprice = null; //force recalculation of unitprice, as probably the price changed...
+ }
+ }
if (!isset($prodfourn->fourn_unitprice))
{
@@ -468,7 +501,7 @@ class ProductFournisseur extends Product
$sql = "SELECT s.nom as supplier_name, s.rowid as fourn_id,";
$sql.= " pfp.rowid as product_fourn_price_id, pfp.ref_fourn,";
$sql.= " pfp.price, pfp.quantity, pfp.unitprice, pfp.tva_tx, pfp.charges, pfp.unitcharges, ";
- $sql.= " pfp.remise, pfp.remise_percent";
+ $sql.= " pfp.remise, pfp.remise_percent, pfp.fk_price_expression";
$sql.= " FROM ".MAIN_DB_PREFIX."societe as s, ".MAIN_DB_PREFIX."product_fournisseur_price as pfp";
$sql.= " WHERE s.entity IN (".getEntity('societe', 1).")";
$sql.= " AND pfp.fk_product = ".$prodid;
@@ -495,6 +528,7 @@ class ProductFournisseur extends Product
$this->fourn_tva_tx = $record["tva_tx"];
$this->fourn_id = $record["fourn_id"];
$this->fourn_name = $record["supplier_name"];
+ $this->fk_price_expression = $record["fk_price_expression"];
$this->id = $prodid;
$this->db->free($resql);
return 1;
@@ -506,6 +540,39 @@ class ProductFournisseur extends Product
}
}
+ /**
+ * Sets the price expression
+ *
+ * @param string $expression Expression
+ * @return int <0 if KO, >0 if OK
+ */
+ function set_price_expression($expression_id)
+ {
+ global $conf;
+
+ // Clean parameters
+ $this->db->begin();
+
+ $sql = "UPDATE ".MAIN_DB_PREFIX."product_fournisseur_price";
+ $sql.= " SET fk_price_expression = ".$expression_id;
+ $sql.= " WHERE rowid = ".$this->product_fourn_price_id;
+
+ dol_syslog(get_class($this)."::set_price_expression", LOG_DEBUG);
+
+ $resql = $this->db->query($sql);
+ if ($resql)
+ {
+ $this->db->commit();
+ return 1;
+ }
+ else
+ {
+ $this->error=$this->db->error()." sql=".$sql;
+ $this->db->rollback();
+ return -1;
+ }
+ }
+
/**
* Display supplier of product
*
diff --git a/htdocs/fourn/commande/card.php b/htdocs/fourn/commande/card.php
index f1e5449cca7..f85a7a042f2 100644
--- a/htdocs/fourn/commande/card.php
+++ b/htdocs/fourn/commande/card.php
@@ -1544,7 +1544,7 @@ elseif (! empty($object->id))
print '';
// Bank Account
- if (! empty($conf->global->BANK_ASK_PAYMENT_BANK_DURING_SUPPLIER_ORDER) && ! empty($conf->banque->enabled))
+ if (! empty($conf->global->BANK_ASK_PAYMENT_BANK_DURING_SUPPLIER_ORDER) && ! empty($conf->banque->enabled))
{
print '
';
print '
';
diff --git a/htdocs/product/class/priceexpression.class.php b/htdocs/product/class/priceexpression.class.php
new file mode 100755
index 00000000000..a32aadff1ff
--- /dev/null
+++ b/htdocs/product/class/priceexpression.class.php
@@ -0,0 +1,349 @@
+
+ * Copyright (C) 2014 Juanjo Menent
+/* Copyright (C) 2014 Ion Agorria
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+/**
+ * \file htdocs/product/class/priceexpresion.class.php
+ * \ingroup product
+ * \brief Class for accesing price expression table
+ */
+
+
+/**
+ * Class for accesing price expression table
+ */
+class PriceExpression
+{
+ var $db; //!< To store db handler
+ var $error; //!< To return error code (or message)
+ var $errors=array(); //!< To return several error codes (or messages)
+ var $id;
+ var $title;
+ var $expression;
+
+ /**
+ * Constructor
+ *
+ * @param DoliDb $db Database handler
+ */
+ function __construct($db)
+ {
+ $this->db = $db;
+ return 1;
+ }
+
+
+ /**
+ * Create object into database
+ *
+ * @param User $user User that creates
+ * @param int $notrigger 0=launch triggers after, 1=disable triggers
+ * @return int <0 if KO, Id of created object if OK
+ */
+ function create($user, $notrigger=0)
+ {
+ $error=0;
+
+ // Clean parameters
+ if (isset($this->title)) $this->title=trim($this->title);
+ if (isset($this->expression)) $this->expression=trim($this->expression);
+
+ // Insert request
+ $sql = "INSERT INTO ".MAIN_DB_PREFIX."price_expression (";
+ $sql.= "title, expression";
+ $sql.= ") VALUES (";
+ $sql.= " ".(isset($this->title)?"'".$this->db->escape($this->title)."'":"''").",";
+ $sql.= " ".(isset($this->expression)?"'".$this->db->escape($this->expression)."'":"''");
+ $sql.= ")";
+
+ $this->db->begin();
+
+ dol_syslog(get_class($this)."::create", LOG_DEBUG);
+ $resql=$this->db->query($sql);
+ if (! $resql) { $error++; $this->errors[]="Error ".$this->db->lasterror(); }
+
+ if (! $error)
+ {
+ $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
+
+ if (! $notrigger)
+ {
+ // Uncomment this and change MYOBJECT to your own tag if you
+ // want this action calls a trigger.
+
+ //// Call triggers
+ //$result=$this->call_trigger('MYOBJECT_CREATE',$user);
+ //if ($result < 0) { $error++; //Do also what you must do to rollback action if trigger fail}
+ //// End call triggers
+ }
+ }
+
+ // Commit or rollback
+ if ($error)
+ {
+ foreach($this->errors as $errmsg)
+ {
+ dol_syslog(__METHOD__." ".$errmsg, LOG_ERR);
+ $this->error.=($this->error?', '.$errmsg:$errmsg);
+ }
+ $this->db->rollback();
+ return -1*$error;
+ }
+ else
+ {
+ $this->db->commit();
+ return $this->id;
+ }
+ }
+
+
+ /**
+ * Load object in memory from the database
+ *
+ * @param int $id Id object
+ * @return int < 0 if KO, 0 if OK but not found, > 0 if OK
+ */
+ function fetch($id)
+ {
+ $sql = "SELECT title, expression";
+ $sql.= " FROM ".MAIN_DB_PREFIX."price_expression";
+ $sql.= " WHERE rowid = ".$id;
+
+ dol_syslog(get_class($this)."::fetch");
+ $resql=$this->db->query($sql);
+ if ($resql)
+ {
+ $obj = $this->db->fetch_object($resql);
+ if ($obj)
+ {
+ $this->id = $id;
+ $this->title = $obj->title;
+ $this->expression = $obj->expression;
+ return 1;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ else
+ {
+ $this->error="Error ".$this->db->lasterror();
+ return -1;
+ }
+ }
+
+ /**
+ * List all price expressions
+ *
+ * @return array Array of price expressions
+ */
+ function list_price_expression()
+ {
+ $sql = "SELECT rowid, title, expression";
+ $sql.= " FROM ".MAIN_DB_PREFIX."price_expression";
+ $sql.= " ORDER BY title";
+
+ dol_syslog(get_class($this)."::list_price_expression");
+ $resql=$this->db->query($sql);
+ if ($resql)
+ {
+ $retarray = array();
+
+ while ($record = $this->db->fetch_array($resql))
+ {
+ $price_expression_obj = new PriceExpression($this->db);
+ $price_expression_obj->id = $record["rowid"];
+ $price_expression_obj->title = $record["title"];
+ $price_expression_obj->expression = $record["expression"];
+ $retarray[]=$price_expression_obj;
+ }
+
+ $this->db->free($resql);
+ return $retarray;
+ }
+ else
+ {
+ $this->error=$this->db->error();
+ return -1;
+ }
+ }
+
+
+ /**
+ * Returns any existing rowid with specified title
+ *
+ * @param String $title Title of expression
+ * @return int < 0 if KO, 0 if OK but not found, > 0 rowid
+ */
+ function find_title($title)
+ {
+ $sql = "SELECT rowid";
+ $sql.= " FROM ".MAIN_DB_PREFIX."price_expression";
+ $sql.= " WHERE title = '".$this->db->escape($title)."'";
+
+ dol_syslog(get_class($this)."::find_title");
+ $resql=$this->db->query($sql);
+ if ($resql)
+ {
+ $obj = $this->db->fetch_object($resql);
+ if ($obj)
+ {
+ return (int) $obj->rowid;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ else
+ {
+ $this->error="Error ".$this->db->lasterror();
+ return -1;
+ }
+ }
+
+
+ /**
+ * Update object into database
+ *
+ * @param User $user User that modifies
+ * @param int $notrigger 0=launch triggers after, 1=disable triggers
+ * @return int <0 if KO, >0 if OK
+ */
+ function update($user=0, $notrigger=0)
+ {
+ $error=0;
+
+ // Clean parameters
+ if (isset($this->title)) $this->title=trim($this->title);
+ if (isset($this->expression)) $this->expression=trim($this->expression);
+
+ // Update request
+ $sql = "UPDATE ".MAIN_DB_PREFIX."price_expression SET";
+ $sql.= " title = ".(isset($this->title)?"'".$this->db->escape($this->title)."'":"''").",";
+ $sql.= " expression = ".(isset($this->expression)?"'".$this->db->escape($this->expression)."'":"''")."";
+ $sql.= " WHERE rowid = ".$this->id;
+
+ $this->db->begin();
+
+ dol_syslog(get_class($this)."::update");
+ $resql = $this->db->query($sql);
+ if (! $resql) { $error++; $this->errors[]="Error ".$this->db->lasterror(); }
+
+ if (! $error)
+ {
+ if (! $notrigger)
+ {
+ // Uncomment this and change MYOBJECT to your own tag if you
+ // want this action calls a trigger.
+
+ //// Call triggers
+ //$result=$this->call_trigger('MYOBJECT_MODIFY',$user);
+ //if ($result < 0) { $error++; //Do also what you must do to rollback action if trigger fail}
+ //// End call triggers
+ }
+ }
+
+ // Commit or rollback
+ if ($error)
+ {
+ foreach($this->errors as $errmsg)
+ {
+ dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
+ $this->error.=($this->error?', '.$errmsg:$errmsg);
+ }
+ $this->db->rollback();
+ return -1*$error;
+ }
+ else
+ {
+ $this->db->commit();
+ return 1;
+ }
+ }
+
+
+ /**
+ * Delete object in database
+ *
+ * @param int $rowid Row id of expression
+ * @param User $user User that deletes
+ * @param int $notrigger 0=launch triggers after, 1=disable triggers
+ * @return int <0 if KO, >0 if OK
+ */
+ function delete($rowid, $user, $notrigger=0)
+ {
+ $error=0;
+
+ $this->db->begin();
+
+ if (! $error)
+ {
+ if (! $notrigger)
+ {
+ // Uncomment this and change MYOBJECT to your own tag if you
+ // want this action calls a trigger.
+
+ //// Call triggers
+ //$result=$this->call_trigger('MYOBJECT_DELETE',$user);
+ //if ($result < 0) { $error++; //Do also what you must do to rollback action if trigger fail}
+ //// End call triggers
+ }
+ }
+
+ if (! $error)
+ {
+ $sql = "DELETE FROM ".MAIN_DB_PREFIX."price_expression";
+ $sql.= " WHERE rowid = ".$rowid;
+
+ dol_syslog(get_class($this)."::delete");
+ $resql = $this->db->query($sql);
+ if (! $resql) { $error++; $this->errors[]="Error ".$this->db->lasterror(); }
+ }
+
+ // Commit or rollback
+ if ($error)
+ {
+ foreach($this->errors as $errmsg)
+ {
+ dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
+ $this->error.=($this->error?', '.$errmsg:$errmsg);
+ }
+ $this->db->rollback();
+ return -1*$error;
+ }
+ else
+ {
+ $this->db->commit();
+ return 1;
+ }
+ }
+
+ /**
+ * Initialise object with example values
+ * Id must be 0 if object instance is a specimen
+ *
+ * @return void
+ */
+ function initAsSpecimen()
+ {
+ $this->id=0;
+ $this->expression='';
+ }
+}
diff --git a/htdocs/product/class/priceparser.class.php b/htdocs/product/class/priceparser.class.php
new file mode 100755
index 00000000000..5b66a74ae20
--- /dev/null
+++ b/htdocs/product/class/priceparser.class.php
@@ -0,0 +1,240 @@
+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+/**
+ * \file htdocs/product/class/priceparser.class.php
+ * \ingroup product
+ * \brief File of class to calculate prices using expression
+ */
+require_once DOL_DOCUMENT_ROOT.'/includes/evalmath/evalmath.class.php';
+require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
+require_once DOL_DOCUMENT_ROOT.'/product/class/priceexpression.class.php';
+require_once DOL_DOCUMENT_ROOT.'/core/class/extrafields.class.php';
+
+/**
+ * Class to parse product price expressions
+ */
+class PriceParser
+{
+ protected $db;
+ // Limit of expressions per price
+ public $limit = 100;
+ // The error that ocurred when parsing price
+ public $error;
+ // The expression that caused the error
+ public $error_expr;
+ //The special char
+ public $special_chr = "#";
+ //The separator char
+ public $separator_chr = ";";
+
+ /**
+ * Constructor
+ *
+ * @param DoliDB $db Database handler
+ */
+ function __construct($db)
+ {
+ $this->db = $db;
+ }
+
+ /**
+ * Returns translated error
+ *
+ * @return string Translated error
+ */
+ public function translated_error()
+ {
+ global $langs;
+ $langs->load("errors");
+ /*
+ -No arg
+ 9, an unexpected error occured
+ 14, division by zero
+ 19, expression not found
+ 20, empty expression
+
+ -1 Arg
+ 1, cannot assign to constant '%s'
+ 2, cannot redefine built-in function '%s'
+ 3, undefined variable '%s' in function definition
+ 4, illegal character '%s'
+ 5, unexpected '%s'
+ 8, unexpected operator '%s'
+ 10, operator '%s' lacks operand
+ 11, expecting '%s'
+ 17, undefined variable '%s'
+ 21, empty result '%s'
+ 22, negative result '%s'
+
+ -2 Args
+ 6, wrong number of arguments (%s given, %s expected)
+
+ -internal errors
+ 7, internal error
+ 12, internal error
+ 13, internal error
+ 15, internal error
+ 16, internal error
+ 18, internal error
+ */
+ if (empty($this->error)) {
+ return $langs->trans("ErrorPriceExpressionUnknown", 0); //this is not supposed to happen
+ }
+ list($code, $info) = $this->error;
+ if (in_array($code, array(9, 14, 19, 20))) //Errors which have 0 arg
+ {
+ return $langs->trans("ErrorPriceExpression".$code);
+ }
+ else if (in_array($code, array(1, 2, 3, 4, 5, 8, 10, 11, 17, 21, 22))) //Errors which have 1 arg
+ {
+ return $langs->trans("ErrorPriceExpression".$code, $info);
+ }
+ else if (in_array($code, array(6))) //Errors which have 2 args
+ {
+ return $langs->trans("ErrorPriceExpression".$code, $info[0], $info[1]);
+ }
+ else if (in_array($code, array(7, 12, 13, 15, 16, 18))) //Internal errors
+ {
+ return $langs->trans("ErrorPriceExpressionInternal", $code);
+ }
+ else //Unknown errors
+ {
+ return $langs->trans("ErrorPriceExpressionUnknown", $code);
+ }
+ }
+
+ /**
+ * Calculates price based on expression
+ *
+ * @param array $values Strings to replaces
+ * @param String $expression The expression to parse
+ * @return int > 0 if OK, < 1 if KO
+ */
+ public function parse_expression($values, $expression)
+ {
+ //Check if empty
+ $expression = trim($expression);
+ if (empty($expression))
+ {
+ $this->error = array(20, null);
+ return -1;
+ }
+
+ //Prepare the lib, parameters and values
+ $em = new EvalMath();
+ $em->suppress_errors = true; //Don't print errors on page
+ $this->error_expr = null;
+ $search = array();
+ $replace = array();
+ foreach ($values as $key => $value) {
+ if ($value !== null) {
+ $search[] = $this->special_chr.$key.$this->special_chr;
+ $replace[] = $value;
+ }
+ }
+
+ //Iterate over each expression splitted by $separator_chr
+ $expression = str_replace("\n", $this->separator_chr, $expression);
+ $expressions = explode($this->separator_chr, $expression);
+ $expressions = array_slice($expressions, 0, $limit);
+ foreach ($expressions as $expr) {
+ $expr = trim($expr);
+ if (!empty($expr))
+ {
+ $expr = str_ireplace($search, $replace, $expr);
+ $last_result = $em->evaluate($expr);
+ $this->error = $em->last_error_code;
+ if ($this->error !== null) { //$em->last_error is null if no error happened, so just check if error is not null
+ $this->error_expr = $expr;
+ return -2;
+ }
+ }
+ }
+ $vars = $em->vars();
+ if (empty($vars["price"])) {
+ $vars["price"] = $last_result;
+ }
+ if ($vars["price"] === null)
+ {
+ $this->error = array(21, $expression);
+ return -3;
+ }
+ if ($vars["price"] < 0)
+ {
+ $this->error = array(22, $expression);
+ return -4;
+ }
+ return $vars["price"];
+ }
+
+ /**
+ * Calculates supplier product price based on product id and string expression
+ *
+ * @param int $product The Product id to get information
+ * @param string $expression The expression to parse
+ * @param int $quantity Min quantity
+ * @param int $tva_tx VAT rate
+ * @param array $extra_values Any aditional values for expression
+ * @return int > 0 if OK, < 1 if KO
+ */
+ public function parse_product_supplier_expression($product_id, $expression, $quantity = null, $tva_tx = null, $extra_values = array())
+ {
+ //Accessible values by expressions
+ $expression_values = array(
+ "quantity" => $quantity,
+ "tva_tx" => $tva_tx,
+ );
+ $expression_values = array_merge($expression_values, $extra_values);
+
+ //Retreive all extrafield for product and add it to expression_values
+ $extrafields = new ExtraFields($this->db);
+ $extralabels = $extrafields->fetch_name_optionals_label('product', true);
+ $product = new Product($this->db);
+ $product->fetch_optionals($product_id, $extralabels);
+ foreach($extrafields->attribute_label as $key=>$label)
+ {
+ $expression_values['options_'.$key] = $product->array_options['options_'.$key];
+ }
+
+ //Parse the expression and return the price
+ return $this->parse_expression($expression_values, $expression);
+ }
+
+ /**
+ * Calculates supplier product price based on product id and expression id
+ *
+ * @param int $product The Product id to get information
+ * @param int $expression_id The expression to parse
+ * @param int $quantity Min quantity
+ * @param int $tva_tx VAT rate
+ * @param array $extra_values Any aditional values for expression
+ * @return int > 0 if OK, < 1 if KO
+ */
+ public function parse_product_supplier($product_id, $expression_id, $quantity = null, $tva_tx = null, $extra_values = array())
+ {
+ $price_expression = new PriceExpression($this->db);
+ $res = $price_expression->fetch($expression_id);
+ if ($res > 1) {
+ $this->error = array(19, null);
+ return -1;
+ }
+
+ //Parse the expression and return the price
+ return $this->parse_product_supplier_expression($product_id, $price_expression->expression, $quantity, $tva_tx, $extra_values);
+ }
+}
\ No newline at end of file
diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php
old mode 100644
new mode 100755
index e2898943509..1eeea8a6b9e
--- a/htdocs/product/class/product.class.php
+++ b/htdocs/product/class/product.class.php
@@ -1166,11 +1166,12 @@ class Product extends CommonObject
*/
function get_buyprice($prodfournprice,$qty,$product_id=0,$fourn_ref=0)
{
+ require_once DOL_DOCUMENT_ROOT.'/product/class/priceparser.class.php';
$result = 0;
// We do select by searching with qty and prodfournprice
$sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity,";
- $sql.= " pfp.fk_product, pfp.ref_fourn, pfp.fk_soc, pfp.tva_tx";
+ $sql.= " pfp.fk_product, pfp.ref_fourn, pfp.fk_soc, pfp.tva_tx, pfp.fk_price_expression";
$sql.= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price as pfp";
$sql.= " WHERE pfp.rowid = ".$prodfournprice;
if ($qty) $sql.= " AND pfp.quantity <= ".$qty;
@@ -1182,6 +1183,13 @@ class Product extends CommonObject
$obj = $this->db->fetch_object($resql);
if ($obj && $obj->quantity > 0) // If found
{
+ if (!empty($obj->fk_price_expression)) {
+ $priceparser = new PriceParser($this->db);
+ $price_result = $priceparser->parse_product_supplier($obj->fk_product, $obj->fk_price_expression, $obj->quantity, $obj->tva_tx);
+ if ($price_result >= 0) {
+ $obj->price = $price_result;
+ }
+ }
$this->buyprice = $obj->price; // \deprecated
$this->fourn_pu = $obj->price / $obj->quantity; // Prix unitaire du produit pour le fournisseur $fourn_id
$this->ref_fourn = $obj->ref_fourn; // Ref supplier
@@ -1193,7 +1201,7 @@ class Product extends CommonObject
{
// We do same select again but searching with qty, ref and id product
$sql = "SELECT pfp.rowid, pfp.price as price, pfp.quantity as quantity, pfp.fk_soc,";
- $sql.= " pfp.fk_product, pfp.ref_fourn as ref_supplier, pfp.tva_tx";
+ $sql.= " pfp.fk_product, pfp.ref_fourn as ref_supplier, pfp.tva_tx, pfp.fk_price_expression";
$sql.= " FROM ".MAIN_DB_PREFIX."product_fournisseur_price as pfp";
$sql.= " WHERE pfp.ref_fourn = '".$fourn_ref."'";
$sql.= " AND pfp.fk_product = ".$product_id;
@@ -1208,6 +1216,13 @@ class Product extends CommonObject
$obj = $this->db->fetch_object($resql);
if ($obj && $obj->quantity > 0) // If found
{
+ if (!empty($obj->fk_price_expression)) {
+ $priceparser = new PriceParser($this->db);
+ $price_result = $priceparser->parse_product_supplier($obj->fk_product, $obj->fk_price_expression, $obj->quantity, $obj->tva_tx);
+ if ($result >= 0) {
+ $obj->price = $price_result;
+ }
+ }
$this->buyprice = $obj->price; // deprecated
$this->fourn_qty = $obj->quantity; // min quantity for price
$this->fourn_pu = $obj->price / $obj->quantity; // Prix unitaire du produit pour le fournisseur $fourn_id
diff --git a/htdocs/product/expression.php b/htdocs/product/expression.php
new file mode 100755
index 00000000000..bb622224d92
--- /dev/null
+++ b/htdocs/product/expression.php
@@ -0,0 +1,221 @@
+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+/**
+ * \file htdocs/product/expression.php
+ * \ingroup product
+ * \brief Page for editing expression
+ */
+
+require '../main.inc.php';
+require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
+require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
+require_once DOL_DOCUMENT_ROOT.'/product/class/priceexpression.class.php';
+require_once DOL_DOCUMENT_ROOT.'/product/class/priceparser.class.php';
+
+$langs->load("products");
+$langs->load("accountancy"); //"Back" translation is on this file
+
+$id = GETPOST('id', 'int');
+$eid = GETPOST('eid', 'int');
+$action = GETPOST('action', 'alpha');
+$title = GETPOST('expression_title', 'alpha');
+$expression = GETPOST('expression');
+$tab = GETPOST('tab', 'alpha');
+$tab = (!empty($tab)) ? $tab : 'card';
+
+// Security check
+$result=restrictedArea($user,'produit|service&fournisseur',$id,'product&product','','','rowid');
+
+//Initialize objects
+$product = new Product($db);
+$product->fetch($id, '');
+
+$price_expression = new PriceExpression($db);
+
+//Fetch expression data
+if (empty($eid)) //This also disables fetch when eid == 0
+{
+ $eid = 0;
+}
+else if ($action != 'delete')
+{
+ $price_expression->fetch($eid);
+}
+
+/*
+ * Actions
+ */
+
+if ($action == 'add')
+{
+ if ($eid == 0)
+ {
+ $result = $price_expression->find_title($title);
+ if ($result == 0) //No existing entry found with title, ok
+ {
+ //Check the expression validity by parsing it
+ $priceparser = new PriceParser($db);
+ $price_result = $priceparser->parse_product_supplier_expression($id, $expression, 0, 0);
+ if ($price_result < 0) { //Expression is not valid
+ setEventMessage($priceparser->translated_error(), 'errors');
+ }
+ else
+ {
+ $price_expression->title = $title;
+ $price_expression->expression = $expression;
+ $result = $price_expression->create($user);
+ if ($result > 0) //created successfully, set the eid to newly created entry
+ {
+ $eid = $price_expression->id;
+ }
+ else
+ {
+ setEventMessage("add: ".$price_expression->error, 'errors');
+ }
+ }
+ }
+ else if ($result < 0)
+ {
+ setEventMessage("add find: ".$price_expression->error, 'errors');
+ }
+ else
+ {
+ setEventMessage($langs->trans("ErrorRecordAlreadyExists"), 'errors');
+ }
+ }
+}
+
+if ($action == 'update')
+{
+ if ($eid != 0)
+ {
+ $result = $price_expression->find_title($title);
+ if ($result == 0 || $result == $eid) //No existing entry found with title or existing one is the current one, ok
+ {
+ //Check the expression validity by parsing it
+ $priceparser = new PriceParser($db);
+ $price_result = $priceparser->parse_product_supplier_expression($id, $expression, 0, 0);
+ if ($price_result < 0) { //Expression is not valid
+ setEventMessage($priceparser->translated_error(), 'errors');
+ }
+ else
+ {
+ $price_expression->id = $eid;
+ $price_expression->title = $title;
+ $price_expression->expression = $expression;
+ $result = $price_expression->update($user);
+ if ($result < 0)
+ {
+ setEventMessage("update: ".$price_expression->error, 'errors');
+ }
+ }
+ }
+ else if ($result < 0)
+ {
+ setEventMessage("update find: ".$price_expression->error, 'errors');
+ }
+ else
+ {
+ setEventMessage($langs->trans("ErrorRecordAlreadyExists"), 'errors');
+ }
+ }
+}
+
+if ($action == 'delete')
+{
+ if ($eid != 0)
+ {
+ $result = $price_expression->delete($eid, $user);
+ if ($result < 0)
+ {
+ setEventMessage("delete: ".$price_expression->error, 'errors');
+ }
+ $eid = 0;
+ }
+}
+
+/*
+ * View
+ */
+
+//Header
+llxHeader("","",$langs->trans("CardProduct".$product->type));
+print_fiche_titre($langs->trans("PriceExpressionEditor"));
+$form = new Form($db);
+
+//Form/Table
+print '';
+
+// This code reloads the page depending of selected option, goes back in history when back is pressed
+print '';
+
+llxFooter();
+$db->close();
diff --git a/htdocs/product/fournisseurs.php b/htdocs/product/fournisseurs.php
old mode 100644
new mode 100755
index 220b6ad758e..bf5a268f22a
--- a/htdocs/product/fournisseurs.php
+++ b/htdocs/product/fournisseurs.php
@@ -5,6 +5,7 @@
* Copyright (C) 2005-2012 Regis Houssin
* Copyright (C) 2010-2012 Juanjo Menent
* Copyright (C) 2012 Christophe Battarel
+ * Copyright (C) 2014 Ion Agorria
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -30,6 +31,8 @@ require '../main.inc.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propal.class.php';
require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
+require_once DOL_DOCUMENT_ROOT.'/product/class/priceexpression.class.php';
+require_once DOL_DOCUMENT_ROOT.'/product/class/priceparser.class.php';
$langs->load("products");
$langs->load("suppliers");
@@ -174,6 +177,27 @@ if ($action == 'updateprice' && GETPOST('cancel') <> $langs->trans("Cancel"))
{
$error++;
setEventMessage($product->error, 'errors');
+ }
+ else
+ {
+ $price_expression = GETPOST('eid', 'int') == 0 ? 'NULL' : GETPOST('eid', 'int'); //Discard expression if not in expression mode
+ if ($price_expression != 'NULL') {
+ //Check the expression validity by parsing it
+ $priceparser = new PriceParser($db);
+ $price_result = $priceparser->parse_product_supplier($id, $price_expression, $quantity, $tva_tx);
+ if ($price_result < 0) { //Expression is not valid
+ $error++;
+ setEventMessage($priceparser->translated_error(), 'errors');
+ }
+ }
+ if (! $error && ! empty($conf->dinamicprices->enabled)) {
+ $ret=$product->set_price_expression($price_expression);
+ if ($ret < 0)
+ {
+ $error++;
+ setEventMessage($product->error, 'errors');
+ }
+ }
}
}
@@ -267,7 +291,7 @@ if ($id || $ref)
if ($rowid)
{
- $product->fetch_product_fournisseur_price($rowid);
+ $product->fetch_product_fournisseur_price($rowid, 1); //Ignore the math expression when getting the price
print_fiche_titre($langs->trans("ChangeSupplierPrice"));
}
else
@@ -369,8 +393,41 @@ if ($id || $ref)
print '';
print '
';
+ if (! empty($conf->dinamicprices->enabled)) { //Only show price mode and expression selector if module is enabled
+ // Price mode selector
+ print '
';
+ // This code hides the numeric price input if is not selected, loads the editor page if editor button is pressed
+ print '';
+ }
+
// Price qty min
- print '