diff --git a/README.md b/README.md index 564a4171116..b8a764309d1 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,13 @@ ![Downloads per day](https://img.shields.io/sourceforge/dw/dolibarr.svg) ![Build status](https://img.shields.io/travis/Dolibarr/dolibarr/develop.svg) -Dolibarr ERP & CRM is a modern software package to manage your organization's activity (contacts, suppliers, invoices, orders, stocks, agenda…). +Dolibarr ERP & CRM is a modern software package that helps manage your organization's activity (contacts, suppliers, invoices, orders, stocks, agenda…). -It's an Open Source Software (written in PHP with optional JavaScript enhancements) designed for small, medium or large companies, foundations and freelancers. +It's an Open Source Software suite (written in PHP with optional JavaScript enhancements) designed for small, medium or large companies, foundations and freelancers. -You can freely use, study, modify or distribute it according to its Free Software licence. +You can freely use, study, modify or distribute it according to its licence. -You can use it as a standalone application or as a web application to be able to access it from the Internet or a LAN. +You can use it as a standalone application or as a web application to access it from the Internet or a LAN. ![ScreenShot](https://www.dolibarr.org/images/dolibarr_screenshot1_1920x1080.jpg) @@ -35,11 +35,15 @@ Releases can be downloaded from [official website](https://www.dolibarr.org/). ### Advanced setup -You can use a Web server and a supported database (MariaDB, MySQL or PostgreSQL) to install the standard version. +You can use a web server and a supported database (MariaDB, MySQL or PostgreSQL) to install the standard version. + +On GNU/Linux, first check if your distribution has already packaged Dolibarr. + +#### Generic install steps: - Check that your installed PHP version is supported [see PHP support](https://wiki.dolibarr.org/index.php/Versions). -- Uncompress the downloaded .zip archive to copy the "dolibarr/htdocs" directory and all its files inside your web server root or get the files directly from GitHub (recommanded if you known git): +- Uncompress the downloaded .zip archive to copy the "dolibarr/htdocs" directory and all its files inside your web server root or get the files directly from GitHub (recommanded if you know git as it makes it easier if you want to upgrade later): `git clone https://github.com/dolibarr/dolibarr -b x.y` (where x.y is main version like 3.6, 9.0, ...) @@ -70,11 +74,11 @@ If you don't have time to install it yourself, you can try some commercial 'read ## UPGRADING -- At first make a backup of your Dolibarr files & than see https://wiki.dolibarr.org/index.php/Installation_-_Upgrade#Upgrade_Dolibarr +- At first make a backup of your Dolibarr files & then read https://wiki.dolibarr.org/index.php/Installation_-_Upgrade#Upgrade_Dolibarr - Check that your installed PHP version is supported by the new version [see PHP support](./doc/phpmatrix.md). - Overwrite all old files from 'dolibarr' directory with files provided into the new version's package. -- At first next access, Dolibarr will redirect your to the "install/" page to follow the upgrade process. -  If an `install.lock` file exists to lock any other upgrade process, the application will ask you to remove the file manually (you should find the `install.lock` file into the directory used to store generated and uploaded documents, in most cases, it is the directory called "*documents*"). +- At first next access, Dolibarr will redirect you to the "install/" page to follow the upgrade process. +  If an `install.lock` file exists to lock any other upgrade process, the application will ask you to remove the file manually (you should find the `install.lock` file in the directory used to store generated and uploaded documents, in most cases, it is the directory called "*documents*"). *Note: migration process can be safely done multiple times by calling the `/install/index.php` page* @@ -139,7 +143,7 @@ See the [ChangeLog](https://github.com/Dolibarr/dolibarr/blob/develop/ChangeLog) - Highly customizable: enable only the modules you need, add user personalized fields, choose your skin, several menu managers (can be used by internal users as a back-office with a particular menu, or by external users as a front-office with another one) - APIs -- An easy to understand, maintain and develop code (PHP with no heavy framework; trigger and hook architecture) +- Code that is easy to understand, maintain and develop (PHP with no heavy framework; trigger and hook architecture) - Support a lot of country specific features: - Spanish Tax RE and ISPF - French NPR VAT rate (VAT called "Non Perçue Récupérable" for DOM-TOM) @@ -149,7 +153,7 @@ See the [ChangeLog](https://github.com/Dolibarr/dolibarr/blob/develop/ChangeLog) - Compatible with [European directives](http://europa.eu/legislation_summaries/taxation/l31057_en.htm) (2006/112/CE ... 2010/45/UE) - Compatible with European GDPR rules - ... -- PDF or ODT generation for invoice, proposals, orders... +- Flexible PDF & ODT generation for invoices, proposals, orders... - … ### System Environment / Requirements @@ -167,12 +171,12 @@ These are features that Dolibarr does **not** yet fully support: - Tasks dependencies in projects - Payroll module -- No native embedded Webmail +- No native embedded Webmail, but you can send email to contacts in Dolibarr with e.g. offers, invoices, etc. - Dolibarr can't do coffee (yet) ## DOCUMENTATION -Administrator, user, developer and translator's documentations are available along with other community resources on the [Wiki](https://wiki.dolibarr.org). +Administrator, user, developer and translator's documentations are available along with other community resources in the [Wiki](https://wiki.dolibarr.org). ## CONTRIBUTING @@ -182,7 +186,7 @@ This project exists thanks to all the people who contribute. [[Contribute](https ## CREDITS -Dolibarr is the work of many contributors over the years and uses some fine libraries. +Dolibarr is the work of many contributors over the years and uses some fine PHP libraries. See [COPYRIGHT](https://github.com/Dolibarr/dolibarr/blob/develop/COPYRIGHT) file. diff --git a/build/debian/control b/build/debian/control index 970f192f094..0ef5367072f 100755 --- a/build/debian/control +++ b/build/debian/control @@ -14,7 +14,7 @@ Architecture: all Depends: libapache2-mod-php5 | libapache2-mod-php5filter | php5-cgi | php5-fpm | php5 | libapache2-mod-php | libapache2-mod-phpfilter | php-cgi | php-fpm | php, php5-cli | php-cli, # Required PHP extensions - php5-mysql | php5-mysqli | php-mysql | php-mysqli, php5-curl | php-curl, php5-gd | php-gd, php5-ldap | php-gd, + php5-mysql | php5-mysqli | php-mysql | php-mysqli, php5-curl | php-curl, php5-gd | php-gd, php5-ldap | php-gd, php5-zip | php-zip, # Required PHP libraries php-pear, php-mail-mime, # php-tcpdf, diff --git a/htdocs/accountancy/admin/fiscalyear.php b/htdocs/accountancy/admin/fiscalyear.php index b5cc46a3d0d..89aad6e2d0d 100644 --- a/htdocs/accountancy/admin/fiscalyear.php +++ b/htdocs/accountancy/admin/fiscalyear.php @@ -79,6 +79,7 @@ $object = new Fiscalyear($db); $max = 100; $form = new Form($db); +$fiscalyearstatic = new Fiscalyear($db); $title = $langs->trans('AccountingPeriods'); $helpurl = ""; @@ -132,13 +133,15 @@ if ($result) print ''; if ($num) { - $fiscalyearstatic = new Fiscalyear($db); - while ($i < $num && $i < $max) { $obj = $db->fetch_object($result); + $fiscalyearstatic->id = $obj->rowid; + print ''; - print ''.img_object($langs->trans("ShowFiscalYear"), "technic").' '.$obj->rowid.''; + print ''; + print $fiscalyearstatic->getNomUrl(1); + print ''; print ''.$obj->label.''; print ''.dol_print_date($db->jdate($obj->date_start), 'day').''; print ''.dol_print_date($db->jdate($obj->date_end), 'day').''; diff --git a/htdocs/admin/modules.php b/htdocs/admin/modules.php index bd30d863bdc..3c525200442 100644 --- a/htdocs/admin/modules.php +++ b/htdocs/admin/modules.php @@ -38,7 +38,7 @@ require_once DOL_DOCUMENT_ROOT.'/admin/dolistore/class/dolistore.class.php'; // Load translation files required by the page $langs->loadLangs(array("errors", "admin", "modulebuilder")); -$mode = GETPOSTISSET('mode') ? GETPOST('mode', 'alpha') : 'commonkanban'; +$mode = GETPOSTISSET('mode') ? GETPOST('mode', 'alpha') : (empty($conf->global->MAIN_MODULE_SETUP_ON_LIST_BY_DEFAULT) ? 'commonkanban' : 'common'); if (empty($mode)) $mode = 'common'; $action = GETPOST('action', 'alpha'); //var_dump($_POST);exit; diff --git a/htdocs/bom/bom_card.php b/htdocs/bom/bom_card.php index 202373dd302..6826204804f 100644 --- a/htdocs/bom/bom_card.php +++ b/htdocs/bom/bom_card.php @@ -192,10 +192,10 @@ if (empty($reshook)) $error = 0; // Set if we used free entry or predefined product - $qty = GETPOST('qty', 'int'); + $qty = price2num(GETPOST('qty', 'int')); $qty_frozen = GETPOST('qty_frozen', 'int'); $disable_stock_change = GETPOST('disable_stock_change', 'int'); - $efficiency = GETPOST('efficiency', 'int'); + $efficiency = price2num(GETPOST('efficiency', 'int')); if ($qty == '') { setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('Qty')), null, 'errors'); @@ -219,6 +219,8 @@ if (empty($reshook)) unset($_POST['qty']); unset($_POST['qty_frozen']); unset($_POST['disable_stock_change']); + + $object->fetchLines(); } } } @@ -541,7 +543,7 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea if (!empty($object->table_element_line)) { - print '
+ print ' diff --git a/htdocs/bom/tpl/objectline_create.tpl.php b/htdocs/bom/tpl/objectline_create.tpl.php index 8501f1ca30f..74eb88393df 100644 --- a/htdocs/bom/tpl/objectline_create.tpl.php +++ b/htdocs/bom/tpl/objectline_create.tpl.php @@ -113,7 +113,7 @@ if ($conf->global->PRODUCT_USE_UNITS) { $coldisplay++; print ''; - print $form->selectUnits($line->fk_unit, "units"); + print $form->selectUnits(empty($line->fk_unit) ? $conf->global->PRODUCT_USE_UNITS : $line->fk_unit, "units"); print ''; } diff --git a/htdocs/compta/facture/list.php b/htdocs/compta/facture/list.php index aef7aa1869c..0e4270e04f7 100644 --- a/htdocs/compta/facture/list.php +++ b/htdocs/compta/facture/list.php @@ -5,7 +5,7 @@ * Copyright (C) 2005 Marc Barilley / Ocebo * Copyright (C) 2005-2015 Regis Houssin * Copyright (C) 2006 Andre Cianfarani - * Copyright (C) 2010-2012 Juanjo Menent + * Copyright (C) 2010-2020 Juanjo Menent * Copyright (C) 2012 Christophe Battarel * Copyright (C) 2013 Florian Henry * Copyright (C) 2013 Cédric Salvador @@ -545,6 +545,8 @@ if (!$sall) $sql .= ' f.paye, f.fk_statut, f.close_code,'; $sql .= ' f.datec, f.tms, f.date_closing,'; $sql .= ' f.retained_warranty, f.retained_warranty_date_limit, f.situation_final, f.situation_cycle_ref, f.situation_counter,'; + $sql .= ' f.fk_user_author, f.fk_multicurrency, f.multicurrency_code, f.multicurrency_tx, f.multicurrency_total_ht, f.multicurrency_total_tva,'; + $sql .= ' f.multicurrency_total_tva, f.multicurrency_total_ttc,'; $sql .= ' s.rowid, s.nom, s.email, s.town, s.zip, s.fk_pays, s.client, s.fournisseur, s.code_client, s.code_fournisseur, s.code_compta, s.code_compta_fournisseur,'; $sql .= ' typent.code,'; $sql .= ' state.code_departement, state.nom,'; diff --git a/htdocs/compta/facture/prelevement.php b/htdocs/compta/facture/prelevement.php index 951dc9ed059..88a0b922e55 100644 --- a/htdocs/compta/facture/prelevement.php +++ b/htdocs/compta/facture/prelevement.php @@ -38,8 +38,6 @@ require_once DOL_DOCUMENT_ROOT.'/societe/class/companybankaccount.class.php'; require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.class.php'; require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.facture.class.php'; -if (!$user->rights->facture->lire) accessforbidden(); - // Load translation files required by the page $langs->loadLangs(array('bills', 'banks', 'withdrawals', 'companies')); @@ -51,7 +49,6 @@ $type = GETPOST('type', 'aZ09'); $fieldid = (!empty($ref) ? 'ref' : 'rowid'); if ($user->socid) $socid = $user->socid; -$result = restrictedArea($user, 'facture', $id, '', '', 'fk_soc', $fieldid); if ($type == 'bank-transfer') { $object = new FactureFournisseur($db); @@ -63,6 +60,7 @@ if ($type == 'bank-transfer') { if ($id > 0 || !empty($ref)) { $ret = $object->fetch($id, $ref); + $isdraft = (($object->statut == FactureFournisseur::STATUS_DRAFT) ? 1 : 0); if ($ret > 0) { $object->fetch_thirdparty(); @@ -71,6 +69,13 @@ if ($id > 0 || !empty($ref)) $hookmanager->initHooks(array('directdebitcard', 'globalcard')); +if ($type == 'bank-transfer') { + $result = restrictedArea($user, 'fournisseur', $id, 'facture_fourn', 'facture', 'fk_soc', $fieldid, $isdraft); + if (!$user->rights->fournisseur->facture->lire) accessforbidden(); +} else { + $result = restrictedArea($user, 'facture', $id, '', '', 'fk_soc', $fieldid, $isdraft); + if (!$user->rights->facture->lire) accessforbidden(); +} /* diff --git a/htdocs/compta/index.php b/htdocs/compta/index.php index d5daadcb557..3121aeffb17 100644 --- a/htdocs/compta/index.php +++ b/htdocs/compta/index.php @@ -2,7 +2,7 @@ /* Copyright (C) 2001-2005 Rodolphe Quiedeville * Copyright (C) 2004-2013 Laurent Destailleur * Copyright (C) 2005-2015 Regis Houssin - * Copyright (C) 2015-2016 Juanjo Menent + * Copyright (C) 2015-2020 Juanjo Menent * Copyright (C) 2015 Jean-François Ferry * Copyright (C) 2015 Raphaël Doursenaud * Copyright (C) 2016 Marcos García @@ -161,6 +161,14 @@ if (!empty($conf->facture->enabled) && $user->rights->facture->lire) $reshook = $hookmanager->executeHooks('printFieldListWhereCustomerDraft', $parameters); $sql .= $hookmanager->resPrint; + $sql.= " GROUP BY f.rowid, f.ref, f.datef, f.total, f.tva, f.total_ttc, f.ref_client, f.type, "; + $sql.= "s.email, s.nom, s.rowid, s.code_client, s.code_compta, s.code_fournisseur, s.code_compta_fournisseur"; + + // Add Group from hooks + $parameters = array(); + $reshook = $hookmanager->executeHooks('printFieldListGroupByCustomerDraft', $parameters); + $sql .= $hookmanager->resPrint; + $resql = $db->query($sql); if ($resql) @@ -458,7 +466,7 @@ if ((!empty($conf->fournisseur->enabled) && empty($conf->global->MAIN_USE_NEW_SU $sql .= $hookmanager->resPrint; $sql .= " GROUP BY ff.rowid, ff.ref, ff.fk_statut, ff.libelle, ff.total_ht, ff.tva, ff.total_tva, ff.total_ttc, ff.tms, ff.paye,"; - $sql .= " s.nom, s.rowid, s.code_fournisseur, s.code_compta_fournisseur"; + $sql .= " s.nom, s.rowid, s.code_fournisseur, s.code_compta_fournisseur, s.email"; $sql .= " ORDER BY ff.tms DESC "; $sql .= $db->plimit($max, 0); diff --git a/htdocs/contact/list.php b/htdocs/contact/list.php index 7dad73cdb1c..9eeae17280f 100644 --- a/htdocs/contact/list.php +++ b/htdocs/contact/list.php @@ -206,7 +206,13 @@ if (is_array($extrafields->attributes[$object->table_element]['label']) && count foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $val) { if (!empty($extrafields->attributes[$object->table_element]['list'][$key])) - $arrayfields["ef.".$key] = array('label'=>$extrafields->attributes[$object->table_element]['label'][$key], 'checked'=>(($extrafields->attributes[$object->table_element]['list'][$key] < 0) ? 0 : 1), 'position'=>$extrafields->attributes[$object->table_element]['pos'][$key], 'enabled'=>(abs($extrafields->attributes[$object->table_element]['list'][$key]) != 3 && $extrafields->attributes[$object->table_element]['perms'][$key])); + $arrayfields["ef.".$key] = array( + 'label'=>$extrafields->attributes[$object->table_element]['label'][$key], + 'checked'=>(($extrafields->attributes[$object->table_element]['list'][$key] < 0) ? 0 : 1), + 'position'=>$extrafields->attributes[$object->table_element]['pos'][$key], + 'enabled'=>(abs($extrafields->attributes[$object->table_element]['list'][$key]) != 3 && $extrafields->attributes[$object->table_element]['perms'][$key]), + 'langfile'=>$extrafields->attributes[$object->table_element]['langfile'][$key], + ); } } $object->fields = dol_sort_array($object->fields, 'position'); diff --git a/htdocs/core/class/CMailFile.class.php b/htdocs/core/class/CMailFile.class.php index 48a2b503eca..bca09cb967e 100644 --- a/htdocs/core/class/CMailFile.class.php +++ b/htdocs/core/class/CMailFile.class.php @@ -355,8 +355,8 @@ class CMailFile } $smtps->setSubject($subjecttouse); - $smtps->setTO($this->getValidAddress($this->to, 0, 1)); - $smtps->setFrom($this->getValidAddress($this->from, 0, 1)); + $smtps->setTO($this->getValidAddress($this->addr_to, 0, 1)); + $smtps->setFrom($this->getValidAddress($this->addr_from, 0, 1)); $smtps->setTrackId($this->trackid); $smtps->setReplyTo($this->getValidAddress($this->replyto, 0, 1)); @@ -402,7 +402,7 @@ class CMailFile $smtps->setDeliveryReceipt($this->deliveryreceipt); $host = dol_getprefix('email'); - $this->msgid = time().'.SMTPs-dolibarr-'.$trackid.'@'.$host; + $this->msgid = time().'.SMTPs-dolibarr-'.$this->trackid.'@'.$host; $this->smtps = $smtps; } elseif ($this->sendmode == 'swiftmailer') { @@ -786,14 +786,14 @@ class CMailFile $from = $this->smtps->getFrom('org'); if ($res && !$from) { - $this->error = "Failed to send mail with smtps lib to HOST=".$server.", PORT=".$conf->global->$keyforsmtpport."
Sender address '$from' invalid"; + $this->error = "Failed to send mail with smtps lib to HOST=".$server.", PORT=".$conf->global->$keyforsmtpport." - Sender address '$from' invalid"; dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR); $res = false; } $dest = $this->smtps->getTo(); if ($res && !$dest) { - $this->error = "Failed to send mail with smtps lib to HOST=".$server.", PORT=".$conf->global->$keyforsmtpport."
Recipient address '$dest' invalid"; + $this->error = "Failed to send mail with smtps lib to HOST=".$server.", PORT=".$conf->global->$keyforsmtpport." - Recipient address '$dest' invalid"; dol_syslog("CMailFile::sendfile: mail end error=".$this->error, LOG_ERR); $res = false; } @@ -814,7 +814,7 @@ class CMailFile $res = true; } else { if (empty($this->error)) $this->error = $result; - dol_syslog("CMailFile::sendfile: mail end error with smtps lib to HOST=".$server.", PORT=".$conf->global->$keyforsmtpport."
".$this->error, LOG_ERR); + dol_syslog("CMailFile::sendfile: mail end error with smtps lib to HOST=".$server.", PORT=".$conf->global->$keyforsmtpport." - ".$this->error, LOG_ERR); $res = false; } } diff --git a/htdocs/core/class/fiscalyear.class.php b/htdocs/core/class/fiscalyear.class.php index 0686b68115a..c0216f16a19 100644 --- a/htdocs/core/class/fiscalyear.class.php +++ b/htdocs/core/class/fiscalyear.class.php @@ -35,6 +35,8 @@ class Fiscalyear extends CommonObject */ public $element = 'fiscalyear'; + public $picto = 'technic'; + /** * @var string Name of table without prefix where object is stored */ @@ -97,6 +99,7 @@ class Fiscalyear extends CommonObject public $statuts = array(); public $statuts_short = array(); + /** * Constructor * @@ -267,6 +270,78 @@ class Fiscalyear extends CommonObject } } + /** + * Return clicable link of object (with eventually picto) + * + * @param int $withpicto Add picto into link + * @param int $notooltip 1=Disable tooltip + * @param int $save_lastsearch_value -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking + * @return string String with URL + */ + public function getNomUrl($withpicto = 0, $notooltip = 0, $save_lastsearch_value = -1) + { + global $conf, $langs, $user; + + if (empty($this->ref)) $this->ref = $this->id; + + if (!empty($conf->dol_no_mouse_hover)) $notooltip = 1; // Force disable tooltips + + $result = ''; + + $url = DOL_URL_ROOT.'/accountancy/admin/fiscalyear_card.php?id='.$this->id; + + if (!$user->rights->accounting->fiscalyear->write) + $option = 'nolink'; + + if ($option !== 'nolink') + { + // Add param to save lastsearch_values or not + $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0); + if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) $add_save_lastsearch_values = 1; + if ($add_save_lastsearch_values) $url .= '&save_lastsearch_values=1'; + } + + if ($short) return $url; + + $label = ''; + + if ($user->rights->accounting->fiscalyear->write) { + $label = ''.$langs->trans("FiscalPeriod").''; + $label .= '
'.$langs->trans('Ref').': '.$this->id; + if (isset($this->statut)) { + $label .= '
'.$langs->trans("Status").": ".$this->getLibStatut(5); + } + } + + $linkclose = ''; + if (empty($notooltip) && $user->rights->accounting->fiscalyear->write) + { + if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) + { + $label = $langs->trans("FiscalYear"); + $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"'; + } + $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"'; + $linkclose .= ' class="classfortooltip"'; + } + + $linkstart = ''; + $linkend = ''; + + if ($option === 'nolink') { + $linkstart = ''; + $linkend = ''; + } + + $result .= $linkstart; + if ($withpicto) $result .= img_object(($notooltip ? '' : $label), $this->picto, ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1); + if ($withpicto != 2) $result .= $this->ref; + $result .= $linkend; + + return $result; + } + /** * Give a label from a status * diff --git a/htdocs/core/class/html.form.class.php b/htdocs/core/class/html.form.class.php index 41d696d49aa..6f45ec7a648 100644 --- a/htdocs/core/class/html.form.class.php +++ b/htdocs/core/class/html.form.class.php @@ -5830,7 +5830,7 @@ class Form $urlforajaxcall = DOL_URL_ROOT.'/core/ajax/selectobject.php'; // No immediate load of all database - $urloption = 'htmlname='.$htmlname.'&outjson=1&objectdesc='.$objectdesc.'&filter='.urlencode($objecttmp->filter).($moreparams ? $moreparams : ''); + $urloption = 'htmlname='.$htmlname.'&outjson=1&objectdesc='.$objectdesc.'&filter='.urlencode($objecttmp->filter); // Activate the auto complete using ajax call. $out .= ajax_autocompleter($preselectedvalue, $htmlname, $urlforajaxcall, $urloption, $conf->global->$confkeyforautocompletemode, 0, array()); $out .= ''; @@ -6496,7 +6496,7 @@ class Form */ public static function multiSelectArrayWithCheckbox($htmlname, &$array, $varpage) { - global $conf, $langs, $user; + global $conf, $langs, $user, $extrafields; if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) return ''; @@ -6528,6 +6528,10 @@ class Form } if ($val['label']) { + if (! empty($val['langfile']) && is_object($langs)) { + $langs->load($val['langfile']); + } + $lis .= '
  • '; $listcheckedstring .= (empty($val['checked']) ? '' : $key.','); } diff --git a/htdocs/core/lib/ajax.lib.php b/htdocs/core/lib/ajax.lib.php index c0e6c84ea54..384635e6f25 100644 --- a/htdocs/core/lib/ajax.lib.php +++ b/htdocs/core/lib/ajax.lib.php @@ -25,25 +25,26 @@ /** - * Generic function that return javascript to add to a page to transform a common input field into an autocomplete field by calling an Ajax page (ex: /societe/ajaxcompanies.php). - * The HTML field must be an input text with id=search_$htmlname. - * This use the jQuery "autocomplete" function. If we want to use the select2, we must also convert the input into select on funcntions that call this method. + * Generic function that return javascript to add to a page to transform a common input field into an autocomplete field by calling an Ajax page (ex: /societe/ajaxcompanies.php). + * The HTML field must be an input text with id=search_$htmlname. + * This use the jQuery "autocomplete" function. If we want to use the select2, we must also convert the input into select on funcntions that call this method. * - * @param string $selected Preselected value - * @param string $htmlname HTML name of input field - * @param string $url Ajax Url to call for request: /path/page.php. Must return a json array ('key'=>id, 'value'=>String shown into input field once selected, 'label'=>String shown into combo list) - * @param string $urloption More parameters on URL request - * @param int $minLength Minimum number of chars to trigger that Ajax search - * @param int $autoselect Automatic selection if just one value - * @param array $ajaxoptions Multiple options array + * @param string $selected Preselected value + * @param string $htmlname HTML name of input field + * @param string $url Ajax Url to call for request: /path/page.php. Must return a json array ('key'=>id, 'value'=>String shown into input field once selected, 'label'=>String shown into combo list) + * @param string $urloption More parameters on URL request + * @param int $minLength Minimum number of chars to trigger that Ajax search + * @param int $autoselect Automatic selection if just one value + * @param array $ajaxoptions Multiple options array * - Ex: array('update'=>array('field1','field2'...)) will reset field1 and field2 once select done * - Ex: array('disabled'=> ) * - Ex: array('show'=> ) * - Ex: array('update_textarea'=> ) * - Ex: array('option_disabled'=> id to disable and warning to show if we select a disabled value (this is possible when using autocomplete ajax) - * @return string Script + * @param string $moreparams More params provided to ajax call + * @return string Script */ -function ajax_autocompleter($selected, $htmlname, $url, $urloption = '', $minLength = 2, $autoselect = 0, $ajaxoptions = array()) +function ajax_autocompleter($selected, $htmlname, $url, $urloption = '', $minLength = 2, $autoselect = 0, $ajaxoptions = array(), $moreparams = '') { if (empty($minLength)) $minLength = 1; @@ -55,7 +56,7 @@ function ajax_autocompleter($selected, $htmlname, $url, $urloption = '', $minLen // Input search_htmlname is original field // Input htmlname is a second input field used when using ajax autocomplete. - $script = ''; + $script = ''; $script .= ''."\n"; $script .= ' - + global->MAIN_AUTO_OPEN_SELECT2_ON_FOCUS_FOR_CUSTOMER_PRODUCTS)) { ?> - - + '; } - // Validator + // Approver if (!$edit && $action != 'editvalidator') { print ''; print ''; @@ -1310,7 +1310,7 @@ if ((empty($id) && empty($ref)) || $action == 'add' || $action == 'request' || $ $include_users = $object->fetch_users_approver_holiday(); if (is_array($include_users) && in_array($user->id, $include_users) && $object->statut == Holiday::STATUS_VALIDATED) { - print ''.img_edit($langs->trans("Edit")).''; + print ''.img_edit($langs->trans("Edit")).''; } print ''; print ''; diff --git a/htdocs/install/mysql/migration/12.0.0-13.0.0.sql b/htdocs/install/mysql/migration/12.0.0-13.0.0.sql index 3bb62c53e61..6fbedbafefc 100644 --- a/htdocs/install/mysql/migration/12.0.0-13.0.0.sql +++ b/htdocs/install/mysql/migration/12.0.0-13.0.0.sql @@ -175,6 +175,16 @@ ALTER TABLE llx_recruitment_recruitmentcandidature_extrafields ADD INDEX idx_fk_ +CREATE TABLE llx_product_attribute_combination_price_level +( + rowid INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + fk_product_attribute_combination INTEGER DEFAULT 1 NOT NULL, + fk_price_level INTEGER DEFAULT 1 NOT NULL, + variation_price DOUBLE(24,8) NOT NULL, + variation_price_percentage INTEGER NULL +)ENGINE=innodb; + +ALTER TABLE llx_product_attribute_combination_price_level ADD UNIQUE( fk_product_attribute_combination, fk_price_level); @@ -219,3 +229,10 @@ create table llx_c_recruitment_origin label varchar(64) NOT NULL, active tinyint DEFAULT 1 NOT NULL )ENGINE=innodb; + + + +ALTER TABLE llx_product MODIFY COLUMN seuil_stock_alerte float; +ALTER TABLE llx_product MODIFY COLUMN desiredstock float; +ALTER TABLE llx_product_warehouse_properties MODIFY COLUMN seuil_stock_alerte float; +ALTER TABLE llx_product_warehouse_properties MODIFY COLUMN desiredstock float; diff --git a/htdocs/install/mysql/tables/llx_product.sql b/htdocs/install/mysql/tables/llx_product.sql index b360b4d7f9d..074012e138c 100644 --- a/htdocs/install/mysql/tables/llx_product.sql +++ b/htdocs/install/mysql/tables/llx_product.sql @@ -60,7 +60,7 @@ create table llx_product tobatch tinyint DEFAULT 0 NOT NULL, -- Is it a product that need a batch management (eat-by or lot management) fk_product_type integer DEFAULT 0, -- Type of product: 0 for regular product, 1 for service, 9 for other (used by external module) duration varchar(6), - seuil_stock_alerte integer DEFAULT NULL, + seuil_stock_alerte float DEFAULT NULL, url varchar(255), barcode varchar(180) DEFAULT NULL, -- barcode fk_barcode_type integer DEFAULT NULL, -- barcode type @@ -96,7 +96,7 @@ create table llx_product import_key varchar(14), -- Import key model_pdf varchar(255), -- model save dodument used fk_price_expression integer, -- Link to the rule for dynamic price calculation - desiredstock integer DEFAULT 0, + desiredstock float DEFAULT 0, fk_unit integer DEFAULT NULL, price_autogen tinyint DEFAULT 0, fk_project integer DEFAULT NULL -- Used when product was generated by a project or is specifif to a project diff --git a/htdocs/install/mysql/tables/llx_product_attribute_combination_price_level.sql b/htdocs/install/mysql/tables/llx_product_attribute_combination_price_level.sql new file mode 100644 index 00000000000..ae068b71ed0 --- /dev/null +++ b/htdocs/install/mysql/tables/llx_product_attribute_combination_price_level.sql @@ -0,0 +1,28 @@ +-- ============================================================================ +-- Copyright (C) 2020 John BOTELLA +-- +-- 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_attribute_combination_price_level +( + rowid INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT, + fk_product_attribute_combination INTEGER DEFAULT 1 NOT NULL, + fk_price_level INTEGER DEFAULT 1 NOT NULL, + variation_price DOUBLE(24,8) NOT NULL, + variation_price_percentage INTEGER NULL +)ENGINE=innodb; + +ALTER TABLE llx_product_attribute_combination_price_level ADD UNIQUE( fk_product_attribute_combination, fk_price_level); diff --git a/htdocs/install/mysql/tables/llx_product_stock_entrepot.sql b/htdocs/install/mysql/tables/llx_product_stock_entrepot.sql index b5d69defd9e..903bd59b524 100644 --- a/htdocs/install/mysql/tables/llx_product_stock_entrepot.sql +++ b/htdocs/install/mysql/tables/llx_product_stock_entrepot.sql @@ -24,8 +24,8 @@ create table llx_product_warehouse_properties tms timestamp, fk_product integer NOT NULL, fk_entrepot integer NOT NULL, - seuil_stock_alerte integer DEFAULT '0', - desiredstock integer DEFAULT '0', + seuil_stock_alerte float DEFAULT '0', + desiredstock float DEFAULT '0', import_key varchar(14) -- Import key )ENGINE=innodb; diff --git a/htdocs/langs/en_US/cashdesk.lang b/htdocs/langs/en_US/cashdesk.lang index 73d8bdaa0df..a96f919e4f4 100644 --- a/htdocs/langs/en_US/cashdesk.lang +++ b/htdocs/langs/en_US/cashdesk.lang @@ -77,7 +77,7 @@ POSModule=POS Module BasicPhoneLayout=Use basic layout for phones SetupOfTerminalNotComplete=Setup of terminal %s is not complete DirectPayment=Direct payment -DirectPaymentButton=Direct cash payment button +DirectPaymentButton=Add a "Direct cash payment" button InvoiceIsAlreadyValidated=Invoice is already validated NoLinesToBill=No lines to bill CustomReceipt=Custom Receipt @@ -94,12 +94,12 @@ TakeposConnectorMethodDescription=External module with extra features. Posibilit PrintMethod=Print method ReceiptPrinterMethodDescription=Powerful method with a lot of parameters. Full customizable with templates. Cannot print from the cloud. ByTerminal=By terminal -TakeposNumpadUsePaymentIcon=Use payment icon on numpad +TakeposNumpadUsePaymentIcon=Use icon instead of text on payment buttons of numpad CashDeskRefNumberingModules=Numbering module for POS sales CashDeskGenericMaskCodes6 =
    {TN} tag is used to add the terminal number TakeposGroupSameProduct=Group same products lines StartAParallelSale=Start a new parallel sale -ControlCashOpening=Control cash box at opening pos +ControlCashOpening=Control cash box at opening POS CloseCashFence=Close cash fence CashReport=Cash report MainPrinterToUse=Main printer to use @@ -117,6 +117,6 @@ HideCategoryImages=Hide Category Images HideProductImages=Hide Product Images NumberOfLinesToShow=Number of lines of images to show DefineTablePlan=Define tables plan -GiftReceiptButton=Gift receipt button +GiftReceiptButton=Add a "Gift receipt" button GiftReceipt=Gift receipt ModuleReceiptPrinterMustBeEnabled=Module Receipt printer must have been enabled first diff --git a/htdocs/langs/en_US/main.lang b/htdocs/langs/en_US/main.lang index 61f936ad827..1466b6389a9 100644 --- a/htdocs/langs/en_US/main.lang +++ b/htdocs/langs/en_US/main.lang @@ -355,8 +355,8 @@ PriceUTTC=U.P. (inc. tax) Amount=Amount AmountInvoice=Invoice amount AmountInvoiced=Amount invoiced -AmountInvoicedHT=Amount invoiced (incl. tax) -AmountInvoicedTTC=Amount invoiced (excl. tax) +AmountInvoicedHT=Amount invoiced (excl. tax) +AmountInvoicedTTC=Amount invoiced (inc. tax) AmountPayment=Payment amount AmountHTShort=Amount (excl.) AmountTTCShort=Amount (inc. tax) diff --git a/htdocs/langs/en_US/products.lang b/htdocs/langs/en_US/products.lang index 7f82db63178..b59aa2f7080 100644 --- a/htdocs/langs/en_US/products.lang +++ b/htdocs/langs/en_US/products.lang @@ -104,6 +104,7 @@ SetDefaultBarcodeType=Set barcode type BarcodeValue=Barcode value NoteNotVisibleOnBill=Note (not visible on invoices, proposals...) ServiceLimitedDuration=If product is a service with limited duration: +FillWithLastServiceDates=Fill with last service line dates MultiPricesAbility=Multiple price segments per product/service (each customer is in one price segment) MultiPricesNumPrices=Number of prices DefaultPriceType=Base of prices per default (with versus without tax) when adding new sale prices @@ -361,6 +362,9 @@ SelectCombination=Select combination ProductCombinationGenerator=Variants generator Features=Features PriceImpact=Price impact +ImpactOnPriceLevel=Impact on price level %s +ApplyToAllPriceImpactLevel= Apply to all levels +ApplyToAllPriceImpactLevelHelp=By clicking here you set the same price impact on all levels WeightImpact=Weight impact NewProductAttribute=New attribute NewProductAttributeValue=New attribute value diff --git a/htdocs/langs/en_US/projects.lang b/htdocs/langs/en_US/projects.lang index a068fe247fe..670fff06f03 100644 --- a/htdocs/langs/en_US/projects.lang +++ b/htdocs/langs/en_US/projects.lang @@ -211,9 +211,9 @@ ProjectNbProjectByMonth=No. of created projects by month ProjectNbTaskByMonth=No. of created tasks by month ProjectOppAmountOfProjectsByMonth=Amount of leads by month ProjectWeightedOppAmountOfProjectsByMonth=Weighted amount of leads by month -ProjectOpenedProjectByOppStatus=Open project/lead by lead status -ProjectsStatistics=Statistics on projects/leads -TasksStatistics=Statistics on project/lead tasks +ProjectOpenedProjectByOppStatus=Open project|lead by lead status +ProjectsStatistics=Statistics on projects or leads +TasksStatistics=Statistics on tasks of projects or leads TaskAssignedToEnterTime=Task assigned. Entering time on this task should be possible. IdTaskTime=Id task time YouCanCompleteRef=If you want to complete the ref with some suffix, it is recommanded to add a - character to separate it, so the automatic numbering will still work correctly for next projects. For example %s-MYSUFFIX diff --git a/htdocs/langs/fr_FR/main.lang b/htdocs/langs/fr_FR/main.lang index bea3097d2ff..1d5dd9bc6cb 100644 --- a/htdocs/langs/fr_FR/main.lang +++ b/htdocs/langs/fr_FR/main.lang @@ -355,8 +355,8 @@ PriceUTTC=P.U TTC Amount=Montant AmountInvoice=Montant facture AmountInvoiced=Montant facturé -AmountInvoicedHT=Montant facturé (TTC) -AmountInvoicedTTC=Montant facturé (HT) +AmountInvoicedHT=Montant facturé (HT) +AmountInvoicedTTC=Montant facturé (TTC) AmountPayment=Montant paiement AmountHTShort=Montant HT AmountTTCShort=Montant TTC diff --git a/htdocs/modulebuilder/template/myobject_list.php b/htdocs/modulebuilder/template/myobject_list.php index 25dc30b085a..2dfa672acbf 100644 --- a/htdocs/modulebuilder/template/myobject_list.php +++ b/htdocs/modulebuilder/template/myobject_list.php @@ -147,7 +147,8 @@ if (is_array($extrafields->attributes[$object->table_element]['label']) && count 'label'=>$extrafields->attributes[$object->table_element]['label'][$key], 'checked'=>(($extrafields->attributes[$object->table_element]['list'][$key] < 0) ? 0 : 1), 'position'=>$extrafields->attributes[$object->table_element]['pos'][$key], - 'enabled'=>(abs($extrafields->attributes[$object->table_element]['list'][$key]) != 3 && $extrafields->attributes[$object->table_element]['perms'][$key]) + 'enabled'=>(abs($extrafields->attributes[$object->table_element]['list'][$key]) != 3 && $extrafields->attributes[$object->table_element]['perms'][$key]), + 'langfile'=>$extrafields->attributes[$object->table_element]['langfile'][$key] ); } } diff --git a/htdocs/product/card.php b/htdocs/product/card.php index 0327456615c..32c50ccb661 100644 --- a/htdocs/product/card.php +++ b/htdocs/product/card.php @@ -1107,7 +1107,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) { print ''.$langs->trans('DefaultUnitToShow').''; print ''; - print $form->selectUnits('', 'units'); + print $form->selectUnits(empty($line->fk_unit) ? $conf->global->PRODUCT_USE_UNITS : $line->fk_unit, 'units'); print ''; } diff --git a/htdocs/product/class/api_products.class.php b/htdocs/product/class/api_products.class.php index 89c9a78ddaa..f1b3e91617b 100644 --- a/htdocs/product/class/api_products.class.php +++ b/htdocs/product/class/api_products.class.php @@ -287,8 +287,8 @@ class Products extends DolibarrApi foreach ($request_data as $field => $value) { if ($field == 'id') { continue; } - if ($field == 'stock_reel') { - throw new RestException(400, 'Stock reel cannot be updated here. Use the /stockmovements endpoint instead'); + if ($field == 'stock_reel') { + throw new RestException(400, 'Stock reel cannot be updated here. Use the /stockmovements endpoint instead'); } $this->product->$field = $value; } @@ -578,12 +578,12 @@ class Products extends DolibarrApi } if ($result > 0) { - require_once DOL_DOCUMENT_ROOT.'/product/class/productcustomerprice.class.php'; - $prodcustprice = new Productcustomerprice($this->db); - $filter = array(); - $filter['t.fk_product'] .= $id; - if ($thirdparty_id) $filter['t.fk_soc'] .= $thirdparty_id; - $result = $prodcustprice->fetch_all('', '', 0, 0, $filter); + require_once DOL_DOCUMENT_ROOT.'/product/class/productcustomerprice.class.php'; + $prodcustprice = new Productcustomerprice($this->db); + $filter = array(); + $filter['t.fk_product'] .= $id; + if ($thirdparty_id) $filter['t.fk_soc'] .= $thirdparty_id; + $result = $prodcustprice->fetch_all('', '', 0, 0, $filter); } if (empty($prodcustprice->lines)) { @@ -624,8 +624,8 @@ class Products extends DolibarrApi } return array( - 'prices_by_qty'=>$this->product->prices_by_qty[0], // 1 if price by quantity was activated for the product - 'prices_by_qty_list'=>$this->product->prices_by_qty_list[0] + 'prices_by_qty'=>$this->product->prices_by_qty[0], // 1 if price by quantity was activated for the product + 'prices_by_qty_list'=>$this->product->prices_by_qty_list[0] ); } @@ -641,11 +641,11 @@ class Products extends DolibarrApi * @param string $ref_fourn Supplier ref * @param float $tva_tx New VAT Rate (For example 8.5. Should not be a string) * @param string $charges costs affering to product - * @param float $remise_percent Discount regarding qty (percent) - * @param float $remise Discount regarding qty (amount) - * @param int $newnpr Set NPR or not - * @param int $delivery_time_days Delay in days for delivery (max). May be '' if not defined. - * @param string $supplier_reputation Reputation with this product to the defined supplier (empty, FAVORITE, DONOTORDER) + * @param float $remise_percent Discount regarding qty (percent) + * @param float $remise Discount regarding qty (amount) + * @param int $newnpr Set NPR or not + * @param int $delivery_time_days Delay in days for delivery (max). May be '' if not defined. + * @param string $supplier_reputation Reputation with this product to the defined supplier (empty, FAVORITE, DONOTORDER) * @param array $localtaxes_array Array with localtaxes info array('0'=>type1,'1'=>rate1,'2'=>type2,'3'=>rate2) (loaded by getLocalTaxesFromRate(vatrate, 0, ...) function). * @param string $newdefaultvatcode Default vat code * @param float $multicurrency_buyprice Purchase price for the quantity min in currency @@ -749,77 +749,77 @@ class Products extends DolibarrApi */ public function getSupplierProducts($sortfield = "t.ref", $sortorder = 'ASC', $limit = 100, $page = 0, $mode = 0, $category = 0, $supplier = 0, $sqlfilters = '') { - global $db, $conf; - $obj_ret = array(); - $socid = DolibarrApiAccess::$user->socid ? DolibarrApiAccess::$user->socid : ''; - $sql = "SELECT t.rowid, t.ref, t.ref_ext"; - $sql .= " FROM ".MAIN_DB_PREFIX."product as t"; - if ($category > 0) { - $sql .= ", ".MAIN_DB_PREFIX."categorie_product as c"; - } - $sql .= ", ".MAIN_DB_PREFIX."product_fournisseur_price as s"; + global $db, $conf; + $obj_ret = array(); + $socid = DolibarrApiAccess::$user->socid ? DolibarrApiAccess::$user->socid : ''; + $sql = "SELECT t.rowid, t.ref, t.ref_ext"; + $sql .= " FROM ".MAIN_DB_PREFIX."product as t"; + if ($category > 0) { + $sql .= ", ".MAIN_DB_PREFIX."categorie_product as c"; + } + $sql .= ", ".MAIN_DB_PREFIX."product_fournisseur_price as s"; - $sql .= ' WHERE t.entity IN ('.getEntity('product').')'; + $sql .= ' WHERE t.entity IN ('.getEntity('product').')'; - if ($supplier > 0) { - $sql .= " AND s.fk_soc = ".$db->escape($supplier); - } - $sql .= " AND s.fk_product = t.rowid"; - // Select products of given category - if ($category > 0) { - $sql .= " AND c.fk_categorie = ".$db->escape($category); - $sql .= " AND c.fk_product = t.rowid"; - } - if ($mode == 1) { - // Show only products - $sql .= " AND t.fk_product_type = 0"; - } elseif ($mode == 2) { - // Show only services - $sql .= " AND t.fk_product_type = 1"; - } - // Add sql filters - if ($sqlfilters) { - if (!DolibarrApi::_checkFilters($sqlfilters)) { - throw new RestException(503, 'Error when validating parameter sqlfilters '.$sqlfilters); - } - $regexstring = '\(([^:\'\(\)]+:[^:\'\(\)]+:[^:\(\)]+)\)'; - $sql .= " AND (".preg_replace_callback('/'.$regexstring.'/', 'DolibarrApi::_forge_criteria_callback', $sqlfilters).")"; - } - $sql .= $db->order($sortfield, $sortorder); - if ($limit) { - if ($page < 0) { - $page = 0; - } - $offset = $limit * $page; - $sql .= $db->plimit($limit + 1, $offset); - } - $result = $db->query($sql); - if ($result) { - $num = $db->num_rows($result); - $min = min($num, ($limit <= 0 ? $num : $limit)); - $i = 0; - while ($i < $min) - { - $obj = $db->fetch_object($result); + if ($supplier > 0) { + $sql .= " AND s.fk_soc = ".$db->escape($supplier); + } + $sql .= " AND s.fk_product = t.rowid"; + // Select products of given category + if ($category > 0) { + $sql .= " AND c.fk_categorie = ".$db->escape($category); + $sql .= " AND c.fk_product = t.rowid"; + } + if ($mode == 1) { + // Show only products + $sql .= " AND t.fk_product_type = 0"; + } elseif ($mode == 2) { + // Show only services + $sql .= " AND t.fk_product_type = 1"; + } + // Add sql filters + if ($sqlfilters) { + if (!DolibarrApi::_checkFilters($sqlfilters)) { + throw new RestException(503, 'Error when validating parameter sqlfilters '.$sqlfilters); + } + $regexstring = '\(([^:\'\(\)]+:[^:\'\(\)]+:[^:\(\)]+)\)'; + $sql .= " AND (".preg_replace_callback('/'.$regexstring.'/', 'DolibarrApi::_forge_criteria_callback', $sqlfilters).")"; + } + $sql .= $db->order($sortfield, $sortorder); + if ($limit) { + if ($page < 0) { + $page = 0; + } + $offset = $limit * $page; + $sql .= $db->plimit($limit + 1, $offset); + } + $result = $db->query($sql); + if ($result) { + $num = $db->num_rows($result); + $min = min($num, ($limit <= 0 ? $num : $limit)); + $i = 0; + while ($i < $min) + { + $obj = $db->fetch_object($result); - $product_fourn = new ProductFournisseur($this->db); - $product_fourn_list = $product_fourn->list_product_fournisseur_price($obj->rowid, '', '', 0, 0); - foreach ($product_fourn_list as $tmpobj) { - $this->_cleanObjectDatas($tmpobj); - } + $product_fourn = new ProductFournisseur($this->db); + $product_fourn_list = $product_fourn->list_product_fournisseur_price($obj->rowid, '', '', 0, 0); + foreach ($product_fourn_list as $tmpobj) { + $this->_cleanObjectDatas($tmpobj); + } - //var_dump($product_fourn_list->db);exit; - $obj_ret[$obj->rowid] = $product_fourn_list; + //var_dump($product_fourn_list->db);exit; + $obj_ret[$obj->rowid] = $product_fourn_list; - $i++; - } - } else { - throw new RestException(503, 'Error when retrieve product list : '.$db->lasterror()); - } - if (!count($obj_ret)) { - throw new RestException(404, 'No product found'); - } - return $obj_ret; + $i++; + } + } else { + throw new RestException(503, 'Error when retrieve product list : '.$db->lasterror()); + } + if (!count($obj_ret)) { + throw new RestException(404, 'No product found'); + } + return $obj_ret; } /** @@ -1057,7 +1057,7 @@ class Products extends DolibarrApi $result = $prodattr->delete(DolibarrApiAccess::$user); if ($result <= 0) { - throw new RestException(500, "Error deleting attribute"); + throw new RestException(500, "Error deleting attribute"); } return $result; @@ -1161,9 +1161,23 @@ class Products extends DolibarrApi throw new RestException(401); } - $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_attribute_value WHERE ref LIKE '".trim($ref)."' AND fk_product_attribute = ".(int) $id; + $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."product_attribute_value WHERE ref LIKE '".trim($ref)."' AND fk_product_attribute = ".(int) $id." AND entity IN (".getEntity('product').")"; + $query = $this->db->query($sql); - if ($this->db->query($sql)) { + if (!$query) { + throw new RestException(401); + } + + if (!$this->db->num_rows($query)) { + throw new RestException(404, 'Attribute value not found'); + } + + $result = $this->db->fetch_object($query); + + $attrval = new ProductAttributeValue($this->db); + $attrval->id = $result->rowid; + $result = $attrval->delete(DolibarrApiAccess::$user); + if ($result > 0) { return 1; } @@ -1328,7 +1342,7 @@ class Products extends DolibarrApi $objectval = new ProductAttributeValue($this->db); $objectval->id = (int) $id; - if ($objectval->delete() > 0) { + if ($objectval->delete(DolibarrApiAccess::$user) > 0) { return 1; } throw new RestException(500, "Error deleting attribute value"); @@ -1448,9 +1462,9 @@ class Products extends DolibarrApi $result = $prodcomb->createProductCombination(DolibarrApiAccess::$user, $this->product, $features, array(), $price_impact_is_percent, $price_impact, $weight_impact, $reference); if ($result > 0) { - return $result; + return $result; } else { - throw new RestException(500, "Error creating new product variant"); + throw new RestException(500, "Error creating new product variant"); } } @@ -1573,7 +1587,7 @@ class Products extends DolibarrApi $result = $prodcomb->delete(DolibarrApiAccess::$user); if ($result <= 0) { - throw new RestException(500, "Error deleting variant"); + throw new RestException(500, "Error deleting variant"); } return $result; } @@ -1665,17 +1679,17 @@ class Products extends DolibarrApi } if ($includestockdata) { - $this->product->load_stock(); + $this->product->load_stock(); - if (is_array($this->product->stock_warehouse)) { - foreach ($this->product->stock_warehouse as $keytmp => $valtmp) { - if (is_array($this->product->stock_warehouse[$keytmp]->detail_batch)) { - foreach ($this->product->stock_warehouse[$keytmp]->detail_batch as $keytmp2 => $valtmp2) { - unset($this->product->stock_warehouse[$keytmp]->detail_batch[$keytmp2]->db); - } - } - } - } + if (is_array($this->product->stock_warehouse)) { + foreach ($this->product->stock_warehouse as $keytmp => $valtmp) { + if (is_array($this->product->stock_warehouse[$keytmp]->detail_batch)) { + foreach ($this->product->stock_warehouse[$keytmp]->detail_batch as $keytmp2 => $valtmp2) { + unset($this->product->stock_warehouse[$keytmp]->detail_batch[$keytmp2]->db); + } + } + } + } } if ($includesubproducts) { diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php index d5dbc0204c0..1fcb76a571d 100644 --- a/htdocs/product/class/product.class.php +++ b/htdocs/product/class/product.class.php @@ -205,7 +205,7 @@ class Product extends CommonObject /** * Stock alert * - * @var int + * @var float */ public $seuil_stock_alerte = 0; @@ -995,7 +995,7 @@ class Product extends CommonObject $sql .= ", volume = ".($this->volume != '' ? "'".$this->db->escape($this->volume)."'" : 'null'); $sql .= ", volume_units = ".($this->volume_units != '' ? "'".$this->db->escape($this->volume_units)."'" : 'null'); $sql .= ", fk_default_warehouse = ".($this->fk_default_warehouse > 0 ? $this->db->escape($this->fk_default_warehouse) : 'null'); - $sql .= ", seuil_stock_alerte = ".((isset($this->seuil_stock_alerte) && is_numeric($this->seuil_stock_alerte)) ? (int) $this->seuil_stock_alerte : 'null'); + $sql .= ", seuil_stock_alerte = ".((isset($this->seuil_stock_alerte) && is_numeric($this->seuil_stock_alerte)) ? (float) $this->seuil_stock_alerte : 'null'); $sql .= ", description = '".$this->db->escape($this->description)."'"; $sql .= ", url = ".($this->url ? "'".$this->db->escape($this->url)."'" : 'null'); $sql .= ", customcode = '".$this->db->escape($this->customcode)."'"; @@ -1008,7 +1008,7 @@ class Product extends CommonObject $sql .= ", accountancy_code_sell= '".$this->db->escape($this->accountancy_code_sell)."'"; $sql .= ", accountancy_code_sell_intra= '".$this->db->escape($this->accountancy_code_sell_intra)."'"; $sql .= ", accountancy_code_sell_export= '".$this->db->escape($this->accountancy_code_sell_export)."'"; - $sql .= ", desiredstock = ".((isset($this->desiredstock) && is_numeric($this->desiredstock)) ? (int) $this->desiredstock : "null"); + $sql .= ", desiredstock = ".((isset($this->desiredstock) && is_numeric($this->desiredstock)) ? (float) $this->desiredstock : "null"); $sql .= ", cost_price = ".($this->cost_price != '' ? $this->db->escape($this->cost_price) : 'null'); $sql .= ", fk_unit= ".(!$this->fk_unit ? 'NULL' : (int) $this->fk_unit); $sql .= ", price_autogen = ".(!$this->price_autogen ? 0 : 1); @@ -1077,7 +1077,7 @@ class Product extends CommonObject $comb = new ProductCombination($this->db); foreach ($comb->fetchAllByFkProductParent($this->id) as $currcomb) { - $currcomb->updateProperties($this, $user); + $currcomb->updateProperties($this, $user); } } diff --git a/htdocs/product/composition/card.php b/htdocs/product/composition/card.php index aa3b49dd2c6..1c1dfa0913e 100644 --- a/htdocs/product/composition/card.php +++ b/htdocs/product/composition/card.php @@ -66,7 +66,7 @@ if ($id > 0 || !empty($ref)) if ($cancel) $action = ''; -// Action association d'un sousproduit +// Add subproduct to product if ($action == 'add_prod' && ($user->rights->produit->creer || $user->rights->service->creer)) { $error = 0; diff --git a/htdocs/product/stock/list.php b/htdocs/product/stock/list.php index 3ffee80362c..86178adeee2 100644 --- a/htdocs/product/stock/list.php +++ b/htdocs/product/stock/list.php @@ -101,7 +101,9 @@ $search_all = trim(GETPOST("search_all", 'alpha')); $search = array(); foreach ($object->fields as $key => $val) { - if (GETPOST('search_'.$key, 'alpha') !== '') $search[$key] = GETPOST('search_'.$key, 'alpha'); + $search_key = $key; + if ($search_key == 'statut') $search_key = 'status'; // remove this after refactor entrepot.class property statut to status + if (GETPOST('search_'.$search_key, 'alpha') !== '') $search[$search_key] = GETPOST('search_'.$search_key, 'alpha'); } // Definition of fields for list @@ -225,13 +227,15 @@ if (!empty($conf->categorie->enabled)) } foreach ($search as $key => $val) { + $class_key = $key; + if ($class_key == 'status') $class_key = 'statut'; // remove this after refactor entrepot.class property statut to status if (($key == 'status' && $search[$key] == -1) || $key=='entity') continue; $mode_search = (($object->isInt($object->fields[$key]) || $object->isFloat($object->fields[$key])) ? 1 : 0); if (strpos($object->fields[$key]['type'], 'integer:') === 0) { if ($search[$key] == '-1') $search[$key] = ''; $mode_search = 2; } - if ($search[$key] != '') $sql .= natural_search((($key == 'ref') ? 't.ref' : 't.' . $key), $search[$key], (($key == 'status') ? 2 : $mode_search)); + if ($search[$key] != '') $sql .= natural_search((($key == 'ref') ? 't.ref' : 't.' . $class_key), $search[$key], (($key == 'status') ? 2 : $mode_search)); } if ($search_all) $sql .= natural_search(array_keys($fieldstosearchall), $search_all); // Add where from extra fields diff --git a/htdocs/projet/info.php b/htdocs/projet/info.php index 6c73d2cfc8b..b3cebaad913 100644 --- a/htdocs/projet/info.php +++ b/htdocs/projet/info.php @@ -61,7 +61,7 @@ $search_agenda_label = GETPOST('search_agenda_label'); $id = GETPOST("id", 'int'); $socid = 0; //if ($user->socid > 0) $socid = $user->socid; // For external user, no check is done on company because readability is managed by public status of project and assignement. -$result = restrictedArea($user, 'projet', $id, ''); +$result = restrictedArea($user, 'projet', $id, 'projet&project'); if (!$user->rights->projet->lire) accessforbidden(); diff --git a/htdocs/public/demo/index.php b/htdocs/public/demo/index.php index 3773a530691..e69f08d10e7 100644 --- a/htdocs/public/demo/index.php +++ b/htdocs/public/demo/index.php @@ -417,16 +417,14 @@ if (!empty($conf->google->enabled) && !empty($conf->global->MAIN_GOOGLE_AD_CLIEN if (empty($conf->dol_use_jmobile)) { print '
    '."\n"; - print ''."\n"; - print ''."\n"; + print ''."\n"; print '
    '."\n"; } else { print ''."\n"; diff --git a/htdocs/takepos/admin/bar.php b/htdocs/takepos/admin/bar.php index b6a06dee9fb..2ec623024c7 100644 --- a/htdocs/takepos/admin/bar.php +++ b/htdocs/takepos/admin/bar.php @@ -34,7 +34,8 @@ if (!$user->admin) accessforbidden(); $langs->loadLangs(array("admin", "cashdesk", "printing")); -global $db; +$res = 0; + /* * Actions @@ -44,9 +45,15 @@ if (GETPOST('action', 'alpha') == 'set') { $db->begin(); - dol_syslog("admin/cashdesk: level ".GETPOST('level', 'alpha')); + dol_syslog("admin/bar"); - if (!$res > 0) $error++; + $suplement_category = GETPOST('TAKEPOS_SUPPLEMENTS_CATEGORY', 'alpha'); + if ($suplement_category < 0) $suplement_category= 0; + + $res = dolibarr_set_const($db, "TAKEPOS_SUPPLEMENTS_CATEGORY", $suplement_category, 'chaine', 0, '', $conf->entity); + if ($res <= 0) { + $error++; + } if (!$error) { @@ -74,7 +81,6 @@ $linkback = ''.$langs->trans("BackT print load_fiche_titre($langs->trans("CashDeskSetup").' (TakePOS)', $linkback, 'title_setup'); $head = takepos_prepare_head(); dol_fiche_head($head, 'bar', 'TakePOS', -1, 'cash-register'); -print '
    '; // Mode @@ -93,129 +99,138 @@ function Floors() { '.$langs->trans("DefineTablePlan").'

    '; -print '

    '; - -print '
    '; -print ''; -print ''; -print ''; -print "\n"; - -if ($conf->global->TAKEPOS_BAR_RESTAURANT && $conf->global->TAKEPOS_PRINT_METHOD != "browser") { - print ''; - print ''; - - print ''; - print ''; -} - -print ''; -print ''; - -print ''; -print ''; - -if ($conf->global->TAKEPOS_SUPPLEMENTS) -{ - print ''; - print '\n"; -} - -print ''; -print ''; - -print ''; -print ''; - -print '
    '.$langs->trans("Parameters").''.$langs->trans("Value").'
    '; - print $langs->trans("OrderPrinters").' ('.$langs->trans("Setup").')'; - print ''; - print ajax_constantonoff("TAKEPOS_ORDER_PRINTERS", array(), $conf->entity, 0, 0, 1, 0); - //print $form->selectyesno("TAKEPOS_ORDER_PRINTERS", $conf->global->TAKEPOS_ORDER_PRINTERS, 1); - print '
    '; - print $langs->trans("OrderNotes"); - print ''; - print ajax_constantonoff("TAKEPOS_ORDER_NOTES", array(), $conf->entity, 0, 0, 1, 0); - //print $form->selectyesno("TAKEPOS_ORDER_NOTES", $conf->global->TAKEPOS_ORDER_NOTES, 1); - print '
    '; -print $langs->trans("BasicPhoneLayout"); -print ''; -//print $form->selectyesno("TAKEPOS_PHONE_BASIC_LAYOUT", $conf->global->TAKEPOS_PHONE_BASIC_LAYOUT, 1); -print ajax_constantonoff("TAKEPOS_PHONE_BASIC_LAYOUT", array(), $conf->entity, 0, 0, 1, 0); -print '
    '; -print $langs->trans("ProductSupplements"); -print ''; -//print $form->selectyesno("TAKEPOS_SUPPLEMENTS", $conf->global->TAKEPOS_SUPPLEMENTS, 1); -print ajax_constantonoff("TAKEPOS_SUPPLEMENTS", array(), $conf->entity, 0, 0, 1, 0); -print '
    '; - print $langs->trans("SupplementCategory"); - print ''; - print $form->select_all_categories(Categorie::TYPE_PRODUCT, $conf->global->TAKEPOS_SUPPLEMENTS_CATEGORY, 'TAKEPOS_SUPPLEMENTS_CATEGORY', 64, 0, 0); - print ajax_combobox('TAKEPOS_SUPPLEMENTS_CATEGORY'); - print "
    '; -print 'QR - '.$langs->trans("CustomerMenu"); -print ''; -print ajax_constantonoff("TAKEPOS_QR_MENU", array(), $conf->entity, 0, 0, 1, 0); -print '
    '; -print 'QR - '.$langs->trans("AutoOrder"); -print ''; -print ajax_constantonoff("TAKEPOS_AUTO_ORDER", array(), $conf->entity, 0, 0, 1, 0); -print '
    '; - - -if ($conf->global->TAKEPOS_QR_MENU) -{ - $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root)); - $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file - print '
    '; - print ''; - print ''; - print ''; - print "\n"; - print ''; - print ''; - print '
    '.$langs->trans("URL").''.$langs->trans("QR").'
    '; - print "".$urlwithroot."/takepos/public/menu.php"; - print ''; - print ""; - print '
    '; -} - -if ($conf->global->TAKEPOS_AUTO_ORDER) -{ - print '
    '; - print ''; - print ''; - print ''; - print "\n"; - - //global $dolibarr_main_url_root; - $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root)); - $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file - $sql = "SELECT rowid, entity, label, leftpos, toppos, floor FROM ".MAIN_DB_PREFIX."takepos_floor_tables"; - $resql = $db->query($sql); - $rows = array(); - while ($row = $db->fetch_array($resql)) { - print ''; - print ''; - print ''; - } - - print '
    '.$langs->trans("Table").''.$langs->trans("URL").''.$langs->trans("QR").'
    '; - print $langs->trans("Table")." ".$row['label']; - print ''; - print "".$urlwithroot."/takepos/public/auto_order.php?key=".dol_encode($row['rowid']).""; - print ''; - print ""; - print '
    '; -} - -print '
    '; +print $langs->trans("EnableBarOrRestaurantFeatures"); +print ajax_constantonoff("TAKEPOS_BAR_RESTAURANT", array(), $conf->entity, 0, 0, 1, 0); print '
    '; -print '
    '; +if ($conf->global->TAKEPOS_BAR_RESTAURANT) { + print '
    '; + print ' '.$langs->trans("DefineTablePlan").'
    '; + print '

    '; + + print '
    '; + print ''; + print ''; + print ''; + print "\n"; + + if ($conf->global->TAKEPOS_PRINT_METHOD != "browser") { + print ''; + print ''; + + print ''; + print ''; + } + + print ''; + print ''; + + print ''; + print ''; + + if ($conf->global->TAKEPOS_SUPPLEMENTS) + { + print ''; + print '\n"; + } + + print ''; + print ''; + + print ''; + print ''; + + print '
    '.$langs->trans("Parameters").''.$langs->trans("Value").'
    '; + print $langs->trans("OrderPrinters").' ('.$langs->trans("Setup").')'; + print ''; + print ajax_constantonoff("TAKEPOS_ORDER_PRINTERS", array(), $conf->entity, 0, 0, 1, 0); + //print $form->selectyesno("TAKEPOS_ORDER_PRINTERS", $conf->global->TAKEPOS_ORDER_PRINTERS, 1); + print '
    '; + print $langs->trans("OrderNotes"); + print ''; + print ajax_constantonoff("TAKEPOS_ORDER_NOTES", array(), $conf->entity, 0, 0, 1, 0); + //print $form->selectyesno("TAKEPOS_ORDER_NOTES", $conf->global->TAKEPOS_ORDER_NOTES, 1); + print '
    '; + print $langs->trans("BasicPhoneLayout"); + print ''; + //print $form->selectyesno("TAKEPOS_PHONE_BASIC_LAYOUT", $conf->global->TAKEPOS_PHONE_BASIC_LAYOUT, 1); + print ajax_constantonoff("TAKEPOS_PHONE_BASIC_LAYOUT", array(), $conf->entity, 0, 0, 1, 0); + print '
    '; + print $langs->trans("ProductSupplements"); + print ''; + //print $form->selectyesno("TAKEPOS_SUPPLEMENTS", $conf->global->TAKEPOS_SUPPLEMENTS, 1); + print ajax_constantonoff("TAKEPOS_SUPPLEMENTS", array(), $conf->entity, 0, 0, 1, 0); + print '
    '; + print $langs->trans("SupplementCategory"); + print ''; + print $form->select_all_categories(Categorie::TYPE_PRODUCT, $conf->global->TAKEPOS_SUPPLEMENTS_CATEGORY, 'TAKEPOS_SUPPLEMENTS_CATEGORY', 64, 0, 0); + print ajax_combobox('TAKEPOS_SUPPLEMENTS_CATEGORY'); + print "
    '; + print 'QR - '.$langs->trans("CustomerMenu"); + print ''; + print ajax_constantonoff("TAKEPOS_QR_MENU", array(), $conf->entity, 0, 0, 1, 0); + print '
    '; + print 'QR - '.$langs->trans("AutoOrder"); + print ''; + print ajax_constantonoff("TAKEPOS_AUTO_ORDER", array(), $conf->entity, 0, 0, 1, 0); + print '
    '; + print '
    '; + + print '
    '; + + print '
    '; +} + +if ($conf->global->TAKEPOS_BAR_RESTAURANT) { + if ($conf->global->TAKEPOS_QR_MENU) + { + $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root)); + $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file + print '
    '; + print ''; + print ''; + print ''; + print "\n"; + print ''; + print ''; + print '
    '.$langs->trans("URL").''.$langs->trans("QR").'
    '; + print "".$urlwithroot."/takepos/public/menu.php"; + print ''; + print ""; + print '
    '; + } + + if ($conf->global->TAKEPOS_AUTO_ORDER) + { + print '
    '; + print ''; + print ''; + print ''; + print "\n"; + + //global $dolibarr_main_url_root; + $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root)); + $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file + $sql = "SELECT rowid, entity, label, leftpos, toppos, floor FROM ".MAIN_DB_PREFIX."takepos_floor_tables"; + $resql = $db->query($sql); + $rows = array(); + while ($row = $db->fetch_array($resql)) { + print ''; + print ''; + print ''; + } + + print '
    '.$langs->trans("Table").''.$langs->trans("URL").''.$langs->trans("QR").'
    '; + print $langs->trans("Table")." ".$row['label']; + print ''; + print "".$urlwithroot."/takepos/public/auto_order.php?key=".dol_encode($row['rowid']).""; + print ''; + print ""; + print '
    '; + } +} + print "\n"; diff --git a/htdocs/takepos/admin/receipt.php b/htdocs/takepos/admin/receipt.php index 59e0f3da96c..252781ec635 100644 --- a/htdocs/takepos/admin/receipt.php +++ b/htdocs/takepos/admin/receipt.php @@ -87,6 +87,7 @@ print ''; print load_fiche_titre($langs->trans("PrintMethod"), '', ''); +print '
    '; print ''; print ''; print ''; @@ -143,14 +144,25 @@ if ($conf->global->TAKEPOS_PRINT_METHOD == "takeposconnector") } print "\n"; print '
    '.$langs->trans("Name").''.$langs->trans("Description").''.$langs->trans("Status").'
    '; +print '
    '; + print load_fiche_titre($langs->trans("Setup"), '', ''); +print '
    '; print ''; print ''; print ''; print "\n"; +// VAT Grouped on ticket +print '\n"; + if ($conf->global->TAKEPOS_PRINT_METHOD == "takeposconnector") { print '\n"; print '
    '.$langs->trans("Parameters").''.$langs->trans("Value").'
    '; +print $langs->trans('TicketVatGrouped'); +print ''; +print ajax_constantonoff("TAKEPOS_TICKET_VAT_GROUPPED", array(), $conf->entity, 0, 0, 1, 0); +//print $form->selectyesno("TAKEPOS_TICKET_VAT_GROUPPED", $conf->global->TAKEPOS_TICKET_VAT_GROUPPED, 1); +print "
    '; print $langs->trans("URL")." / ".$langs->trans("IPAddress").' ('.$langs->trans("TakeposConnectorNecesary").')'; @@ -214,6 +226,7 @@ print $form->selectyesno("TAKEPOS_AUTO_PRINT_TICKETS", $conf->global->TAKEPOS_AU print "
    '; +print '
    '; print '
    '; diff --git a/htdocs/takepos/admin/setup.php b/htdocs/takepos/admin/setup.php index 22355c70d9d..ab3b200df35 100644 --- a/htdocs/takepos/admin/setup.php +++ b/htdocs/takepos/admin/setup.php @@ -274,14 +274,6 @@ print img_object('', 'category', 'class="paddingright"').$form->select_all_categ print ajax_combobox('TAKEPOS_ROOT_CATEGORY_ID'); print "\n"; -// VAT Grouped on ticket -print ''; -print $langs->trans('TicketVatGrouped'); -print ''; -print ajax_constantonoff("TAKEPOS_TICKET_VAT_GROUPPED", array(), $conf->entity, 0, 0, 1, 0); -//print $form->selectyesno("TAKEPOS_TICKET_VAT_GROUPPED", $conf->global->TAKEPOS_TICKET_VAT_GROUPPED, 1); -print "\n"; - // Sort product print ''; print $langs->trans("SortProductField"); @@ -429,14 +421,6 @@ print "\n"; //print $form->selectarray('TAKEPOS_ADDON', $array, (empty($conf->global->TAKEPOS_ADDON) ? '0' : $conf->global->TAKEPOS_ADDON), 0); //print "\n"; -print ''; -print $langs->trans("EnableBarOrRestaurantFeatures"); -print ''; -print ''; -print ajax_constantonoff("TAKEPOS_BAR_RESTAURANT", array(), $conf->entity, 0, 0, 1, 0); -//print $form->selectyesno("TAKEPOS_BAR_RESTAURANT", $conf->global->TAKEPOS_BAR_RESTAURANT, 1); -print "\n"; - print ''; print ''; diff --git a/htdocs/takepos/invoice.php b/htdocs/takepos/invoice.php index cfde72c9fd3..350299814ec 100644 --- a/htdocs/takepos/invoice.php +++ b/htdocs/takepos/invoice.php @@ -398,6 +398,7 @@ if ($action == "deleteline") { $invoice->deleteline($deletelineid); $invoice->fetch($placeid); } + if (count($invoice->lines)==0) $invoice->delete($user); } if ($action == "delete") { diff --git a/htdocs/theme/eldy/info-box.inc.php b/htdocs/theme/eldy/info-box.inc.php index b009d7ef95b..45be80d026b 100644 --- a/htdocs/theme/eldy/info-box.inc.php +++ b/htdocs/theme/eldy/info-box.inc.php @@ -77,7 +77,12 @@ if (!defined('ISLOADEDBYSTEELSHEET')) die('Must be call by steelsheet'); ?> text-align: center; font-size: 45px; line-height: 90px; - background: rgba(0, 0, 0, 0.08) !important + background: rgba(0, 0, 0, 0.08) !important; +} + +.info-box-module .info-box-icon { + padding-top: 5px; + padding-bottom: 5px; } .info-box-sm .info-box-icon { height: 80px; @@ -436,7 +441,7 @@ if (GETPOSTISSET('THEME_SATURATE_RATIO')) $conf->global->THEME_SATURATE_RATIO = .info-box-module .info-box-content { - height: 6.4em; + height: 98px; } /* Disabled. This break the responsive on smartphone .box{ diff --git a/htdocs/theme/eldy/theme_vars.inc.php b/htdocs/theme/eldy/theme_vars.inc.php index cd88654e309..c28b9a21e0c 100644 --- a/htdocs/theme/eldy/theme_vars.inc.php +++ b/htdocs/theme/eldy/theme_vars.inc.php @@ -71,7 +71,7 @@ $colortexttitle = '0,0,0'; $colortexttitlelink = '10, 20, 100'; $colortext = '0,0,0'; $colortextlink = '10, 20, 100'; -$fontsize = '0.86em'; +$fontsize = '0.95em'; $fontsizesmaller = '0.75em'; $topMenuFontSize = '1.1em'; $toolTipBgColor = 'rgba(255, 255, 255, 0.96)'; diff --git a/htdocs/variants/card.php b/htdocs/variants/card.php index 0669964ba57..6d077e85ad1 100644 --- a/htdocs/variants/card.php +++ b/htdocs/variants/card.php @@ -90,9 +90,9 @@ if ($confirm == 'yes') { if ($action == 'confirm_delete') { $db->begin(); - $res = $objectval->deleteByFkAttribute($object->id); + $res = $objectval->deleteByFkAttribute($object->id, $user); - if ($res < 1 || ($object->delete() < 1)) { + if ($res < 1 || ($object->delete($user) < 1)) { $db->rollback(); setEventMessages($langs->trans('CoreErrorMessage'), $object->errors, 'errors'); header('Location: '.dol_buildpath('/variants/card.php?id='.$object->id, 2)); @@ -105,7 +105,7 @@ if ($confirm == 'yes') { } elseif ($action == 'confirm_deletevalue') { if ($objectval->fetch($valueid) > 0) { - if ($objectval->delete() < 1) { + if ($objectval->delete($user) < 1) { setEventMessages($langs->trans('CoreErrorMessage'), $objectval->errors, 'errors'); } else { setEventMessages($langs->trans('RecordSaved'), null, 'mesgs'); diff --git a/htdocs/variants/class/ProductAttribute.class.php b/htdocs/variants/class/ProductAttribute.class.php index 36b4823b51b..6e2335c2f99 100644 --- a/htdocs/variants/class/ProductAttribute.class.php +++ b/htdocs/variants/class/ProductAttribute.class.php @@ -16,17 +16,18 @@ * along with this program. If not, see . */ +require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php'; /** * Class ProductAttribute * Used to represent a product attribute */ -class ProductAttribute +class ProductAttribute extends CommonObject { /** * Database handler * @var DoliDB */ - private $db; + public $db; /** * Id of the product attribute @@ -119,7 +120,8 @@ class ProductAttribute $return[] = $tmp; } - } else dol_print_error($this->db); + } + else dol_print_error($this->db); return $return; } @@ -127,11 +129,21 @@ class ProductAttribute /** * Creates a product attribute * - * @param User $user Object user that create + * @param User $user Object user + * @param int $notrigger Do not execute trigger * @return int <0 KO, Id of new variant if OK */ - public function create(User $user) + public function create(User $user, $notrigger = 0) { + if (empty($notrigger)) { + // Call trigger + $result = $this->call_trigger('PRODUCT_ATTRIBUTE_CREATE', $user); + if ($result < 0) { + return -1; + } + // End call triggers + } + //Ref must be uppercase $this->ref = strtoupper($this->ref); @@ -152,11 +164,21 @@ class ProductAttribute /** * Updates a product attribute * - * @param User $user Object user + * @param User $user Object user + * @param int $notrigger Do not execute trigger * @return int <0 KO, >0 OK */ - public function update(User $user) + public function update(User $user, $notrigger = 0) { + if (empty($notrigger)) { + // Call trigger + $result = $this->call_trigger('PRODUCT_ATTRIBUTE_MODIFY', $user); + if ($result < 0) { + return -1; + } + // End call triggers + } + //Ref must be uppercase $this->ref = trim(strtoupper($this->ref)); $this->label = trim($this->label); @@ -173,11 +195,21 @@ class ProductAttribute /** * Deletes a product attribute * - * @param User $user Object user - * @return int <0 KO, >0 OK + * @param User $user Object user + * @param int $notrigger Do not execute trigger + * @return int <0 KO, >0 OK */ - public function delete($user = null) + public function delete(User $user, $notrigger = 0) { + if (empty($notrigger)) { + // Call trigger + $result = $this->call_trigger('PRODUCT_ATTRIBUTE_DELETE', $user); + if ($result < 0) { + return -1; + } + // End call triggers + } + $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_attribute WHERE rowid = ".(int) $this->id; if ($this->db->query($sql)) { diff --git a/htdocs/variants/class/ProductAttributeValue.class.php b/htdocs/variants/class/ProductAttributeValue.class.php index 0a26b3eace2..ab998c6752b 100644 --- a/htdocs/variants/class/ProductAttributeValue.class.php +++ b/htdocs/variants/class/ProductAttributeValue.class.php @@ -16,17 +16,18 @@ * along with this program. If not, see . */ +require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php'; /** * Class ProductAttributeValue * Used to represent a product attribute value */ -class ProductAttributeValue +class ProductAttributeValue extends CommonObject { /** * Database handler * @var DoliDB */ - private $db; + public $db; /** * Attribute value id @@ -144,10 +145,11 @@ class ProductAttributeValue /** * Creates a value for a product attribute * - * @param User $user Object user - * @return int <0 KO >0 OK + * @param User $user Object user + * @param int $notrigger Do not execute trigger + * @return int <0 KO >0 OK */ - public function create(User $user) + public function create(User $user, $notrigger = 0) { if (!$this->fk_product_attribute) { return -1; @@ -155,15 +157,25 @@ class ProductAttributeValue // Ref must be uppercase $this->ref = strtoupper($this->ref); + $this->value = $this->db->escape($this->value); $sql = "INSERT INTO ".MAIN_DB_PREFIX."product_attribute_value (fk_product_attribute, ref, value, entity) VALUES ('".(int) $this->fk_product_attribute."', '".$this->db->escape($this->ref)."', - '".$this->db->escape($this->value)."', ".(int) $this->entity.")"; + '".$this->value."', ".(int) $this->entity.")"; $query = $this->db->query($sql); if ($query) { $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'product_attribute_value'); + if (empty($notrigger)) { + // Call trigger + $result = $this->call_trigger('PRODUCT_ATTRIBUTE_VALUE_CREATE', $user); + if ($result < 0) { + return -1; + } + // End call triggers + } + return 1; } @@ -173,11 +185,21 @@ class ProductAttributeValue /** * Updates a product attribute value * - * @param User $user Object user - * @return int <0 if KO, >0 if OK + * @param User $user Object user + * @param int $notrigger Do not execute trigger + * @return int <0 if KO, >0 if OK */ - public function update(User $user) + public function update(User $user, $notrigger = 0) { + if (empty($notrigger)) { + // Call trigger + $result = $this->call_trigger('PRODUCT_ATTRIBUTE_VALUE_MODIFY', $user); + if ($result < 0) { + return -1; + } + // End call triggers + } + //Ref must be uppercase $this->ref = trim(strtoupper($this->ref)); $this->value = trim($this->value); @@ -196,33 +218,62 @@ class ProductAttributeValue /** * Deletes a product attribute value * + * @param User $user Object user + * @param int $notrigger Do not execute trigger * @return int <0 KO, >0 OK */ - public function delete() + public function delete(User $user, $notrigger = 0) { - $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_attribute_value WHERE rowid = ".(int) $this->id; - if ($this->db->query($sql)) { - return 1; - } + if (empty($notrigger)) { + // Call trigger + $result = $this->call_trigger('PRODUCT_ATTRIBUTE_VALUE_DELETE', $user); + if ($result < 0) { + return -1; + } + // End call triggers + } + $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_attribute_value WHERE rowid = ".(int) $this->id; + if ($this->db->query($sql)) { + return 1; + } - return -1; + return -1; } /** * Deletes all product attribute values by a product attribute id * - * @param int $fk_attribute Product attribute id + * @param int $fk_attribute Product attribute id + * @param User $user Object user * @return int <0 KO, >0 OK */ - public function deleteByFkAttribute($fk_attribute) + public function deleteByFkAttribute($fk_attribute, User $user) { - $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_attribute_value WHERE fk_product_attribute = ".(int) $fk_attribute; + $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."product_attribute_value WHERE fk_product_attribute = ".(int) $fk_attribute; - if ($this->db->query($sql)) { - return 1; - } + $query = $this->db->query($sql); - return -1; + if (!$query) { + return -1; + } + + if (!$this->db->num_rows($query)) { + return 1; + } + + while ($obj = $this->db->fetch_object($query)) { + $tmp = new ProductAttributeValue($this->db); + if ($tmp->fetch($obj->rowid) > 0) { + $result = $tmp->delete($user); + if ($result < 0) { + return -1; + } + } else { + return -1; + } + } + + return 1; } } diff --git a/htdocs/variants/class/ProductCombination.class.php b/htdocs/variants/class/ProductCombination.class.php index 3bb4f7312ee..7d6a07d736d 100644 --- a/htdocs/variants/class/ProductCombination.class.php +++ b/htdocs/variants/class/ProductCombination.class.php @@ -71,6 +71,12 @@ class ProductCombination */ public $entity; + /** + * Combination price level + * @var ProductCombinationLevel[] + */ + public $combination_price_levels; + /** * Constructor * @@ -92,6 +98,8 @@ class ProductCombination */ public function fetch($rowid) { + global $conf; + $sql = "SELECT rowid, fk_product_parent, fk_product_child, variation_price, variation_price_percentage, variation_weight FROM ".MAIN_DB_PREFIX."product_attribute_combination WHERE rowid = ".(int) $rowid." AND entity IN (".getEntity('product').")"; $query = $this->db->query($sql); @@ -113,9 +121,118 @@ class ProductCombination $this->variation_price_percentage = $obj->variation_price_percentage; $this->variation_weight = $obj->variation_weight; + if (!empty($conf->global->PRODUIT_MULTIPRICES)) { + $this->fetchCombinationPriceLevels(); + } + return 1; } + + /** + * Retrieves combination price levels + * + * @param int $fk_price_level the price level to fetch, use 0 for all + * @param bool $useCache to use cache or not + * @return int <0 KO, >0 OK + */ + public function fetchCombinationPriceLevels($fk_price_level = 0, $useCache = true) + { + global $conf; + + // Check cache + if (!empty($this->combination_price_levels) && $useCache){ + if ((!empty($fk_price_level) && isset($this->combination_price_levels[$fk_price_level])) || empty($fk_price_level)){ + return 1; + } + } + + if (!is_array($this->combination_price_levels) + || empty($fk_price_level) // if fetch an unique level dont erase all already fetched + ){ + $this->combination_price_levels = array(); + } + + $staticProductCombinationLevel = new ProductCombinationLevel($this->db); + $combination_price_levels = $staticProductCombinationLevel->fetchAll($this->id, $fk_price_level); + + if (!is_array($combination_price_levels)) { + return -1; + } + + if (empty($combination_price_levels)){ + + /** + * for auto retrocompatibility with last behavior + */ + $productCombinationLevel = new ProductCombinationLevel($this->db); + $productCombinationLevel->fk_price_level = intval($fk_price_level); + $productCombinationLevel->fk_product_attribute_combination = $this->id; + $productCombinationLevel->variation_price = $this->variation_price; + $productCombinationLevel->variation_price_percentage = $this->variation_price_percentage; + + if ($fk_price_level>0){ + $combination_price_levels[$fk_price_level] = $productCombinationLevel; + } + else { + for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++){ + $combination_price_levels[$i] = $productCombinationLevel; + } + } + } + + $this->combination_price_levels = $combination_price_levels; + + return 1; + } + + /** + * Retrieves combination price levels + * + * @param int $clean levels off PRODUIT_MULTIPRICES_LIMIT + * @return int <0 KO, >0 OK + */ + public function saveCombinationPriceLevels($clean = 1) + { + global $conf; + + $errors = 0; + + $staticProductCombinationLevel = new ProductCombinationLevel($this->db); + + // Delete all + if (empty($this->combination_price_levels)){ + return $staticProductCombinationLevel->deleteAllForCombination($this->id); + } + + // Clean not needed price levels + if ($clean){ + $res = $staticProductCombinationLevel->clean($this->id); + + if ($res<0){ + $this->errors[] = 'Fail to clean not needed price levels'; + return -1; + } + } + + foreach ($this->combination_price_levels as $fk_price_level => $combination_price_level){ + $res = $combination_price_level->save(); + if ($res<1){ + $this->error = 'save combination price level '.$fk_price_level . ' '.$combination_price_level->error; + $this->errors[] = $this->error; + $errors ++; + } + } + + if ($errors > 0){ + return $errors*-1; + } + else { + return 1; + } + } + + /** * Retrieves a product combination by a child product row id * @@ -124,6 +241,8 @@ class ProductCombination */ public function fetchByFkProductChild($fk_child) { + global $conf; + $sql = "SELECT rowid, fk_product_parent, fk_product_child, variation_price, variation_price_percentage, variation_weight FROM ".MAIN_DB_PREFIX."product_attribute_combination WHERE fk_product_child = ".(int) $fk_child." AND entity IN (".getEntity('product').")"; $query = $this->db->query($sql); @@ -145,6 +264,10 @@ class ProductCombination $this->variation_price_percentage = $result->variation_price_percentage; $this->variation_weight = $result->variation_weight; + if (!empty($conf->global->PRODUIT_MULTIPRICES)) { + $this->fetchCombinationPriceLevels(); + } + return 1; } @@ -156,6 +279,8 @@ class ProductCombination */ public function fetchAllByFkProductParent($fk_product_parent) { + global $conf; + $sql = "SELECT rowid, fk_product_parent, fk_product_child, variation_price, variation_price_percentage, variation_weight FROM ".MAIN_DB_PREFIX."product_attribute_combination WHERE fk_product_parent = ".(int) $fk_product_parent." AND entity IN (".getEntity('product').")"; $query = $this->db->query($sql); @@ -175,6 +300,10 @@ class ProductCombination $tmp->variation_price_percentage = $result->variation_price_percentage; $tmp->variation_weight = $result->variation_weight; + if (!empty($conf->global->PRODUIT_MULTIPRICES)) { + $tmp->fetchCombinationPriceLevels(); + } + $return[] = $tmp; } @@ -209,6 +338,8 @@ class ProductCombination */ public function create($user) { + global $conf; + $sql = "INSERT INTO ".MAIN_DB_PREFIX."product_attribute_combination (fk_product_parent, fk_product_child, variation_price, variation_price_percentage, variation_weight, entity) VALUES (".(int) $this->fk_product_parent.", ".(int) $this->fk_product_child.", @@ -221,6 +352,13 @@ class ProductCombination return -1; } + if (!empty($conf->global->PRODUIT_MULTIPRICES)) { + $res = $this->saveCombinationPriceLevels(); + if ($res<0){ + return -2; + } + } + $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'product_attribute_combination'); return 1; @@ -234,6 +372,8 @@ class ProductCombination */ public function update(User $user) { + global $conf; + $sql = "UPDATE ".MAIN_DB_PREFIX."product_attribute_combination SET fk_product_parent = ".(int) $this->fk_product_parent.", fk_product_child = ".(int) $this->fk_product_child.", variation_price = ".(float) $this->variation_price.", variation_price_percentage = ".(int) $this->variation_price_percentage.", @@ -245,6 +385,14 @@ class ProductCombination return -1; } + + if (!empty($conf->global->PRODUIT_MULTIPRICES)) { + $res = $this->saveCombinationPriceLevels(); + if ($res<0){ + return -2; + } + } + $parent = new Product($this->db); $parent->fetch($this->fk_product_parent); @@ -266,6 +414,12 @@ class ProductCombination $comb2val = new ProductCombination2ValuePair($this->db); $comb2val->deleteByFkCombination($this->id); + // remove combination price levels + if (!$this->db->query("DELETE FROM ".MAIN_DB_PREFIX."product_attribute_combination_price_level WHERE fk_product_attribute_combination = ".(int) $this->id)) { + $this->db->rollback(); + return -1; + } + $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_attribute_combination WHERE rowid = ".(int) $this->id; if ($this->db->query($sql)) { @@ -341,6 +495,7 @@ class ProductCombination $child->label = $parent->label.$varlabel;; } + if ($child->update($child->id, $user) > 0) { $new_vat = $parent->tva_tx; $new_npr = $parent->tva_npr; @@ -349,9 +504,12 @@ class ProductCombination if (!empty($conf->global->PRODUIT_MULTIPRICES)) { for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) { - if ($parent->multiprices[$i] != '') { + if ($parent->multiprices[$i] != '' || isset($this->combination_price_levels[$i]->variation_price)) { $new_type = $parent->multiprices_base_type[$i]; $new_min_price = $parent->multiprices_min[$i]; + $variation_price = doubleval(!isset($this->combination_price_levels[$i]->variation_price) ? $this->variation_price : $this->combination_price_levels[$i]->variation_price); + $variation_price_percentage = doubleval(!isset($this->combination_price_levels[$i]->variation_price_percentage) ? $this->variation_price_percentage : $this->combination_price_levels[$i]->variation_price_percentage); + if ($parent->prices_by_qty_list[$i]) { $new_psq = 1; } else { @@ -364,12 +522,12 @@ class ProductCombination $new_price = $parent->multiprices[$i]; } - if ($this->variation_price_percentage) { + if ($variation_price_percentage) { if ($new_price != 0) { - $new_price *= 1 + ($this->variation_price / 100); + $new_price *= 1 + ($variation_price / 100); } } else { - $new_price += $this->variation_price; + $new_price += $variation_price; } $child->updatePrice($new_price, $new_type, $user, $new_vat, $new_min_price, $i, $new_npr, $new_psq); @@ -508,7 +666,7 @@ WHERE c.fk_product_parent = ".(int) $productid." AND p.tosell = 1"; * @param Product $product Parent product * @param array $combinations Attribute and value combinations. * @param array $variations Price and weight variations - * @param bool $price_var_percent Is the price variation a relative variation? + * @param bool|array $price_var_percent Is the price variation a relative variation? * @param bool|float $forced_pricevar If the price variation is forced * @param bool|float $forced_weightvar If the weight variation is forced * @param bool|string $forced_refvar If the reference is forced @@ -523,6 +681,8 @@ WHERE c.fk_product_parent = ".(int) $productid." AND p.tosell = 1"; $db->begin(); + $price_impact = array(1=>0); // init level price impact + $forced_refvar = trim($forced_refvar); if (!empty($forced_refvar) && $forced_refvar != $product->ref) { @@ -545,7 +705,12 @@ WHERE c.fk_product_parent = ".(int) $productid." AND p.tosell = 1"; $weight_impact = (float) $forced_weightvar; // If false, return 0 //Final price impact - $price_impact = (float) $forced_pricevar; // If false, return 0 + if (!is_array($forced_pricevar)){ + $price_impact[1] = (float) $forced_pricevar; // If false, return 0 + } + else { + $price_impact = $forced_pricevar; + } $newcomb = new ProductCombination($db); $existingCombination = $newcomb->fetchByProductCombination2ValuePairs($product->id, $combinations); @@ -587,7 +752,15 @@ WHERE c.fk_product_parent = ".(int) $productid." AND p.tosell = 1"; $weight_impact += (float) price2num($variations[$currcombattr][$currcombval]['weight']); } if ($forced_pricevar === false) { - $price_impact += (float) price2num($variations[$currcombattr][$currcombval]['price']); + $price_impact[1] += (float) price2num($variations[$currcombattr][$currcombval]['price']); + + // Manage Price levels + if ($conf->global->PRODUIT_MULTIPRICES){ + for ($i = 2; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) + { + $price_impact[$i] += (float) price2num($variations[$currcombattr][$currcombval]['price']); + } + } } if ($forced_refvar === false) { @@ -606,9 +779,27 @@ WHERE c.fk_product_parent = ".(int) $productid." AND p.tosell = 1"; } $newcomb->variation_price_percentage = $price_var_percent; - $newcomb->variation_price = $price_impact; + $newcomb->variation_price = $price_impact[1]; $newcomb->variation_weight = $weight_impact; + // Init price level + if ($conf->global->PRODUIT_MULTIPRICES){ + for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++){ + $productCombinationLevel = new ProductCombinationLevel($this->db); + $productCombinationLevel->fk_product_attribute_combination = 0; + $productCombinationLevel->fk_price_level = $i; + $productCombinationLevel->variation_price = $price_impact[$i]; + + if (is_array($price_var_percent)){ + $productCombinationLevel->variation_price_percentage = !empty($price_var_percent[$i]) ? $price_var_percent[$i] : 0; + }else { + $productCombinationLevel->variation_price_percentage = $price_var_percent; + } + + $newcomb->combination_price_levels[$i] = $productCombinationLevel; + } + } + $newproduct->weight += $weight_impact; // Now create the product @@ -764,3 +955,260 @@ WHERE c.fk_product_parent = ".(int) $productid." AND p.tosell = 1"; return $label; } } + + + +/** + * Class ProductCombinationLevel + * Used to represent a product combination Level + */ +class ProductCombinationLevel +{ + /** + * Database handler + * @var DoliDB + */ + private $db; + + /** + * @var string Name of table without prefix where object is stored + */ + public $table_element = 'product_attribute_combination_price_level'; + + /** + * Rowid of combination + * @var int + */ + public $id; + + /** + * Rowid of parent product combination + * @var int + */ + public $fk_product_attribute_combination; + + /** + * Combination price level + * @var int + */ + public $fk_price_level; + + /** + * Price variation + * @var float + */ + public $variation_price; + + /** + * Is the price variation a relative variation? + * @var bool + */ + public $variation_price_percentage = false; + + /** + * Constructor + * + * @param DoliDB $db Database handler + */ + public function __construct(DoliDB $db) + { + $this->db = $db; + } + + /** + * Retrieves a combination level by its rowid + * + * @param int $rowid Row id + * @return int <0 KO, >0 OK + */ + public function fetch($rowid) + { + $sql = "SELECT rowid, fk_product_attribute_combination, fk_price_level, variation_price, variation_price_percentage FROM " . MAIN_DB_PREFIX . $this->table_element." WHERE rowid = " . (int) $rowid; + + $obj = $this->db->getRow($sql); + + if ($obj){ + return $this->fetchFormObj($obj); + } + + return -1; + } + + + /** + * Retrieves combination price levels + * + * @param int $fk_product_attribute_combination + * @param int $fk_price_level the price level to fetch, use 0 for all + * @return self[] | -1 on KO + */ + public function fetchAll($fk_product_attribute_combination, $fk_price_level = 0) + { + + $sql = "SELECT rowid, fk_product_attribute_combination, fk_price_level, variation_price, variation_price_percentage" + ." FROM ".MAIN_DB_PREFIX.$this->table_element + ." WHERE fk_product_attribute_combination = ".intval($fk_product_attribute_combination); + + if (!empty($fk_price_level)){ + $sql.= ' AND fk_price_level = '.intval($fk_price_level); + } + + $combination_price_levels = $this->db->getRows($sql); + + if (!is_array($combination_price_levels)) { + return -1; + } + + $result = array(); + + if (!empty($combination_price_levels)) { + // For more simple usage set level as array key + foreach ($combination_price_levels as $k => $row){ + $productCombinationLevel = new ProductCombinationLevel($this->db); + $productCombinationLevel->fetchFormObj($row); + $result[$row->fk_price_level] = $productCombinationLevel; + } + } + + return $result; + } + + /** + * assign vars form an stdclass like sql obj + * + * @param int $rowid Row id + * @return int <0 KO, >0 OK + */ + public function fetchFormObj($obj) + { + if (!$obj) { + return -1; + } + + $this->id = $obj->rowid; + $this->fk_product_attribute_combination = doubleval($obj->fk_product_attribute_combination); + $this->fk_price_level = intval($obj->fk_price_level); + $this->variation_price = doubleval($obj->variation_price); + $this->variation_price_percentage = (bool) $obj->variation_price_percentage; + + return 1; + } + + + /** + * save + * + * @return int <0 KO, >0 OK + */ + public function save() + { + $errors = 0; + + + if (empty($this->fk_product_attribute_combination) || empty($this->fk_price_level)){ + return -1; + } + + // check if level exist in DB before add + if (empty($this->id)){ + $sql = "SELECT rowid id" + ." FROM ".MAIN_DB_PREFIX . $this->table_element + ." WHERE fk_product_attribute_combination = ".(int) $this->fk_product_attribute_combination + .' AND fk_price_level = '.intval($this->fk_price_level); + + $existObj = $this->db->getRow($sql); + if ($existObj){ + $this->id = $existObj->id; + } + } + + // Update + if (!empty($this->id)) { + $sql = 'UPDATE ' . MAIN_DB_PREFIX . $this->table_element . ' SET ' + . ' variation_price = '.doubleval($this->variation_price) + . ' , variation_price_percentage = '.intval($this->variation_price_percentage) + . ' WHERE rowid = '.intval($this->id); + + $res = $this->db->query($sql); + if ($res>0){ + return $this->id; + } + else { + $this->error = $this->db->error(); + $this->errors[] = $this->error; + return -1; + } + } + else { + // ADD + $sql = "INSERT INTO " . MAIN_DB_PREFIX . $this->table_element . " (" + . " fk_product_attribute_combination, fk_price_level, variation_price, variation_price_percentage" + . " ) VALUES ( " + . intval($this->fk_product_attribute_combination) + . ' , '.intval($this->fk_price_level) + . ' , '.doubleval($this->variation_price) + . ' , '.intval($this->variation_price_percentage) + . " )"; + + $res = $this->db->query($sql); + if ($res){ + $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX . $this->table_element); + } + else { + $this->error = $this->db->error(); + $this->errors[] = $this->error; + return -1; + } + } + + return $this->id; + } + + + /** + * delete + * + * @return int <0 KO, >0 OK + */ + public function delete() + { + $res = $this->db->query("DELETE FROM ".MAIN_DB_PREFIX.$this->table_element + ." WHERE rowid = ".(int) $this->id); + + return $res ? 1 : -1; + } + + + /** + * delete all for a combination + * + * @param $fk_product_attribute_combination + * @return int <0 KO, >0 OK + */ + public function deleteAllForCombination($fk_product_attribute_combination) + { + $res = $this->db->query("DELETE FROM ".MAIN_DB_PREFIX.$this->table_element + ." WHERE fk_product_attribute_combination = ".(int) $fk_product_attribute_combination); + + return $res ? 1 : -1; + } + + + /** + * Clean not needed price levels for a combination + * + * @param $fk_product_attribute_combination + * @return int <0 KO, >0 OK + */ + public function clean($fk_product_attribute_combination) + { + global $conf; + + $res = $this->db->query("DELETE FROM ".MAIN_DB_PREFIX.$this->table_element + . " WHERE fk_product_attribute_combination = ".(int) $fk_product_attribute_combination + . " AND fk_price_level > ".intval($conf->global->PRODUIT_MULTIPRICES_LIMIT) ); + + + return $res ? 1 : -1; + } +} diff --git a/htdocs/variants/combinations.php b/htdocs/variants/combinations.php index 3886a8fcdce..b9f62bdcf73 100644 --- a/htdocs/variants/combinations.php +++ b/htdocs/variants/combinations.php @@ -33,6 +33,10 @@ $ref = GETPOST('ref', 'alpha'); $weight_impact = GETPOST('weight_impact', 'alpha'); $price_impact = GETPOST('price_impact', 'alpha'); $price_impact_percent = (bool) GETPOST('price_impact_percent'); + +$level_price_impact = GETPOST('level_price_impact', 'array'); +$level_price_impact_percent = GETPOST('level_price_impact_percent', 'array'); + $reference = GETPOST('reference', 'alpha'); $form = new Form($db); @@ -112,6 +116,18 @@ if ($_POST) { } $weight_impact = price2num($weight_impact); $price_impact = price2num($price_impact); + + // for conf PRODUIT_MULTIPRICES + if ($conf->global->PRODUIT_MULTIPRICES) { + $level_price_impact = array_map('price2num', $level_price_impact); + $level_price_impact_percent = array_map('price2num', $level_price_impact_percent); + } + else { + $level_price_impact = array(1 => $weight_impact); + $level_price_impact_percent = array(1 => $price_impact_percent); + } + + $sanit_features = array(); //First, sanitize @@ -141,11 +157,10 @@ if ($_POST) { // sanit_feature is an array with 1 (and only 1) value per attribute. // For example: Color->blue, Size->Small, Option->2 //var_dump($sanit_features); - //var_dump($productCombination2ValuePairs1); exit; if (!$prodcomb->fetchByProductCombination2ValuePairs($id, $sanit_features)) { - $result = $prodcomb->createProductCombination($user, $object, $sanit_features, array(), $price_impact_percent, $price_impact, $weight_impact, $reference); + $result = $prodcomb->createProductCombination($user, $object, $sanit_features, array(), $level_price_impact_percent, $level_price_impact, $weight_impact, $reference); if ($result > 0) { setEventMessages($langs->trans('RecordSaved'), null, 'mesgs'); @@ -227,6 +242,32 @@ if ($_POST) { $prodcomb->variation_price = $price_impact; $prodcomb->variation_weight = $weight_impact; + // for conf PRODUIT_MULTIPRICES + if ($conf->global->PRODUIT_MULTIPRICES) { + $level_price_impact = array_map('price2num', $level_price_impact); + $level_price_impact_percent = array_map(function ($a) { + return !empty($a);}, $level_price_impact_percent); + + $prodcomb->variation_price = $level_price_impact[1]; + $prodcomb->variation_price_percentage = (bool) $level_price_impact_percent[1]; + } + else { + $level_price_impact = array(1 => $weight_impact); + $level_price_impact_percent = array(1 => $price_impact_percent); + } + + if ($conf->global->PRODUIT_MULTIPRICES){ + $prodcomb->combination_price_levels = array(); + for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++){ + $productCombinationLevel = new ProductCombinationLevel($db); + $productCombinationLevel->fk_product_attribute_combination = $prodcomb->id; + $productCombinationLevel->fk_price_level = $i; + $productCombinationLevel->variation_price = $level_price_impact[$i]; + $productCombinationLevel->variation_price_percentage = $level_price_impact_percent[$i]; + $prodcomb->combination_price_levels[$i] = $productCombinationLevel; + } + } + if ($prodcomb->update($user) > 0) { setEventMessages($langs->trans('RecordSaved'), null, 'mesgs'); header('Location: '.dol_buildpath('/variants/combinations.php?id='.$id, 2)); @@ -594,12 +635,33 @@ if (!empty($id) || !empty($ref)) + global->PRODUIT_MULTIPRICES)){ ?> - > + > + - fetchCombinationPriceLevels(); + + for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) + { + print ''; + print ''; + if ($i===1){ + print ' ('.$langs->trans('ApplyToAllPriceImpactLevel').')'; + } + print ''; + print ''; + print 'combination_price_levels[$i]->variation_price_percentage) ? ' checked' : '' ).'> '; + + print ''; + print ''; + } + } + if ($object->isProduct()) { print ''; print ''; @@ -609,9 +671,30 @@ if (!empty($id) || !empty($ref)) print ''; } - dol_fiche_end(); - ?> + if (!empty($conf->global->PRODUIT_MULTIPRICES)){ + ?> + +
    value="trans('Create') : $langs->trans('Save') ?>" class="button">