';
@@ -4402,7 +4403,7 @@ function dolGetFirstLastname($firstname,$lastname,$nameorder=-1)
}
else if ($nameorder == 2)
{
- $ret.=$firstname;
+ $ret.=$firstname;
}
else
{
@@ -5225,7 +5226,7 @@ function natural_search($fields, $value, $mode=0, $nofirstand=0)
/**
* Return the filename of file to get the thumbs
- *
+ *
* @param string $file Original filename
* @param string $extName Extension to differenciate thumb file name ('', '_small', '_mini')
* @param string $extImgTarget Force image format for thumbs. Use '' to keep same extension than original image.
@@ -5235,10 +5236,10 @@ function getImageFileNameForSize($file, $extName, $extImgTarget='')
{
$dirName = dirname($file);
if ($dirName == '.') $dirName='';
-
+
$fileName = preg_replace('/(\.gif|\.jpeg|\.jpg|\.png|\.bmp)$/i','',$file); // On enleve extension quelquesoit la casse
$fileName = basename($fileName);
-
+
if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.jpg$/i',$file)?'.jpg':'');
if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.jpeg$/i',$file)?'.jpeg':'');
if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.gif$/i',$file)?'.gif':'');
@@ -5246,9 +5247,9 @@ function getImageFileNameForSize($file, $extName, $extImgTarget='')
if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.bmp$/i',$file)?'.bmp':'');
if (! $extImgTarget) return $file;
-
+
$subdir='';
if ($extName) $subdir = 'thumbs/';
-
+
return $dirName.$subdir.$fileName.$extName.$extImgTarget; // New filename for thumb
}
diff --git a/htdocs/core/lib/product.lib.php b/htdocs/core/lib/product.lib.php
index ff2e9c14dbe..69eb63717b2 100644
--- a/htdocs/core/lib/product.lib.php
+++ b/htdocs/core/lib/product.lib.php
@@ -3,6 +3,7 @@
* Copyright (C) 2007 Rodolphe Quiedeville
* Copyright (C) 2009-2010 Regis Houssin
* Copyright (C) 2015 Raphaël Doursenaud
+ * Copyright (C) 2015 Marcos García
*
* 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
@@ -132,18 +133,6 @@ function product_prepare_head($object)
$head[$h][2] = 'documents';
$h++;
-
- // More tabs from canvas
- // TODO Is this still used ?
- if (isset($object->onglets) && is_array($object->onglets))
- {
- foreach ($object->onglets as $onglet)
- {
- $head[$h] = $onglet;
- $h++;
- }
- }
-
complete_head_from_modules($conf,$langs,$object,$head,$h,'product', 'remove');
return $head;
@@ -166,6 +155,16 @@ function product_admin_prepare_head()
$head[$h][2] = 'general';
$h++;
+ if (!empty($conf->global->PRODUIT_MULTIPRICES) && ! empty($conf->global->PRODUIT_MULTIPRICES_ALLOW_AUTOCALC_PRICELEVEL))
+ {
+ $head[$h] = array(
+ 0 => DOL_URL_ROOT."/product/admin/price_rules.php",
+ 1 => $langs->trans('MultipriceRules'),
+ 2 => 'generator'
+ );
+ $h++;
+ }
+
// Show more tabs from modules
// Entries must be declared in modules descriptor with line
// $this->tabs = array('entity:+tabname:Title:@mymodule:/mymodule/mypage.php?id=__ID__'); to add new tab
diff --git a/htdocs/core/lib/project.lib.php b/htdocs/core/lib/project.lib.php
index 94b2de526dd..004ad3a619f 100644
--- a/htdocs/core/lib/project.lib.php
+++ b/htdocs/core/lib/project.lib.php
@@ -977,7 +977,7 @@ function print_projecttasks_array($db, $form, $socid, $projectsListId, $mytasks=
{
$var=!$var;
print "
';
diff --git a/htdocs/core/modules/modWebsite.class.php b/htdocs/core/modules/modWebsite.class.php
index 0e75daa26bd..7bb5a27a0c5 100644
--- a/htdocs/core/modules/modWebsite.class.php
+++ b/htdocs/core/modules/modWebsite.class.php
@@ -62,7 +62,7 @@ class modWebsite extends DolibarrModules
// Config pages
//-------------
- $this->config_page_url = array("website.php");
+ $this->config_page_url = array();
// Dependancies
//-------------
diff --git a/htdocs/core/triggers/interface_50_modLdap_Ldapsynchro.class.php b/htdocs/core/triggers/interface_50_modLdap_Ldapsynchro.class.php
index 7a63a5cdeec..561ba330694 100644
--- a/htdocs/core/triggers/interface_50_modLdap_Ldapsynchro.class.php
+++ b/htdocs/core/triggers/interface_50_modLdap_Ldapsynchro.class.php
@@ -414,7 +414,7 @@ class InterfaceLdapsynchro extends DolibarrTriggers
elseif ($action == 'MEMBER_CREATE')
{
dol_syslog("Trigger '".$this->name."' for action '$action' launched by ".__FILE__.". id=".$object->id);
- if (! empty($conf->global->LDAP_MEMBER_ACTIVE))
+ if (! empty($conf->global->LDAP_MEMBER_ACTIVE) && (string) $conf->global->LDAP_MEMBER_ACTIVE == '1')
{
$ldap=new Ldap();
$ldap->connect_bind();
@@ -433,7 +433,7 @@ class InterfaceLdapsynchro extends DolibarrTriggers
elseif ($action == 'MEMBER_VALIDATE')
{
dol_syslog("Trigger '".$this->name."' for action '$action' launched by ".__FILE__.". id=".$object->id);
- if (! empty($conf->global->LDAP_MEMBER_ACTIVE))
+ if (! empty($conf->global->LDAP_MEMBER_ACTIVE) && (string) $conf->global->LDAP_MEMBER_ACTIVE == '1')
{
// If status field is setup to be synchronized
if (! empty($conf->global->LDAP_FIELD_MEMBER_STATUS))
@@ -457,7 +457,7 @@ class InterfaceLdapsynchro extends DolibarrTriggers
elseif ($action == 'MEMBER_SUBSCRIPTION')
{
dol_syslog("Trigger '".$this->name."' for action '$action' launched by ".__FILE__.". id=".$object->id);
- if (! empty($conf->global->LDAP_MEMBER_ACTIVE))
+ if (! empty($conf->global->LDAP_MEMBER_ACTIVE) && (string) $conf->global->LDAP_MEMBER_ACTIVE == '1')
{
// If subscriptions fields are setup to be synchronized
if ($conf->global->LDAP_FIELD_MEMBER_FIRSTSUBSCRIPTION_DATE
@@ -485,7 +485,7 @@ class InterfaceLdapsynchro extends DolibarrTriggers
elseif ($action == 'MEMBER_MODIFY')
{
dol_syslog("Trigger '".$this->name."' for action '$action' launched by ".__FILE__.". id=".$object->id);
- if (! empty($conf->global->LDAP_MEMBER_ACTIVE))
+ if (! empty($conf->global->LDAP_MEMBER_ACTIVE) && (string) $conf->global->LDAP_MEMBER_ACTIVE == '1')
{
$ldap=new Ldap();
$ldap->connect_bind();
@@ -522,7 +522,7 @@ class InterfaceLdapsynchro extends DolibarrTriggers
elseif ($action == 'MEMBER_NEW_PASSWORD')
{
dol_syslog("Trigger '".$this->name."' for action '$action' launched by ".__FILE__.". id=".$object->id);
- if (! empty($conf->global->LDAP_MEMBER_ACTIVE))
+ if (! empty($conf->global->LDAP_MEMBER_ACTIVE) && (string) $conf->global->LDAP_MEMBER_ACTIVE == '1')
{
// If password field is setup to be synchronized
if ($conf->global->LDAP_FIELD_PASSWORD || $conf->global->LDAP_FIELD_PASSWORD_CRYPTED)
@@ -546,7 +546,7 @@ class InterfaceLdapsynchro extends DolibarrTriggers
elseif ($action == 'MEMBER_RESILIATE')
{
dol_syslog("Trigger '".$this->name."' for action '$action' launched by ".__FILE__.". id=".$object->id);
- if (! empty($conf->global->LDAP_MEMBER_ACTIVE))
+ if (! empty($conf->global->LDAP_MEMBER_ACTIVE) && (string) $conf->global->LDAP_MEMBER_ACTIVE == '1')
{
// If status field is setup to be synchronized
if (! empty($conf->global->LDAP_FIELD_MEMBER_STATUS))
@@ -570,7 +570,7 @@ class InterfaceLdapsynchro extends DolibarrTriggers
elseif ($action == 'MEMBER_DELETE')
{
dol_syslog("Trigger '".$this->name."' for action '$action' launched by ".__FILE__.". id=".$object->id);
- if (! empty($conf->global->LDAP_MEMBER_ACTIVE))
+ if (! empty($conf->global->LDAP_MEMBER_ACTIVE) && (string) $conf->global->LDAP_MEMBER_ACTIVE == '1')
{
$ldap=new Ldap();
$ldap->connect_bind();
diff --git a/htdocs/fourn/class/fournisseur.product.class.php b/htdocs/fourn/class/fournisseur.product.class.php
index cf580459a9c..d7344787623 100644
--- a/htdocs/fourn/class/fournisseur.product.class.php
+++ b/htdocs/fourn/class/fournisseur.product.class.php
@@ -121,26 +121,38 @@ class ProductFournisseur extends Product
*/
function remove_product_fournisseur_price($rowid)
{
- global $conf;
+ global $conf, $user;
$this->db->begin();
- $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_fournisseur_price";
- $sql.= " WHERE rowid = ".$rowid;
+ // Call trigger
+ $result=$this->call_trigger('SUPPLIER_PRODUCT_BUYPRICE_DELETE',$user);
+ if ($result < 0) $error++;
+ // End call triggers
- dol_syslog(get_class($this)."::remove_product_fournisseur_price", LOG_DEBUG);
- $resql = $this->db->query($sql);
- if ($resql)
+ if (empty($error))
{
+
+ $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_fournisseur_price";
+ $sql.= " WHERE rowid = ".$rowid;
+
+ dol_syslog(get_class($this)."::remove_product_fournisseur_price", LOG_DEBUG);
+ $resql = $this->db->query($sql);
+ if (!$resql)
+ {
+ $this->error=$this->db->lasterror();
+ $error++;
+ }
+ }
+
+ if (empty($error)){
$this->db->commit();
return 1;
- }
- else
- {
- $this->error=$this->db->lasterror();
+ }else{
$this->db->rollback();
return -1;
}
+
}
diff --git a/htdocs/fourn/commande/card.php b/htdocs/fourn/commande/card.php
index 37809f6bf87..cb1754fa8d3 100644
--- a/htdocs/fourn/commande/card.php
+++ b/htdocs/fourn/commande/card.php
@@ -872,6 +872,8 @@ if (empty($reshook))
}
}
+ include DOL_DOCUMENT_ROOT.'/core/actions_printing.inc.php';
+
/*
* Create an order
diff --git a/htdocs/install/mysql/data/llx_c_hrm_function.sql b/htdocs/install/mysql/data/llx_c_hrm_function.sql
index f8b56563689..dd5c6e832bd 100644
--- a/htdocs/install/mysql/data/llx_c_hrm_function.sql
+++ b/htdocs/install/mysql/data/llx_c_hrm_function.sql
@@ -26,9 +26,9 @@
INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(1, 5,'EXECBOARD', 'Executive board', 0, 1);
INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(2, 10, 'MANAGDIR', 'Managing director', 1, 1);
INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(3, 15, 'ACCOUNTMANAG', 'Account manager', 0, 1);
-INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(3, 20, 'ENGAGDIR', 'Engagement director', 1, 1);
-INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(4, 25, 'DIRECTOR', 'Director', 1, 1);
-INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(5, 30, 'PROJMANAG', 'Project manager', 0, 1);
-INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(6, 35, 'DEPHEAD', 'Department head', 0, 1);
-INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(7, 40, 'SECRETAR', 'Secretary', 0, 1);
-INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(8, 45, 'EMPLOYEE', 'Department employee', 0, 1);
+INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(4, 20, 'ENGAGDIR', 'Engagement director', 1, 1);
+INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(5, 25, 'DIRECTOR', 'Director', 1, 1);
+INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(6, 30, 'PROJMANAG', 'Project manager', 0, 1);
+INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(7, 35, 'DEPHEAD', 'Department head', 0, 1);
+INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(8, 40, 'SECRETAR', 'Secretary', 0, 1);
+INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(9, 45, 'EMPLOYEE', 'Department employee', 0, 1);
diff --git a/htdocs/install/mysql/migration/3.8.0-3.9.0.sql b/htdocs/install/mysql/migration/3.8.0-3.9.0.sql
index dac62a2a4b8..ddbf3c79f1f 100755
--- a/htdocs/install/mysql/migration/3.8.0-3.9.0.sql
+++ b/htdocs/install/mysql/migration/3.8.0-3.9.0.sql
@@ -71,7 +71,6 @@ ALTER TABLE llx_contrat ADD COLUMN ref_customer varchar(30);
ALTER TABLE llx_commande ADD COLUMN fk_warehouse integer DEFAULT NULL after fk_shipping_method;
ALTER TABLE llx_ecm_directories MODIFY COLUMN fullpath varchar(750);
-
ALTER TABLE llx_ecm_directories DROP INDEX idx_ecm_directories;
ALTER TABLE llx_ecm_directories ADD UNIQUE INDEX uk_ecm_directories (label, fk_parent, entity);
--ALTER TABLE llx_ecm_directories ADD UNIQUE INDEX uk_ecm_directories_fullpath(fullpath);
@@ -106,21 +105,6 @@ ALTER TABLE llx_product ADD COLUMN onportal tinyint DEFAULT 0 after tobuy;
ALTER TABLE llx_user ADD COLUMN employee tinyint DEFAULT 1;
-create table llx_stock_lotserial
-(
- rowid integer AUTO_INCREMENT PRIMARY KEY,
- datec datetime,
- tms timestamp,
- fk_user_creat integer,
- fk_user_modif integer,
- fk_product integer NOT NULL, -- Id of product
- batch varchar(30) DEFAULT NULL, -- Lot or serial number
- eatby date DEFAULT NULL, -- Eatby date
- sellby date DEFAULT NULL -- Sellby date
-) ENGINE=innodb;
-
-
-
CREATE TABLE IF NOT EXISTS llx_c_hrm_function
(
rowid integer PRIMARY KEY,
@@ -134,12 +118,12 @@ CREATE TABLE IF NOT EXISTS llx_c_hrm_function
INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(1, 5, 'EXECBOARD', 'Executive board', 0, 1);
INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(2, 10, 'MANAGDIR', 'Managing director', 1, 1);
INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(3, 15, 'ACCOUNTMANAG', 'Account manager', 0, 1);
-INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(3, 20, 'ENGAGDIR', 'Engagement director', 1, 1);
-INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(4, 25, 'DIRECTOR', 'Director', 1, 1);
-INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(5, 30, 'PROJMANAG', 'Project manager', 0, 1);
-INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(6, 35, 'DEPHEAD', 'Department head', 0, 1);
-INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(7, 40, 'SECRETAR', 'Secretary', 0, 1);
-INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(8, 45, 'EMPLOYEE', 'Department employee', 0, 1);
+INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(4, 20, 'ENGAGDIR', 'Engagement director', 1, 1);
+INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(5, 25, 'DIRECTOR', 'Director', 1, 1);
+INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(6, 30, 'PROJMANAG', 'Project manager', 0, 1);
+INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(7, 35, 'DEPHEAD', 'Department head', 0, 1);
+INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(8, 40, 'SECRETAR', 'Secretary', 0, 1);
+INSERT INTO llx_c_hrm_function (rowid, pos, code, label, c_level, active) VALUES(9, 45, 'EMPLOYEE', 'Department employee', 0, 1);
CREATE TABLE IF NOT EXISTS llx_c_hrm_department
(
@@ -247,7 +231,7 @@ ALTER TABLE llx_budget_lines ADD UNIQUE INDEX uk_budget_lines (fk_budget, fk_pro
-- MYSQL V4 DELETE llx_budget_lines FROM llx_budget_lines LEFT JOIN llx_budget ON llx_budget.rowid = llx_budget_lines.fk_budget WHERE llx_budget_lines.rowid IS NULL;
-- POSTGRESQL V8 DELETE FROM llx_budget_lines USING llx_budget WHERE llx_budget_lines.fk_budget NOT IN (SELECT llx_budget.rowid FROM llx_budget);
-ALTER TABLE llx_budget_lines ADD INDEX idx_budget_lines (fk_projet);
+ALTER TABLE llx_budget_lines ADD INDEX idx_budget_lines (fk_project);
ALTER TABLE llx_budget_lines ADD CONSTRAINT fk_budget_lines_budget FOREIGN KEY (fk_budget) REFERENCES llx_budget (rowid);
@@ -255,3 +239,15 @@ ALTER TABLE llx_budget_lines ADD CONSTRAINT fk_budget_lines_budget FOREIGN KEY (
ALTER TABLE llx_c_typent ADD COLUMN position integer NOT NULL DEFAULT 0;
ALTER TABLE llx_c_forme_juridique ADD COLUMN position integer NOT NULL DEFAULT 0;
ALTER TABLE llx_c_type_fees ADD COLUMN position integer NOT NULL DEFAULT 0;
+
+-- NEW Level multiprice generator based on per cent variations over base price
+CREATE TABLE llx_product_pricerules
+(
+ rowid INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
+ level INT NOT NULL, -- Which price level is this rule for?
+ fk_level INT NOT NULL, -- Price variations are made over price of X
+ var_percent FLOAT NOT NULL, -- Price variation over based price
+ var_min_percent FLOAT NOT NULL -- Min price discount over general price
+);
+ALTER TABLE llx_product ADD COLUMN price_autogen TINYINT(1) DEFAULT 0;
+ALTER TABLE llx_product_pricerules ADD CONSTRAINT unique_level UNIQUE (level);
\ No newline at end of file
diff --git a/htdocs/install/mysql/tables/llx_product.sql b/htdocs/install/mysql/tables/llx_product.sql
index 42832a6b227..b5df0ade60c 100755
--- a/htdocs/install/mysql/tables/llx_product.sql
+++ b/htdocs/install/mysql/tables/llx_product.sql
@@ -46,7 +46,7 @@ create table llx_product
price_base_type varchar(3) DEFAULT 'HT',
tva_tx double(6,3), -- Default VAT rate of product
recuperableonly integer NOT NULL DEFAULT '0', -- French NPR VAT
- localtax1_tx double(6,3) DEFAULT 0, -- Spanish local VAT 1
+ localtax1_tx double(6,3) DEFAULT 0, -- Spanish local VAT 1
localtax2_tx double(6,3) DEFAULT 0, -- Spanish local VAT 2
fk_user_author integer DEFAULT NULL, -- user making creation
fk_user_modif integer, -- user making last change
@@ -81,5 +81,6 @@ create table llx_product
import_key varchar(14), -- Import key
fk_price_expression integer, -- Link to the rule for dynamic price calculation
desiredstock integer DEFAULT 0,
- fk_unit integer DEFAULT NULL
+ fk_unit integer DEFAULT NULL,
+ price_autogen TINYINT DEFAULT 0
)ENGINE=innodb;
diff --git a/htdocs/install/mysql/tables/llx_product_pricerules.key.sql b/htdocs/install/mysql/tables/llx_product_pricerules.key.sql
new file mode 100644
index 00000000000..42b9289d26a
--- /dev/null
+++ b/htdocs/install/mysql/tables/llx_product_pricerules.key.sql
@@ -0,0 +1,19 @@
+-- ============================================================================
+-- Copyright (C) 2015 Marcos García
+--
+-- 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 .
+--
+-- ===========================================================================
+
+ALTER TABLE llx_product_pricerules ADD CONSTRAINT unique_level UNIQUE (level);
\ No newline at end of file
diff --git a/htdocs/install/mysql/tables/llx_product_pricerules.sql b/htdocs/install/mysql/tables/llx_product_pricerules.sql
new file mode 100644
index 00000000000..3e3dfc02ed1
--- /dev/null
+++ b/htdocs/install/mysql/tables/llx_product_pricerules.sql
@@ -0,0 +1,26 @@
+-- ============================================================================
+-- Copyright (C) 2015 Marcos García
+--
+-- 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 .
+--
+-- ===========================================================================
+
+CREATE TABLE llx_product_pricerules
+(
+ rowid INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
+ level INT NOT NULL, -- Which price level is this rule for?
+ fk_level INT NOT NULL, -- Price variations are made over price of X
+ var_percent FLOAT NOT NULL, -- Price variation over based price
+ var_min_percent FLOAT NOT NULL -- Min price discount over general price
+);
\ No newline at end of file
diff --git a/htdocs/langs/en_US/admin.lang b/htdocs/langs/en_US/admin.lang
index a4b7423ebe8..d5243b02bb2 100755
--- a/htdocs/langs/en_US/admin.lang
+++ b/htdocs/langs/en_US/admin.lang
@@ -1708,3 +1708,4 @@ TitleExampleForMajorRelease=Example of message you can use to announce this majo
TitleExampleForMaintenanceRelease=Example of message you can use to announce this maintenance release (feel free to use it on your web sites)
ExampleOfNewsMessageForMajorRelease=Dolibarr ERP & CRM %s is available. Version %s is a major release with a lot of new features for both users and developers. You can download it from the download area of http://www.dolibarr.org portal (subdirectory Stable versions). You can read ChangeLog for complete list of changes.
ExampleOfNewsMessageForMaintenanceRelease=Dolibarr ERP & CRM %s is available. Version %s is a maintenance version, so it contains only fixes of bugs. We recommend everybody using an older version to upgrade to this one. As any maintenance release, no new features, nor data structure change is present into this version. You can download it from the download area of http://www.dolibarr.org portal (subdirectory Stable versions). You can read ChangeLog for complete list of changes.
+MultiPriceRuleDesc=When option "Several level of prices per product/service" is on, you can define different prices (one per price level) for each product. To save you time, you can enter here rule to have price for each level autocalculated according to price of first level, so you will have to enter only price for first level on each product. This page is here to save you time and can be usefull only if your prices for each leve are relative to first level. You can ignore this page in most cases.
diff --git a/htdocs/langs/en_US/errors.lang b/htdocs/langs/en_US/errors.lang
index 6bf95ebe75b..858e76af447 100755
--- a/htdocs/langs/en_US/errors.lang
+++ b/htdocs/langs/en_US/errors.lang
@@ -173,6 +173,7 @@ ErrorFieldMustBeAnInteger=Field %s must be an integer
ErrorMandatoryParametersNotProvided=Mandatory parameter(s) not provided
ErrorOppStatusRequiredIfAmount=You set an estimated amount for this opportunity/lead. So you must also enter its status
ErrorBadDefinitionOfMenuArrayInModuleDescriptor=Bad Definition Of Menu Array In Module Descriptor (bad value for key fk_menu)
+ErrorSavingChanges=An error has ocurred when saving the changes
# Warnings
WarningPasswordSetWithNoAccount=A password was set for this member. However, no user account was created. So this password is stored but can't be used to login to Dolibarr. It may be used by an external module/interface but if you don't need to define any login nor password for a member, you can disable option "Manage a login for each member" from Member module setup. If you need to manage a login but don't need any password, you can keep this field empty to avoid this warning. Note: Email can also be used as a login if the member is linked to a user.
diff --git a/htdocs/langs/en_US/margins.lang b/htdocs/langs/en_US/margins.lang
index eb77ca9fe4c..9aeaf8d60e6 100644
--- a/htdocs/langs/en_US/margins.lang
+++ b/htdocs/langs/en_US/margins.lang
@@ -24,7 +24,7 @@ StartDate=Start date
EndDate=End date
Launch=Start
ForceBuyingPriceIfNull=Force buying price if null
-ForceBuyingPriceIfNullDetails=if "ON", margin will be zero on line (buying price = selling price), otherwise ("OFF"), marge will be equal to selling price (buying price = 0)
+ForceBuyingPriceIfNullDetails=if "ON", margin will be zero on line (buying price = selling price), otherwise ("OFF"), marge will be equal to suggested default.
MARGIN_METHODE_FOR_DISCOUNT=Margin method for global discounts
UseDiscountAsProduct=As a product
UseDiscountAsService=As a service
diff --git a/htdocs/langs/en_US/paypal.lang b/htdocs/langs/en_US/paypal.lang
index b6b6d59d4a0..b2eadfce654 100644
--- a/htdocs/langs/en_US/paypal.lang
+++ b/htdocs/langs/en_US/paypal.lang
@@ -23,3 +23,9 @@ PAYPAL_PAYONLINE_SENDEMAIL=EMail to warn after a payment (success or not)
ReturnURLAfterPayment=Return URL after payment
ValidationOfPaypalPaymentFailed=Validation of Paypal payment failed
PaypalConfirmPaymentPageWasCalledButFailed=Payment confirmation page for Paypal was called by Paypal but confirmation failed
+SetExpressCheckoutAPICallFailed=SetExpressCheckout API call failed.
+DoExpressCheckoutPaymentAPICallFailed=DoExpressCheckoutPayment API call failed.
+DetailedErrorMessage=Detailed Error Message
+ShortErrorMessage=Short Error Message
+ErrorCode=Error Code
+ErrorSeverityCode=Error Severity Code
diff --git a/htdocs/langs/en_US/products.lang b/htdocs/langs/en_US/products.lang
index 1759eb12e9f..ddda0152245 100644
--- a/htdocs/langs/en_US/products.lang
+++ b/htdocs/langs/en_US/products.lang
@@ -69,6 +69,7 @@ ProductStatusNotOnBuyShort=Not for purchase
UpdatePrice=Update price
UpdateVAT=Update vat
UpdateDefaultPrice=Update default price
+UpdateLevelPrices=Update prices for each level
AppliedPricesFrom=Applied prices from
SellingPrice=Selling price
SellingPriceHT=Selling price (net of tax)
@@ -235,6 +236,10 @@ PriceByQuantityRange=Quantity range
ProductsDashboard=Products/Services summary
UpdateOriginalProductLabel=Modify original label
HelpUpdateOriginalProductLabel=Allows to edit the name of the product
+MultipriceRules=Price level rules
+UseMultipriceRules=Use price level rules (defined into product module setup) to autocalculate prices of all other level according to first level
+PercentVariationOver=%% variation over %s
+PercentDiscountOver=%% discount over %s
### composition fabrication
Building=Production and items dispatchment
Build=Produce
diff --git a/htdocs/paypal/lib/paypal.lib.php b/htdocs/paypal/lib/paypal.lib.php
index f9ec79244a5..824f312a9af 100644
--- a/htdocs/paypal/lib/paypal.lib.php
+++ b/htdocs/paypal/lib/paypal.lib.php
@@ -406,11 +406,11 @@ function print_paypal_redirect($paymentAmount,$currencyCodeType,$paymentType,$re
$ErrorLongMsg = urldecode($resArray["L_LONGMESSAGE0"]);
$ErrorSeverityCode = urldecode($resArray["L_SEVERITYCODE0"]);
- echo "SetExpressCheckout API call failed. \n";
- echo "Detailed Error Message: " . $ErrorLongMsg." \n";
- echo "Short Error Message: " . $ErrorShortMsg." \n";
- echo "Error Code: " . $ErrorCode." \n";
- echo "Error Severity Code: " . $ErrorSeverityCode." \n";
+ echo $langs->trans('SetExpressCheckoutAPICallFailed') . " \n";
+ echo $langs->trans('DetailedErrorMessage') . ": " . $ErrorLongMsg." \n";
+ echo $langs->trans('ShortErrorMessage') . ": " . $ErrorShortMsg." \n";
+ echo $langs->trans('ErrorCode') . ": " . $ErrorCode." \n";
+ echo $langs->trans('ErrorSeverityCode') . ": " . $ErrorSeverityCode." \n";
}
}
diff --git a/htdocs/product/admin/price_rules.php b/htdocs/product/admin/price_rules.php
new file mode 100644
index 00000000000..83d90485b3d
--- /dev/null
+++ b/htdocs/product/admin/price_rules.php
@@ -0,0 +1,205 @@
+.
+ *
+ * Page to set how to autocalculate price for each level when option
+ * PRODUCT_MULTIPRICE is on.
+ */
+
+require '../../main.inc.php';
+require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
+require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
+
+$langs->load("admin");
+$langs->load("products");
+
+// Security check
+if (! $user->admin || (empty($conf->product->enabled) && empty($conf->service->enabled)))
+ accessforbidden();
+
+/**
+ * Actions
+ */
+
+if ($_POST) {
+
+ $var_percent = GETPOST('var_percent', 'array');
+ $var_min_percent = GETPOST('var_min_percent', 'array');
+ $fk_level = GETPOST('fk_level', 'array');
+
+ for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
+
+ $check = isset($var_min_percent[$i]);
+
+ if ($i != 1) {
+ $check = $check && isset($var_percent[$i]) && isset($fk_level[$i]);
+ }
+
+ if (!$check) {
+ continue;
+ }
+
+ if ($i != 1) {
+ $i_var_percent = (float) price2num($var_percent[$i]);
+ } else {
+ $i_var_percent = 0;
+ }
+
+ $i_var_min_percent = (float) price2num($var_min_percent[$i]);
+ $i_fk_level = (int) $fk_level[$i];
+
+ if ($i == 1) {
+ $check1 = true;
+ $check2 = $i_var_min_percent;
+ } else {
+ $check1 = $i_fk_level >= 1 && $i_fk_level <= $conf->global->PRODUIT_MULTIPRICES_LIMIT;
+ $check2 = $i_var_percent && $i_var_min_percent;
+ }
+
+ if (!$check1 || !$check2) {
+
+ //If the level is between range but percent fields are empty, then we ensure it does not exist in DB
+ if ($check1 && !$check2) {
+ $db->query("DELETE FROM ".MAIN_DB_PREFIX."product_pricerules WHERE level = ".(int) $i);
+ }
+
+ continue;
+ }
+
+ $sql = "INSERT INTO ".MAIN_DB_PREFIX."product_pricerules
+ SET level = ".(int) $i.", fk_level = ".$db->escape($i_fk_level).", var_percent = ".$i_var_percent.", var_min_percent = ".$i_var_min_percent;
+
+ if (!$db->query($sql)) {
+
+ //If we could not create, then we try updating
+ $sql = "UPDATE ".MAIN_DB_PREFIX."product_pricerules
+ SET fk_level = ".$db->escape($i_fk_level).", var_percent = ".$i_var_percent.", var_min_percent = ".$i_var_min_percent." WHERE level = ".$i;
+
+ if (!$db->query($sql)) {
+ setEventMessage($langs->trans('ErrorSavingChanges'), 'errors');
+ }
+ }
+
+ }
+
+ setEventMessage($langs->trans("RecordSaved"));
+}
+
+/*
+ * View
+ */
+
+$sql = "SELECT * FROM ".MAIN_DB_PREFIX."product_pricerules";
+$query = $db->query($sql);
+
+$rules = array();
+
+while ($result = $db->fetch_object($query)) {
+ $rules[$result->level] = $result;
+}
+
+$title = $langs->trans('ProductServiceSetup');
+$tab = $langs->trans("ProductsAndServices");
+
+if (empty($conf->produit->enabled)) {
+ $title = $langs->trans('ServiceSetup');
+ $tab = $langs->trans('Services');
+} elseif (empty($conf->service->enabled)) {
+ $title = $langs->trans('ProductSetup');
+ $tab = $langs->trans('Products');
+}
+
+llxHeader('', $langs->trans('MultipriceRules'));
+
+$linkback=''.$langs->trans("BackToModuleList").'';
+print load_fiche_titre($title,$linkback,'title_setup');
+
+
+
+print '';
+
+llxFooter();
+
+$db->close();
diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php
index fb5eba2abc5..dcf8661466c 100644
--- a/htdocs/product/class/product.class.php
+++ b/htdocs/product/class/product.class.php
@@ -191,6 +191,12 @@ class Product extends CommonObject
*/
public $fk_unit;
+ /**
+ * Price is generated using multiprice rules
+ * @var int
+ */
+ public $price_autogen = 0;
+
/**
* Regular product
*/
@@ -710,6 +716,7 @@ class Product extends CommonObject
$sql.= ", accountancy_code_sell= '" . $this->db->escape($this->accountancy_code_sell)."'";
$sql.= ", desiredstock = " . ((isset($this->desiredstock) && $this->desiredstock != '') ? $this->desiredstock : "null");
$sql.= ", fk_unit= " . (!$this->fk_unit ? 'NULL' : $this->fk_unit);
+ $sql.= ", price_autogen = " . (!$this->price_autogen ? 0 : 1);
$sql.= " WHERE rowid = " . $id;
dol_syslog(get_class($this)."::update", LOG_DEBUG);
@@ -1337,9 +1344,10 @@ class Product extends CommonObject
* @param int $level 0=standard, >0 = level if multilevel prices
* @param int $newnpr 0=Standard vat rate, 1=Special vat rate for French NPR VAT
* @param int $newpsq 1 if it has price by quantity
+ * @param int $ignore_autogen Used to avoid infinite loops
* @return int <0 if KO, >0 if OK
*/
- function updatePrice($newprice, $newpricebase, $user, $newvat='',$newminprice='', $level=0, $newnpr=0, $newpsq=0)
+ function updatePrice($newprice, $newpricebase, $user, $newvat='',$newminprice='', $level=0, $newnpr=0, $newpsq=0, $ignore_autogen=0)
{
global $conf,$langs;
@@ -1353,6 +1361,14 @@ class Product extends CommonObject
// Check parameters
if ($newvat == '') $newvat=$this->tva_tx;
+
+ // If multiprices are enabled, then we check if the current product is subject to price autogeneration
+ // Price will be modified ONLY when the first one is the one that is being modified
+ if (!empty($conf->global->PRODUIT_MULTIPRICES) && !$ignore_autogen && $this->price_autogen && ($level == 1))
+ {
+ return $this->generateMultiprices($user, $newprice, $newpricebase, $newvat, $newnpr, $newpsq);
+ }
+
if (! empty($newminprice) && ($newminprice > $newprice))
{
$this->error='ErrorPriceCantBeLowerThanMinPrice';
@@ -1531,7 +1547,7 @@ class Product extends CommonObject
$sql.= " weight, weight_units, length, length_units, surface, surface_units, volume, volume_units, barcode, fk_barcode_type, finished,";
$sql.= " accountancy_code_buy, accountancy_code_sell, stock, pmp,";
$sql.= " datec, tms, import_key, entity, desiredstock, tobatch, fk_unit";
- $sql.= " , fk_price_expression";
+ $sql.= " , fk_price_expression, price_autogen";
$sql.= " FROM ".MAIN_DB_PREFIX."product";
if ($id) $sql.= " WHERE rowid = ".$this->db->escape($id);
else
@@ -1608,6 +1624,7 @@ class Product extends CommonObject
$this->ref_ext = $obj->ref_ext;
$this->fk_price_expression = $obj->fk_price_expression;
$this->fk_unit = $obj->fk_unit;
+ $this->price_autogen = $obj->price_autogen;
$this->db->free($resql);
@@ -3992,4 +4009,65 @@ class Product extends CommonObject
return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
}
+
+ /**
+ * Generates prices for a product based on product multiprice generation rules
+ *
+ * @param User $user User that updates the prices
+ * @param float $baseprice Base price
+ * @param string $price_type Base price type
+ * @param float $price_vat VAT % tax
+ * @param int $npr NPR
+ * @param string $psq ¿?
+ * @return int -1 KO, 1 OK
+ */
+ public function generateMultiprices(User $user, $baseprice, $price_type, $price_vat, $npr, $psq)
+ {
+ global $conf, $db;
+
+ // FIXME USing * into select is forbidden
+ $sql = "SELECT * FROM ".MAIN_DB_PREFIX."product_pricerules";
+ $query = $db->query($sql);
+
+ $rules = array();
+
+ while ($result = $db->fetch_object($query)) {
+ $rules[$result->level] = $result;
+ }
+
+ //Because prices can be based on other level's prices, we temporarily store them
+ $prices = array(
+ 1 => $baseprice
+ );
+
+ for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
+
+ $price = $baseprice;
+ $price_min = 0;
+
+ if ($i > 1) {
+ //We have to make sure it does exist and it is > 0
+ if (isset($rules[$i]->var_percent) && $rules[$i]->var_percent) {
+ $price = $prices[$rules[$i]->fk_level] * (1 + ($rules[$i]->var_percent/100));
+ }
+ }
+
+ $prices[$i] = $price;
+
+ //We have to make sure it does exist and it is > 0
+ if (isset($rules[$i]->var_min_percent) && $rules[$i]->var_min_percent) {
+ $price_min = $price * (1 - ($rules[$i]->var_min_percent/100));
+ }
+
+ if ($price == $this->multiprices[$i] && ($price_min == $this->multiprices_min[$i])) {
+ continue;
+ }
+
+ if ($this->updatePrice($price, $price_type, $user, $price_vat, $price_min, $i, $npr, $psq, true) < 0) {
+ return -1;
+ }
+ }
+
+ return 1;
+ }
}
diff --git a/htdocs/product/fournisseurs.php b/htdocs/product/fournisseurs.php
index 72f8ed2b0b2..526b67b123c 100644
--- a/htdocs/product/fournisseurs.php
+++ b/htdocs/product/fournisseurs.php
@@ -95,9 +95,14 @@ if (empty($reshook))
{
if ($rowid)
{
- $result=$object->remove_product_fournisseur_price($rowid);
$action = '';
- setEventMessage($langs->trans("PriceRemoved"));
+ $result=$product->remove_product_fournisseur_price($rowid);
+ if($result > 0){
+ setEventMessage($langs->trans("PriceRemoved"));
+ }else{
+ $error++;
+ setEventMessages($product->error, $product->errors, 'errors');
+ }
}
}
diff --git a/htdocs/product/price.php b/htdocs/product/price.php
index ac611faf5c3..8822fd9f7f4 100644
--- a/htdocs/product/price.php
+++ b/htdocs/product/price.php
@@ -115,35 +115,35 @@ if (empty($reshook))
if (($action == 'update_price') && !$cancel && ($user->rights->produit->creer || $user->rights->service->creer))
{
+ $newprice = '';
+ $newprice_min = '';
+ $newpricebase = '';
+ $newvat = '';
+
$maxpricesupplier = $object->min_recommended_price();
$object->fk_price_expression = empty($eid) ? 0 : $eid; //0 discards expression
// MultiPrix
- if (! empty($conf->global->PRODUIT_MULTIPRICES))
- {
- $newprice = '';
- $newprice_min = '';
- $newpricebase = '';
- $newvat = '';
+ if (!empty($conf->global->PRODUIT_MULTIPRICES)) {
- for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i ++)
- {
- if (isset($_POST ["price_" . $i]))
- {
+ //Shall we generate prices using price rules?
+ $object->price_autogen = GETPOST('usePriceRules') == 'on' ? true : false;
+ $object->update($object->id, $user);
+
+ for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i ++) {
+ if (isset($_POST ["price_".$i])) {
$level = $i;
- $newprice = price2num($_POST ["price_" . $i], 'MU');
- $newprice_min = price2num($_POST ["price_min_" . $i], 'MU');
- $newpricebase = $_POST ["multiprices_base_type_" . $i];
- $newnpr = (preg_match('/\*/', $_POST ["tva_tx_" . $i]) ? 1 : 0);
- $newvat = str_replace('*', '', $_POST ["tva_tx_" . $i]);
+ $newprice = price2num($_POST ["price_".$i], 'MU');
+ $newprice_min = price2num($_POST ["price_min_".$i], 'MU');
+ $newpricebase = $_POST ["multiprices_base_type_".$i];
+ $newnpr = (preg_match('/\*/', $_POST ["tva_tx_".$i]) ? 1 : 0);
+ $newvat = str_replace('*', '', $_POST ["tva_tx_".$i]);
$newpsq = GETPOST('psqflag');
$newpsq = empty($newpsq) ? 0 : $newpsq;
break; // We found submited price
}
}
- }
- else
- {
+ } else {
$level = 0;
$newprice = price2num($_POST ["price"], 'MU');
$newprice_min = price2num($_POST ["price_min"], 'MU');
@@ -154,42 +154,39 @@ if (empty($reshook))
$newpsq = empty($newpsq) ? 0 : $newpsq;
}
- if (! empty($conf->global->PRODUCT_MINIMUM_RECOMMENDED_PRICE) && $newprice_min < $maxpricesupplier)
- {
- setEventMessage($langs->trans("MinimumPriceLimit",price($maxpricesupplier,0,'',1,-1,-1,'auto')),'errors');
- $error++;
- $action='edit_price';
+ if (!empty($conf->global->PRODUCT_MINIMUM_RECOMMENDED_PRICE) && $newprice_min < $maxpricesupplier) {
+ setEventMessage($langs->trans("MinimumPriceLimit", price($maxpricesupplier, 0, '', 1, - 1, - 1, 'auto')), 'errors');
+ $error ++;
+ $action = 'edit_price';
}
- if ($newprice < $newprice_min && ! empty($object->fk_price_expression))
- {
+ if ($newprice < $newprice_min && !empty($object->fk_price_expression)) {
$newprice = $newprice_min; //Set price same as min, the user will not see the
}
- if ($object->updatePrice($newprice, $newpricebase, $user, $newvat, $newprice_min, $level, $newnpr, $newpsq) > 0)
- {
+ $res = $object->updatePrice($newprice, $newpricebase, $user, $newvat, $newprice_min, $level, $newnpr, $newpsq);
+
+ if ($res) {
+
if ($object->fk_price_expression != 0) {
//Check the expression validity by parsing it
$priceparser = new PriceParser($db);
$price_result = $priceparser->parseProduct($object);
if ($price_result < 0) { //Expression is not valid
- $error++;
- $action='edit_price';
+ $error ++;
+ $action = 'edit_price';
setEventMessage($priceparser->translatedError(), 'errors');
}
}
- if (empty($error) && ! empty($conf->dynamicprices->enabled))
- {
- $ret=$object->setPriceExpression($object->fk_price_expression);
- if ($ret < 0)
- {
- $error++;
- $action='edit_price';
+ if (empty($error) && !empty($conf->dynamicprices->enabled)) {
+ $ret = $object->setPriceExpression($object->fk_price_expression);
+ if ($ret < 0) {
+ $error ++;
+ $action = 'edit_price';
setEventMessage($object->error, 'errors');
}
}
- if (empty($error))
- {
+ if (empty($error)) {
$action = '';
setEventMessage($langs->trans("RecordSaved"));
}
@@ -199,6 +196,7 @@ if (empty($reshook))
}
}
+
if ($action == 'delete' && $user->rights->produit->supprimer)
{
$result = $object->log_price_delete($user, $_GET ["lineid"]);
@@ -718,28 +716,30 @@ if (! $action || $action == 'delete' || $action == 'showlog_customer_price' || $
{
print "\n" . '
' . "\n";
- if (empty($conf->global->PRODUIT_MULTIPRICES))
+ if (empty($conf->global->PRODUIT_MULTIPRICES)) // For everyone, except multiprices
{
if ($user->rights->produit->creer || $user->rights->service->creer) {
print '