2
0
forked from Wavyzz/dolibarr

NEW Added product attributes feature

This commit is contained in:
Marcos García de La Fuente
2016-07-23 16:37:21 +02:00
parent 8cc8a3f064
commit c082505021
43 changed files with 3763 additions and 97 deletions

View File

@@ -0,0 +1,65 @@
<?php
/* Copyright (C) 2016 Marcos García <marcosgdf@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
require '../../main.inc.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/admin.lib.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
$langs->load("admin");
$langs->load("products");
// Security check
if (! $user->admin || (empty($conf->product->enabled) && empty($conf->service->enabled)))
accessforbidden();
if ($_POST) {
$value = GETPOST('PRODUIT_ATTRIBUTES_HIDECHILD');
if (dolibarr_set_const($db, 'PRODUIT_ATTRIBUTES_HIDECHILD', $value, 'chaine', 0, '', $conf->entity)) {
setEventMessage($langs->trans('RecordSaved'));
} else {
setEventMessage($langs->trans('CoreErrorMessage'), 'errors');
}
}
$title = $langs->trans('ModuleSetup').' '.$langs->trans('ProductAttributes');
llxHeader('', $title);
$linkback='<a href="'.DOL_URL_ROOT.'/admin/modules.php">'.$langs->trans("BackToModuleList").'</a>';
print load_fiche_titre($title,$linkback,'title_setup');
dol_fiche_head(array(), 'general', $tab, 0, 'product');
print '<form method="post">';
print '<table class="noborder" width="100%">';
print '<tr class="liste_titre">';
print '<td>'.$langs->trans("Parameters").'</td>'."\n";
print '<td align="right" width="60">'.$langs->trans("Value").'</td>'."\n";
print '<td width="80">&nbsp;</td></tr>'."\n";
print '<tr><td>'.$langs->trans('HideProductCombinations').'</td><td>';
print $form->selectyesno("PRODUIT_ATTRIBUTES_HIDECHILD",$conf->global->PRODUIT_ATTRIBUTES_HIDECHILD,1).'</td></tr>';
print '</table>';
print '<br><div style="text-align: center"><input type="submit" value="'.$langs->trans('Save').'" class="button"></div>';
print '</form>';
llxFooter();
$db->close();

View File

View File

@@ -0,0 +1,50 @@
<?php
/* Copyright (C) 2016 Marcos García <marcosgdf@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
define('NOTOKENRENEWAL','1');
define('NOREQUIREMENU','1');
define('NOREQUIREHTML','1');
define('NOREQUIREAJAX','1');
define('NOREQUIRESOC','1');
require '../../main.inc.php';
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductCombination.class.php';
header('Content-Type: application/json');
$id = GETPOST('id');
if (!$id) {
print json_encode(array(
'error' => 'ID not set'
));
die;
}
$product = new Product($db);
if ($product->fetch($id) < 0) {
print json_encode(array(
'error' => 'Product not found'
));
}
$prodcomb = new ProductCombination($db);
echo json_encode($prodcomb->getUniqueAttributesAndValuesByFkProductParent($product->id));

View File

@@ -0,0 +1,61 @@
<?php
/* Copyright (C) 2016 Marcos García <marcosgdf@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
define('NOTOKENRENEWAL','1');
define('NOREQUIREMENU','1');
define('NOREQUIREHTML','1');
define('NOREQUIREAJAX','1');
define('NOREQUIRESOC','1');
require '../../main.inc.php';
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductAttribute.class.php';
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductAttributeValue.class.php';
header('Content-Type: application/json');
$id = GETPOST('id');
if (!$id) {
print json_encode(array(
'error' => 'ID not set'
));
die;
}
$prodattr = new ProductAttribute($db);
if ($prodattr->fetch($id) < 0) {
print json_encode(array(
'error' => 'Attribute not found'
));
die;
}
$prodattrval = new ProductAttributeValue($db);
$res = $prodattrval->fetchAllByProductAttribute($id);
if ($res == -1) {
print json_encode(array(
'error' => 'Internal error'
));
die;
}
print json_encode($res);

View File

View File

@@ -0,0 +1,53 @@
<?php
/* Copyright (C) 2016 Marcos García <marcosgdf@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
if (! defined('NOTOKENRENEWAL')) define('NOTOKENRENEWAL','1'); // Disable token renewal
if (! defined('NOREQUIREMENU')) define('NOREQUIREMENU','1');
if (! defined('NOREQUIREHTML')) define('NOREQUIREHTML','1');
if (! defined('NOREQUIREAJAX')) define('NOREQUIREAJAX','1');
if (! defined('NOREQUIRESOC')) define('NOREQUIRESOC','1');
if (! defined('NOREQUIRETRAN')) define('NOREQUIRETRAN','1');
if (! defined('NOREQUIREHOOK')) define('NOREQUIREHOOK','1'); // Disable "main.inc.php" hooks
require '../../main.inc.php';
/*
* View
*/
top_httphead();
// Registering the location of boxes
if (isset($_POST['roworder'])) {
$roworder=GETPOST('roworder','alpha',2);
dol_syslog("AjaxOrderAttribute roworder=".$roworder, LOG_DEBUG);
$rowordertab = explode(',', $roworder);
foreach ($rowordertab as $value) {
if (!empty($value)) {
$newrowordertab[] = $value;
}
}
require DOL_DOCUMENT_ROOT.'/attributes/class/ProductAttribute.class.php';
ProductAttribute::bulkUpdateOrder($db, $newrowordertab);
}

245
htdocs/attributes/card.php Normal file
View File

@@ -0,0 +1,245 @@
<?php
/* Copyright (C) 2016 Marcos García <marcosgdf@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
require '../main.inc.php';
require 'class/ProductAttribute.class.php';
require 'class/ProductAttributeValue.class.php';
$id = GETPOST('id');
$valueid = GETPOST('valueid');
$action = GETPOST('action');
$label = GETPOST('label');
$ref = GETPOST('ref');
$confirm = GETPOST('confirm');
$prodattr = new ProductAttribute($db);
$prodattrval = new ProductAttributeValue($db);
if ($prodattr->fetch($id) < 1) {
dol_print_error($db, $langs->trans('ErrorRecordNotFound'));
die;
}
if ($_POST) {
if ($action == 'edit') {
$prodattr->label = $label;
$prodattr->ref = $ref;
if ($prodattr->update() < 1) {
setEventMessage($langs->trans('CoreErrorMessage'), 'errors');
} else {
setEventMessage($langs->trans('RecordSaved'));
header('Location: '.dol_buildpath('/attributes/card.php?id='.$id, 2));
die;
}
} elseif ($action == 'edit_value') {
if ($prodattrval->fetch($valueid) > 0) {
$prodattrval->ref = $ref;
$prodattrval->value = GETPOST('value');
if ($prodattrval->update() > 0) {
setEventMessage($langs->trans('RecordSaved'));
} else {
setEventMessage($langs->trans('CoreErrorMessage'), 'errors');
}
}
header('Location: '.dol_buildpath('/attributes/card.php?id='.$prodattr->id, 2));
die;
}
}
if ($confirm == 'yes') {
if ($action == 'confirm_delete') {
$db->begin();
$res = $prodattrval->deleteByFkAttribute($prodattr->id);
if ($res < 1 || ($prodattr->delete() < 1)) {
$db->rollback();
setEventMessage($langs->trans('CoreErrorMessage'), 'errors');
header('Location: '.dol_buildpath('/attributes/card.php?id='.$prodattr->id, 2));
} else {
$db->commit();
setEventMessage($langs->trans('RecordSaved'));
header('Location: '.dol_buildpath('/attributes/list.php', 2));
}
die;
} elseif ($action == 'confirm_deletevalue') {
if ($prodattrval->fetch($valueid) > 0) {
if ($prodattrval->delete() < 1) {
setEventMessage($langs->trans('CoreErrorMessage'), 'errors');
} else {
setEventMessage($langs->trans('RecordSaved'));
}
header('Location: '.dol_buildpath('/attributes/card.php?id='.$prodattr->id, 2));
die;
}
}
}
$langs->load('products');
$title = $langs->trans('ProductAttributeName', dol_htmlentities($prodattr->label));
$var = false;
llxHeader('', $title);
print_fiche_titre($title);
dol_fiche_head();
if ($action == 'edit') {
print '<form method="post">';
}
?>
<table class="border" style="width: 100%">
<tr>
<td style="width: 15%" class="fieldrequired"><?php echo $langs->trans('Ref') ?></td>
<td>
<?php if ($action == 'edit') {
print '<input type="text" name="ref" value="'.$prodattr->ref.'">';
} else {
print dol_htmlentities($prodattr->ref);
} ?>
</td>
</tr>
<tr>
<td style="width: 15%" class="fieldrequired"><?php echo $langs->trans('Label') ?></td>
<td>
<?php if ($action == 'edit') {
print '<input type="text" name="label" value="'.$prodattr->label.'">';
} else {
print dol_htmlentities($prodattr->label);
} ?>
</td>
</tr>
</table>
<?php
dol_fiche_end();
if ($action == 'edit') { ?>
<div style="text-align: center;">
<div class="inline-block divButAction">
<input type="submit" class="button" value="<?php echo $langs->trans('Save') ?>">
<a href="card.php?id=<?php echo $prodattr->id ?>" class="butAction"><?php echo $langs->trans('Cancel') ?></a>
</div>
</div></form>
<?php } else {
if ($action == 'delete') {
$form = new Form($db);
print $form->formconfirm(
"card.php?id=".$prodattr->id,
$langs->trans('Delete'),
$langs->trans('ProductAttributeDeleteDialog'),
"confirm_delete",
'',
0,
1
);
} elseif ($action == 'delete_value') {
if ($prodattrval->fetch($valueid) > 0) {
$form = new Form($db);
print $form->formconfirm(
"card.php?id=".$prodattr->id."&valueid=".$prodattrval->id,
$langs->trans('Delete'),
$langs->trans('ProductAttributeValueDeleteDialog', dol_htmlentities($prodattrval->value), dol_htmlentities($prodattrval->ref)),
"confirm_deletevalue",
'',
0,
1
);
}
}
?>
<div class="tabsAction">
<div class="inline-block divButAction">
<a href="card.php?id=<?php echo $prodattr->id ?>&action=edit" class="butAction"><?php echo $langs->trans('Modify') ?></a>
<a href="card.php?id=<?php echo $prodattr->id ?>&action=delete" class="butAction"><?php echo $langs->trans('Delete') ?></a>
</div>
</div>
<?php if ($action == 'edit_value'): ?>
<form method="post">
<?php endif ?>
<table class="liste">
<tr class="liste_titre">
<th class="liste_titre"><?php echo $langs->trans('Ref') ?></th>
<th class="liste_titre"><?php echo $langs->trans('Value') ?></th>
<th class="liste_titre"></th>
</tr>
<?php foreach ($prodattrval->fetchAllByProductAttribute($prodattr->id) as $attrval): ?>
<tr <?php echo $bc[!$var] ?>>
<?php if ($action == 'edit_value' && ($valueid == $attrval->id)): ?>
<td><input type="text" name="ref" value="<?php echo $attrval->ref ?>"></td>
<td><input type="text" name="value" value="<?php echo $attrval->value ?>"></td>
<td style="text-align: right">
<input type="submit" value="<?php echo $langs->trans('Save') ?>" class="button">
<a href="card.php?id=<?php echo $prodattr->id ?>" class="butAction"><?php echo $langs->trans('Cancel') ?></a>
</td>
<?php else: ?>
<td><?php echo dol_htmlentities($attrval->ref) ?></td>
<td><?php echo dol_htmlentities($attrval->value) ?></td>
<td style="text-align: right">
<a href="card.php?id=<?php echo $prodattr->id ?>&action=edit_value&valueid=<?php echo $attrval->id ?>"><?php echo img_edit() ?></a>
<a href="card.php?id=<?php echo $prodattr->id ?>&action=delete_value&valueid=<?php echo $attrval->id ?>"><?php echo img_delete() ?></a>
</td>
<?php endif; ?>
</tr>
<?php
$var = !$var;
endforeach
?>
</table>
<?php if ($action == 'edit_value'): ?>
</form>
<?php endif ?>
<div class="tabsAction">
<div class="inline-block divButAction">
<a href="create_val.php?id=<?php echo $prodattr->id ?>" class="butAction"><?php echo $langs->trans('Create') ?></a>
</div>
</div>
<?php
}
llxFooter();

View File

@@ -0,0 +1,317 @@
<?php
/* Copyright (C) 2016 Marcos García <marcosgdf@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
class ProductAttribute
{
/**
* Database handler
* @var DoliDB
*/
private $db;
/**
* Id of the product attribute
* @var int
*/
public $id;
/**
* Ref of the product attribute
* @var
*/
public $ref;
/**
* Label of the product attribute
* @var string
*/
public $label;
/**
* Order of attribute.
* Lower ones will be shown first and higher ones last
* @var int
*/
public $rang;
public function __construct(DoliDB $db)
{
global $conf;
$this->db = $db;
$this->entity = $conf->entity;
}
/**
* Fetches the properties of a product attribute
*
* @param int $id Attribute id
* @return int <1 KO, >1 OK
*/
public function fetch($id)
{
if (!$id) {
return -1;
}
require_once __DIR__.'/../lib/product_attributes.lib.php';
$sql = "SELECT rowid, ref, label, rang FROM ".MAIN_DB_PREFIX."product_attribute WHERE rowid = ".(int) $id." AND entity IN (".getProductEntities($this->db).")";
$query = $this->db->query($sql);
if (!$this->db->num_rows($query)) {
return -1;
}
$result = $this->db->fetch_object($query);
$this->id = $result->rowid;
$this->ref = $result->ref;
$this->label = $result->label;
$this->rang = $result->rang;
return 1;
}
/**
* Returns an array of all product attributes
*
* @return ProductAttribute[]
*/
public function fetchAll()
{
require_once __DIR__.'/../lib/product_attributes.lib.php';
$return = array();
$sql = 'SELECT rowid, ref, label, rang FROM '.MAIN_DB_PREFIX."product_attribute WHERE entity IN (".getProductEntities($this->db).')';
$sql .= $this->db->order('rang', 'asc');
$query = $this->db->query($sql);
while ($result = $this->db->fetch_object($query)) {
$tmp = new ProductAttribute($this->db);
$tmp->id = $result->rowid;
$tmp->ref = $result->ref;
$tmp->label = $result->label;
$tmp->rang = $result->rang;
$return[] = $tmp;
}
return $return;
}
/**
* Creates a product attribute
*
* @return int <0 KO, >0 OK
*/
public function create()
{
//Ref must be uppercase
$this->ref = strtoupper($this->ref);
$sql = "INSERT INTO ".MAIN_DB_PREFIX."product_attribute (ref, label, entity, rang)
VALUES ('".$this->db->escape($this->ref)."', '".$this->db->escape($this->label)."', ".(int) $this->entity.", ".(int) $this->rang.")";
$query = $this->db->query($sql);
if ($query) {
$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'product_attribute');
return 1;
}
return -1;
}
/**
* Updates a product attribute
*
* @return int <0 KO, >0 OK
*/
public function update()
{
//Ref must be uppercase
$this->ref = strtoupper($this->ref);
$sql = "UPDATE ".MAIN_DB_PREFIX."product_attribute SET ref = '".$this->db->escape($this->ref)."', label = '".$this->db->escape($this->label)."', rang = ".(int) $this->rang." WHERE rowid = ".(int) $this->id;
if ($this->db->query($sql)) {
return 1;
}
return -1;
}
/**
* Deletes a product attribute
*
* @return int <0 KO, >0 OK
*/
public function delete()
{
$sql = "DELETE FROM ".MAIN_DB_PREFIX."product_attribute WHERE rowid = ".(int) $this->id;
if ($this->db->query($sql)) {
return 1;
}
return -1;
}
/**
* Returns the number of products that are using this attribute
*
* @return int
*/
public function countChildProducts()
{
require_once __DIR__.'/../lib/product_attributes.lib.php';
$sql = "SELECT COUNT(*) count FROM ".MAIN_DB_PREFIX."product_attribute_combination2val pac2v
LEFT JOIN ".MAIN_DB_PREFIX."product_attribute_combination pac ON pac2v.fk_prod_combination = pac.rowid WHERE pac2v.fk_prod_attr = ".(int) $this->id." AND pac.entity IN (".getProductEntities($this->db).")";
$query = $this->db->query($sql);
$result = $this->db->fetch_object($query);
return $result->count;
}
/**
* Reorders the order of the attributes.
* This is an internal function used by moveLine function
*
* @return int <0 KO >0 OK
*/
protected function reorderLines()
{
$tmp_order = array();
$sql = 'SELECT rowid FROM '.MAIN_DB_PREFIX.'product_attribute WHERE rang = 0';
$sql .= $this->db->order('rang, rowid', 'asc');
$query = $this->db->query($sql);
if (!$query) {
return -1;
}
while ($result = $this->db->fetch_object($query)) {
$tmp_order[] = $result->rowid;
}
foreach ($tmp_order as $order => $rowid) {
$tmp = new ProductAttribute($this->db);
$tmp->fetch($rowid);
$tmp->rang = $order+1;
if ($tmp->update() < 0) {
return -1;
}
}
return 1;
}
/**
* Internal function to handle moveUp and moveDown functions
*
* @param string $type up/down
* @return int <0 KO >0 OK
*/
private function moveLine($type)
{
if ($this->reorderLines() < 0) {
return -1;
}
$this->db->begin();
if ($type == 'up') {
$newrang = $this->rang - 1;
} else {
$newrang = $this->rang + 1;
}
$sql = 'UPDATE '.MAIN_DB_PREFIX.'product_attribute SET rang = '.$this->rang.' WHERE rang = '.$newrang;
if (!$this->db->query($sql)) {
$this->db->rollback();
return -1;
}
$this->rang = $newrang;
if ($this->update() < 0) {
$this->db->rollback();
return -1;
}
$this->db->commit();
return 1;
}
/**
* Shows this attribute before others
*
* @return int <0 KO >0 OK
*/
public function moveUp()
{
return $this->moveLine('up');
}
/**
* Shows this attribute after others
*
* @return int <0 KO >0 OK
*/
public function moveDown()
{
return $this->moveLine('down');
}
/**
* Updates the order of all attributes. Used by AJAX page for drag&drop
*
* @param DoliDB $db Database handler
* @param array $order Array with row id ordered in ascendent mode
* @return int <0 KO >0 OK
*/
public static function bulkUpdateOrder(DoliDB $db, array $order)
{
$tmp = new ProductAttribute($db);
foreach ($order as $key => $attrid) {
if ($tmp->fetch($attrid) < 0) {
return -1;
}
$tmp->rang = $key;
if ($tmp->update() < 0) {
return -1;
}
}
return 1;
}
}

View File

@@ -0,0 +1,221 @@
<?php
/* Copyright (C) 2016 Marcos García <marcosgdf@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
class ProductAttributeValue
{
/**
* Database handler
* @var DoliDB
*/
private $db;
/**
* Attribute value id
* @var int
*/
public $id;
/**
* Product attribute id
* @var int
*/
public $fk_product_attribute;
/**
* Attribute value ref
* @var string
*/
public $ref;
/**
* Attribute value value
* @var string
*/
public $value;
public function __construct(DoliDB $db)
{
global $conf;
$this->db = $db;
$this->entity = $conf->entity;
}
/**
* Gets a product attribute value
*
* @param int $valueid Product attribute value id
* @return int <0 KO, >0 OK
*/
public function fetch($valueid)
{
require_once __DIR__.'/../lib/product_attributes.lib.php';
$sql = "SELECT rowid, fk_product_attribute, ref, value FROM ".MAIN_DB_PREFIX."product_attribute_value WHERE rowid = ".(int) $valueid." AND entity IN (".getProductEntities($this->db).")";
$query = $this->db->query($sql);
if (!$query) {
return -1;
}
if (!$this->db->num_rows($query)) {
return -1;
}
$result = $this->db->fetch_object($query);
$this->id = $result->rowid;
$this->fk_product_attribute = $result->fk_product_attribute;
$this->ref = $result->ref;
$this->value = $result->value;
return 1;
}
/**
* Returns all product attribute values of a product attribute
*
* @param int $prodattr_id Product attribute id
* @param bool $only_used Fetch only used attribute values
* @return ProductAttributeValue[]
*/
public function fetchAllByProductAttribute($prodattr_id, $only_used = false)
{
require_once __DIR__.'/../lib/product_attributes.lib.php';
$return = array();
$sql = 'SELECT ';
if ($only_used) {
$sql .= 'DISTINCT ';
}
$sql .= 'v.fk_product_attribute, v.rowid, v.ref, v.value FROM '.MAIN_DB_PREFIX.'product_attribute_value v ';
if ($only_used) {
$sql .= 'LEFT JOIN '.MAIN_DB_PREFIX.'product_attribute_combination2val c2v ON c2v.fk_prod_attr_val = v.rowid ';
$sql .= 'LEFT JOIN '.MAIN_DB_PREFIX.'product_attribute_combination c ON c.rowid = c2v.fk_prod_combination ';
$sql .= 'LEFT JOIN '.MAIN_DB_PREFIX.'product p ON p.rowid = c.fk_product_child ';
}
$sql .= 'WHERE v.fk_product_attribute = '.(int) $prodattr_id;
if ($only_used) {
$sql .= ' AND c2v.rowid IS NOT NULL AND p.tosell = 1';
}
$query = $this->db->query($sql);
while ($result = $this->db->fetch_object($query)) {
$tmp = new ProductAttributeValue($this->db);
$tmp->fk_product_attribute = $result->fk_product_attribute;
$tmp->id = $result->rowid;
$tmp->ref = $result->ref;
$tmp->value = $result->value;
$return[] = $tmp;
}
return $return;
}
/**
* Creates a value for a product attribute
*
* @return int <0 KO >0 OK
*/
public function create()
{
if (!$this->fk_product_attribute) {
return -1;
}
//Ref must be uppercase
$this->ref = strtoupper($this->ref);
$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.")";
$query = $this->db->query($sql);
if ($query) {
$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'product_attribute_value');
return 1;
}
return -1;
}
/**
* Updates a product attribute value
*
* @return int
*/
public function update()
{
//Ref must be uppercase
$this->ref = strtoupper($this->ref);
$sql = "UPDATE ".MAIN_DB_PREFIX."product_attribute_value
SET fk_product_attribute = '".(int) $this->fk_product_attribute."', ref = '".$this->db->escape($this->ref)."',
value = '".$this->db->escape($this->value)."' WHERE rowid = ".(int) $this->id;
if ($this->db->query($sql)) {
return 1;
}
return -1;
}
/**
* Deletes a product attribute value
*
* @return int <0 KO, >0 OK
*/
public function delete()
{
$sql = "DELETE FROM ".MAIN_DB_PREFIX."product_attribute_value WHERE rowid = ".(int) $this->id;
if ($this->db->query($sql)) {
return 1;
}
return -1;
}
/**
* Deletes all product attribute values by a product attribute id
*
* @param int $fk_attribute Product attribute id
* @return int <0 KO, >0 OK
*/
public function deleteByFkAttribute($fk_attribute)
{
$sql = "DELETE FROM ".MAIN_DB_PREFIX."product_attribute_value WHERE fk_product_attribute = ".(int) $fk_attribute;
if ($this->db->query($sql)) {
return 1;
}
return -1;
}
}

View File

@@ -0,0 +1,641 @@
<?php
/* Copyright (C) 2016 Marcos García <marcosgdf@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
class ProductCombination
{
/**
* Database handler
* @var DoliDB
*/
private $db;
/**
* Rowid of combination
* @var int
*/
public $id;
/**
* Rowid of parent product
* @var int
*/
public $fk_product_parent;
/**
* Rowid of child product
* @var int
*/
public $fk_product_child;
/**
* Price variation
* @var float
*/
public $variation_price;
/**
* Is the price variation a relative variation?
* @var bool
*/
public $variation_price_percentage = false;
/**
* Weight variation
* @var float
*/
public $variation_weight;
/**
* Combination entity
* @var int
*/
public $entity;
public function __construct(DoliDB $db)
{
global $conf;
$this->db = $db;
$this->entity = $conf->entity;
}
/**
* Retrieves a combination by its rowid
*
* @param int $rowid Row id
* @return int <0 KO, >0 OK
*/
public function fetch($rowid)
{
require_once __DIR__.'/../lib/product_attributes.lib.php';
$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 (".getProductEntities($this->db).")";
$query = $this->db->query($sql);
if (!$query) {
return -1;
}
if (!$this->db->num_rows($query)) {
return -1;
}
$result = $this->db->fetch_object($query);
$this->id = $result->rowid;
$this->fk_product_parent = $result->fk_product_parent;
$this->fk_product_child = $result->fk_product_child;
$this->variation_price = $result->variation_price;
$this->variation_price_percentage = $result->variation_price_percentage;
$this->variation_weight = $result->variation_weight;
return 1;
}
/**
* Retrieves a product combination by a child product row id
*
* @param int $fk_child Product row id
* @return int <0 KO, >0 OK
*/
public function fetchByFkProductChild($fk_child)
{
require_once __DIR__.'/../lib/product_attributes.lib.php';
$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 (".getProductEntities($this->db).")";
$query = $this->db->query($sql);
if (!$query) {
return -1;
}
if (!$this->db->num_rows($query)) {
return -1;
}
$result = $this->db->fetch_object($query);
$this->id = $result->rowid;
$this->fk_product_parent = $result->fk_product_parent;
$this->fk_product_child = $result->fk_product_child;
$this->variation_price = $result->variation_price;
$this->variation_price_percentage = $result->variation_price_percentage;
$this->variation_weight = $result->variation_weight;
return 1;
}
/**
* Retrieves all product combinations by the product parent row id
*
* @param int $fk_product_parent Rowid of parent product
* @return int|ProductCombination[] <0 KO
*/
public function fetchAllByFkProductParent($fk_product_parent)
{
require_once __DIR__.'/../lib/product_attributes.lib.php';
$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 (".getProductEntities($this->db).")";
$query = $this->db->query($sql);
if (!$query) {
return -1;
}
$return = array();
while ($result = $this->db->fetch_object($query)) {
$tmp = new ProductCombination($this->db);
$tmp->id = $result->rowid;
$tmp->fk_product_parent = $result->fk_product_parent;
$tmp->fk_product_child = $result->fk_product_child;
$tmp->variation_price = $result->variation_price;
$tmp->variation_price_percentage = $result->variation_price_percentage;
$tmp->variation_weight = $result->variation_weight;
$return[] = $tmp;
}
return $return;
}
/**
* Creates a product attribute combination
*
* @return int
*/
public function create()
{
$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.",
".(float) $this->variation_price.", ".(int) $this->variation_price_percentage.",
".(float) $this->variation_weight.", ".(int) $this->entity.")";
$query = $this->db->query($sql);
if (!$query) {
return -1;
}
$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'product_attribute_combination');
return 1;
}
/**
* Updates a product combination
*
* @return int <0 KO, >0 OK
*/
public function update()
{
$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.",
variation_weight = ".(float) $this->variation_weight." WHERE rowid = ".(int) $this->id;
$query = $this->db->query($sql);
if (!$query) {
return -1;
}
$parent = new Product($this->db);
$parent->fetch($this->fk_product_parent);
$this->updateProperties($parent);
return 1;
}
/**
* Deletes a product combination
*
* @return int <0 KO >0 OK
*/
public function delete()
{
$this->db->begin();
$comb2val = new ProductCombination2ValuePair($this->db);
$comb2val->deleteByFkCombination($this->id);
$sql = "DELETE FROM ".MAIN_DB_PREFIX."product_attribute_combination WHERE rowid = ".(int) $this->id;
if ($this->db->query($sql)) {
$this->db->commit();
return 1;
}
$this->db->rollback();
return -1;
}
/**
* Deletes all product combinations of a parent product
*
* @param int $fk_product_parent Rowid of parent product
* @return int <0 KO >0 OK
*/
public function deleteByFkProductParent($fk_product_parent)
{
$this->db->begin();
foreach ($this->fetchAllByFkProductParent($fk_product_parent) as $prodcomb) {
$prodstatic = new Product($this->db);
$res = $prodstatic->fetch($prodcomb->fk_product_child);
if ($res > 0) {
$res = $prodcomb->delete();
}
if ($res > 0 && !$prodstatic->isObjectUsed($prodstatic->id)) {
$res = $prodstatic->delete();
}
if ($res < 0) {
$this->db->rollback();
return -1;
}
}
$this->db->commit();
return 1;
}
/**
* Updates the weight of the child product. The price must be updated using Product::updatePrices
*
* @param Product $parent Parent product
* @return int >0 OK <0 KO
*/
public function updateProperties(Product $parent)
{
global $user, $conf;
$this->db->begin();
$child = new Product($this->db);
$child->fetch($this->fk_product_child);
$child->price_autogen = $parent->price_autogen;
$child->weight = $parent->weight + $this->variation_weight;
$child->weight_units = $parent->weight_units;
if ($child->update($child->id, $user) > 0) {
$new_vat = $parent->tva_tx;
$new_npr = $parent->tva_npr;
// MultiPrix
if (! empty($conf->global->PRODUIT_MULTIPRICES)) {
$new_type = $parent->multiprices_base_type[1];
$new_min_price = $parent->multiprices_min[1];
$new_psq = $parent->multiprices_recuperableonly[1];
if ($new_type == 'TTC') {
$new_price = $parent->multiprices_ttc[1];
} else {
$new_price = $parent->multiprices[1];
}
} else {
$new_type = $parent->price_base_type;
$new_min_price = $parent->price_min;
$new_psq = $parent->price_by_qty;
if ($new_type == 'TTC') {
$new_price = $parent->price_ttc;
} else {
$new_price = $parent->price;
}
}
if ($this->variation_price_percentage) {
$new_price *= 1 + ($this->variation_price/100);
} else {
$new_price += $this->variation_price;
}
$child->updatePrice($new_price, $new_type, $user, $new_vat, $new_min_price, 1, $new_npr, $new_psq);
$this->db->commit();
return 1;
}
$this->db->rollback();
return -1;
}
/**
* Retrieves the combination that matches the given features.
*
* @param int $prodid Id of parent product
* @param array $features Format: [$attr] => $attr_val
* @return false|ProductCombination false if not found
*/
public function fetchByProductCombination2ValuePairs($prodid, array $features)
{
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductCombination2ValuePair.class.php';
$actual_comp = array();
$prodcomb2val = new ProductCombination2ValuePair($this->db);
$prodcomb = new ProductCombination($this->db);
foreach ($features as $attr => $attr_val) {
$actual_comp[$attr] = $attr_val;
}
foreach ($prodcomb->fetchAllByFkProductParent($prodid) as $prc) {
$values = array();
foreach ($prodcomb2val->fetchByFkCombination($prc->id) as $value) {
$values[$value->fk_prod_attr] = $value->fk_prod_attr_val;
}
$check1 = count(array_diff_assoc($values, $actual_comp));
$check2 = count(array_diff_assoc($actual_comp, $values));
if (!$check1 && !$check2) {
return $prc;
}
}
return false;
}
/**
* Retrieves all unique attributres for a parent product
*
* @param int $productid Product rowid
* @return ProductAttribute[]
*/
public function getUniqueAttributesAndValuesByFkProductParent($productid)
{
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductAttribute.class.php';
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductAttributeValue.class.php';
$attributes = array();
//Attributes
$sql = "SELECT DISTINCT fk_prod_attr, a.rang
FROM ".MAIN_DB_PREFIX."product_attribute_combination2val c2v LEFT JOIN ".MAIN_DB_PREFIX."product_attribute_combination c
ON c2v.fk_prod_combination = c.rowid
LEFT JOIN ".MAIN_DB_PREFIX."product p ON p.rowid = c.fk_product_child
LEFT JOIN ".MAIN_DB_PREFIX."product_attribute a ON a.rowid = fk_prod_attr
WHERE c.fk_product_parent = ".(int) $productid." AND p.tosell = 1";
$sql .= $this->db->order('a.rang', 'asc');
$query = $this->db->query($sql);
//Values
while ($result = $this->db->fetch_object($query)) {
$attr = new ProductAttribute($this->db);
$attr->fetch($result->fk_prod_attr);
$tmp = new stdClass();
$tmp->id = $attr->id;
$tmp->ref = $attr->ref;
$tmp->label = $attr->label;
$tmp->values = array();
$attrval = new ProductAttributeValue($this->db);
foreach ($res = $attrval->fetchAllByProductAttribute($attr->id, true) as $val) {
$tmp->values[] = $val;
}
$attributes[] = $tmp;
}
return $attributes;
}
/**
* Creates a product combination. Check usages to find more about its use
*
* Format of $combinations array:
* array(
* 0 => array(
* attr => value,
* attr2 => value
* [...]
* ),
* [...]
* )
*
* @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|float $forced_pricevar If the price variation is forced
* @param bool|float $forced_weightvar If the weight variation is forced
* @return int <0 KO, >0 OK
*/
public static function createProductCombination(Product $product, array $combinations, array $variations, $price_var_percent = false, $forced_pricevar = false, $forced_weightvar = false)
{
global $db, $user;
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductAttribute.class.php';
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductAttributeValue.class.php';
$db->begin();
$newproduct = clone $product;
//Final weight impact
$weight_impact = $forced_weightvar;
if ($forced_weightvar === false) {
$weight_impact = 0;
}
//Final price impact
$price_impact = $forced_pricevar;
if ($forced_pricevar === false) {
$price_impact = 0;
}
$newcomb = new ProductCombination($db);
$existingCombination = $newcomb->fetchByProductCombination2ValuePairs($product->id, $combinations);
if ($existingCombination) {
$newcomb = $existingCombination;
} else {
$newcomb->fk_product_parent = $product->id;
if ($newcomb->create() < 0) {
$db->rollback();
return -1;
}
}
$prodattr = new ProductAttribute($db);
$prodattrval = new ProductAttributeValue($db);
foreach ($combinations as $currcombattr => $currcombval) {
//This was checked earlier, so no need to double check
$prodattr->fetch($currcombattr);
$prodattrval->fetch($currcombval);
//If there is an existing combination, there is no need to duplicate the valuepair
if (!$existingCombination) {
$tmp = new ProductCombination2ValuePair($db);
$tmp->fk_prod_attr = $currcombattr;
$tmp->fk_prod_attr_val = $currcombval;
$tmp->fk_prod_combination = $newcomb->id;
if ($tmp->create() < 0) {
$db->rollback();
return -1;
}
}
if ($forced_weightvar === false) {
$weight_impact += (float) price2num($variations[$currcombattr][$currcombval]['weight']);
}
if ($forced_pricevar === false) {
$price_impact += (float) price2num($variations[$currcombattr][$currcombval]['price']);
}
$newproduct->ref .= '_'.$prodattrval->ref;
//The first one should not contain a linebreak
if ($newproduct->description) {
$newproduct->description .= '<br>';
}
$newproduct->description .= '<strong>'.$prodattr->label.':</strong> '.$prodattrval->value;
}
$newcomb->variation_price_percentage = $price_var_percent;
$newcomb->variation_price = $price_impact;
$newcomb->variation_weight = $weight_impact;
$newproduct->weight += $weight_impact;
//To avoid wrong information in price history log
$newproduct->price = 0;
$newproduct->price_ttc = 0;
$newproduct->price_min = 0;
$newproduct->price_min_ttc = 0;
if ($newproduct->create($user) < 0) {
//In case the error is not related with an already existing product
if ($newproduct->error != 'ErrorProductAlreadyExists') {
$db->rollback();
return -1;
}
/**
* If there is an existing combination, then we update the prices and weight
* Otherwise, we try adding a random number to the ref
*/
if ($newcomb->fk_product_child) {
$res = $newproduct->fetch($existingCombination->fk_product_child);
} else {
$orig_prod_ref = $newproduct->ref;
$i = 1;
do {
$newproduct->ref = $orig_prod_ref.$i;
$res = $newproduct->create($user);
if ($newproduct->error != 'ErrorProductAlreadyExists') {
break;
}
$i++;
} while ($res < 0);
}
if ($res < 0) {
$db->rollback();
return -1;
}
$newproduct->weight += $weight_impact;
}
$newcomb->fk_product_child = $newproduct->id;
if ($newcomb->update() < 0) {
$db->rollback();
return -1;
}
$db->commit();
return 1;
}
/**
* Copies all product combinations from the origin product to the destination product
*
* @param int $origProductId Origin product id
* @param Product $destProduct Destination product
* @return int >0 OK <0 KO
*/
public function copyAll($origProductId, Product $destProduct)
{
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductCombination2ValuePair.class.php';
//To prevent a loop
if ($origProductId == $destProduct->id) {
return -1;
}
$prodcomb = new ProductCombination($this->db);
$prodcomb2val = new ProductCombination2ValuePair($this->db);
//Retrieve all product combinations
$combinations = $prodcomb->fetchAllByFkProductParent($origProductId);
foreach ($combinations as $combination) {
$variations = array();
foreach ($prodcomb2val->fetchByFkCombination($combination->id) as $tmp_pc2v) {
$variations[$tmp_pc2v->fk_prod_attr] = $tmp_pc2v->fk_prod_attr_val;
}
if (self::createProductCombination(
$destProduct,
$variations,
array(),
$combination->variation_price_percentage,
$combination->variation_price,
$combination->variation_weight
) < 0) {
return -1;
}
}
return 1;
}
}

View File

@@ -0,0 +1,151 @@
<?php
/* Copyright (C) 2016 Marcos García <marcosgdf@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
class ProductCombination2ValuePair
{
/**
* Database handler
* @var DoliDB
*/
private $db;
/**
* Combination 2 value pair id
* @var int
*/
public $id;
/**
* Product combination id
* @var int
*/
public $fk_prod_combination;
/**
* Product attribute id
* @var int
*/
public $fk_prod_attr;
/**
* Product attribute value id
* @var int
*/
public $fk_prod_attr_val;
public function __construct(DoliDB $db)
{
$this->db = $db;
}
/**
* Translates this class to a human-readable string
*
* @return string
*/
public function __toString()
{
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductAttributeValue.class.php';
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductAttribute.class.php';
$prodattr = new ProductAttribute($this->db);
$prodattrval = new ProductAttributeValue($this->db);
$prodattr->fetch($this->fk_prod_attr);
$prodattrval->fetch($this->fk_prod_attr_val);
return $prodattr->label.': '.$prodattrval->value;
}
/**
* Creates a product combination 2 value pair
* @return int <0 KO, >0 OK
*/
public function create()
{
$sql = "INSERT INTO ".MAIN_DB_PREFIX."product_attribute_combination2val
(fk_prod_combination, fk_prod_attr, fk_prod_attr_val)
VALUES(".(int) $this->fk_prod_combination.", ".(int) $this->fk_prod_attr.", ".(int) $this->fk_prod_attr_val.")";
$query = $this->db->query($sql);
if ($query) {
$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'product_attribute_combination2val');
return 1;
}
return -1;
}
/**
* Retrieves a product combination 2 value pair from its rowid
*
* @param int $fk_combination Fk combination to search
* @return int|ProductCombination2ValuePair[] -1 if KO
*/
public function fetchByFkCombination($fk_combination)
{
$sql = "SELECT
c.rowid,
c2v.fk_prod_attr_val,
c2v.fk_prod_attr,
c2v.fk_prod_combination
FROM ".MAIN_DB_PREFIX."product_attribute c LEFT JOIN ".MAIN_DB_PREFIX."product_attribute_combination2val c2v ON c.rowid = c2v.fk_prod_attr
WHERE c2v.fk_prod_combination = ".(int) $fk_combination;
$sql .= $this->db->order('c.rang', 'asc');
$query = $this->db->query($sql);
if (!$query) {
return -1;
}
$return = array();
while ($result = $this->db->fetch_object($query)) {
$tmp = new ProductCombination2ValuePair($this->db);
$tmp->fk_prod_attr_val = $result->fk_prod_attr_val;
$tmp->fk_prod_attr = $result->fk_prod_attr;
$tmp->fk_prod_combination = $result->fk_prod_combination;
$tmp->id = $result->rowid;
$return[] = $tmp;
}
return $return;
}
/**
* Deletes a product combination 2 value pair
*
* @param int $fk_combination Rowid of the combination
* @return int >0 OK <0 KO
*/
public function deleteByFkCombination($fk_combination)
{
$sql = "DELETE FROM ".MAIN_DB_PREFIX."product_attribute_combination2val WHERE fk_prod_combination = ".(int) $fk_combination;
if ($this->db->query($sql)) {
return 1;
}
return -1;
}
}

View File

View File

@@ -0,0 +1,641 @@
<?php
/* Copyright (C) 2016 Marcos García <marcosgdf@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
require '../main.inc.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductAttribute.class.php';
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductAttributeValue.class.php';
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductCombination.class.php';
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductCombination2ValuePair.class.php';
$langs->load("products");
$langs->load("other");
$var = false;
$id = GETPOST('id', 'int');
$valueid = GETPOST('valueid', 'int');
$ref = GETPOST('ref');
$weight_impact = (float) GETPOST('weight_impact');
$price_impact = (float) GETPOST('price_impact');
$price_impact_percent = (bool) GETPOST('price_impact_percent');
$form = new Form($db);
$action = GETPOST('action');
// Security check
$fieldvalue = (! empty($id) ? $id : $ref);
$fieldtype = (! empty($ref) ? 'ref' : 'rowid');
$result=restrictedArea($user,'produit|service',$fieldvalue,'product&product','','',$fieldtype);
$prodstatic = new Product($db);
$prodattr = new ProductAttribute($db);
$prodattr_val = new ProductAttributeValue($db);
$product = new Product($db);
$product->fetch($id);
if (!$product->isProduct()) {
header('Location: '.dol_buildpath('/product/card.php?id='.$product->id, 2));
die;
}
$prodcomb = new ProductCombination($db);
$prodcomb2val = new ProductCombination2ValuePair($db);
$productCombination2ValuePairs1 = array();
if ($_POST) {
if ($action == 'add') {
$features = GETPOST('features', 'array');
if (!$features) {
setEventMessage($langs->trans('ErrorFieldsRequired'), 'errors');
} else {
$weight_impact = price2num($weight_impact);
$price_impact = price2num($price_impact);
$sanit_features = array();
//First, sanitize
foreach ($features as $feature) {
$explode = explode(':', $feature);
if ($prodattr->fetch($explode[0]) < 0) {
continue;
}
if ($prodattr_val->fetch($explode[1]) < 0) {
continue;
}
//Valuepair
$sanit_features[$explode[0]] = $explode[1];
$tmp = new ProductCombination2ValuePair($db);
$tmp->fk_prod_attr = $explode[0];
$tmp->fk_prod_attr_val = $explode[1];
$productCombination2ValuePairs1[] = $tmp;
}
$db->begin();
if (!$prodcomb->fetchByProductCombination2ValuePairs($id, $sanit_features)) {
if (ProductCombination::createProductCombination($product, $sanit_features, array(), $price_impact_percent, $price_impact, $weight_impact)) {
$db->commit();
setEventMessage($langs->trans('RecordSaved'));
header('Location: '.dol_buildpath('/attributes/combinations.php?id='.$id, 2));
die;
} else {
setEventMessage($langs->trans('CoreErrorMessage'), 'errors');
}
} else {
setEventMessage($langs->trans('ErrorRecordAlreadyExists'), 'errors');
}
$db->rollback();
}
} elseif ($action == 'bulk_actions') {
$prodarray = array_keys(GETPOST('select', 'array'));
$bulkaction = GETPOST('bulk_action');
$error = 0;
$prodstatic = new Product($db);
$db->begin();
foreach ($prodarray as $prodid) {
if ($prodstatic->fetch($prodid) < 0) {
continue;
}
if ($bulkaction == 'on_sell') {
$prodstatic->status = 1;
$res = $prodstatic->update($prodstatic->id, $user);
} elseif ($bulkaction == 'on_buy') {
$prodstatic->status_buy = 1;
$res = $prodstatic->update($prodstatic->id, $user);
} elseif ($bulkaction == 'not_sell') {
$prodstatic->status = 0;
$res = $prodstatic->update($prodstatic->id, $user);
} elseif ($bulkaction == 'not_buy') {
$prodstatic->status_buy = 0;
$res = $prodstatic->update($prodstatic->id, $user);
} elseif ($bulkaction == 'delete') {
$res = $prodstatic->delete($prodstatic->id);
} else {
break;
}
if ($res <= 0) {
$error++;
break;
}
}
if ($error) {
$db->rollback();
if ($prodstatic->error) {
setEventMessage($langs->trans($prodstatic->error), 'errors');
} else {
setEventMessage($langs->trans('CoreErrorMessage'), 'errors');
}
} else {
$db->commit();
setEventMessage($langs->trans('RecordSaved'));
}
} else {
if ($prodcomb->fetch($valueid) < 0) {
dol_print_error($db, $langs->trans('ErrorRecordNotFound'));
die;
}
$prodcomb->variation_price_percentage = $price_impact_percent;
$prodcomb->variation_price = $price_impact;
$prodcomb->variation_weight = $weight_impact;
if ($prodcomb->update() > 0) {
setEventMessage($langs->trans('RecordSaved'));
header('Location: '.dol_buildpath('/attributes/combinations.php?id='.$id, 2));
die;
} else {
setEventMessage($langs->trans('CoreErrorMessage'), 'errors');
}
}
}
$productCombinations = $prodcomb->fetchAllByFkProductParent($id);
if ($action === 'confirm_deletecombination') {
if ($prodcomb->fetch($valueid) > 0) {
$db->begin();
if ($prodcomb->delete() > 0 && $prodstatic->fetch($prodcomb->fk_product_child) > 0 && $prodstatic->delete() > 0) {
$db->commit();
setEventMessage($langs->trans('RecordSaved'));
header('Location: '.dol_buildpath('/attributes/combinations.php?id='.$product->id, 2));
die;
}
$db->rollback();
setEventMessage($langs->trans('ProductCombinationAlreadyUsed'), 'errors');
$action = '';
}
} elseif ($action === 'edit') {
if ($prodcomb->fetch($valueid) < 0) {
dol_print_error($db, $langs->trans('ErrorRecordNotFound'));
die;
}
$weight_impact = $prodcomb->variation_weight;
$price_impact = $prodcomb->variation_price;
$price_impact_percent = $prodcomb->variation_price_percentage;
$productCombination2ValuePairs1 = $prodcomb2val->fetchByFkCombination($valueid);
} elseif ($action === 'confirm_copycombination') {
//Check destination product
$dest_product = GETPOST('dest_product');
if ($prodstatic->fetch('', $dest_product) > 0) {
//To prevent from copying to the same product
if ($prodstatic->ref != $product->ref) {
if ($prodcomb->copyAll($product->id, $prodstatic) > 0) {
header('Location: '.dol_buildpath('/attributes/combinations.php?id='.$prodstatic->id, 2));
die;
} else {
setEventMessage($langs->trans('ErrorCopyProductCombinations'), 'errors');
}
}
} else {
setEventMessage($langs->trans('ErrorDestinationProductNotFound'), 'errors');
}
}
/*
* View
*/
if (! empty($id) || ! empty($ref)) {
$object = new Product($db);
$result = $object->fetch($id, $ref);
llxHeader("", "", $langs->trans("CardProduct".$object->type));
if ($result) {
$head = product_prepare_head($object);
$titre = $langs->trans("CardProduct".$object->type);
$picto = ($object->type == Product::TYPE_SERVICE ? 'service' : 'product');
dol_fiche_head($head, 'combinations', $titre, 0, $picto);
print '<table class="border" width="100%">';
// Reference
print '<tr>';
print '<td width="30%">'.$langs->trans("Ref").'</td><td colspan="3">';
print $form->showrefnav($object, 'id', '', 0);
print '</td>';
print '</tr>';
// Label
print '<tr><td>'.$langs->trans("Label").'</td><td colspan="3">'.$object->label.'</td></tr>';
// Status (to sell)
print '<tr><td>'.$langs->trans("Status").' ('.$langs->trans("Sell").')</td><td>';
print $object->getLibStatut(2, 0);
print '</td></tr>';
// Status (to buy)
print '<tr><td>'.$langs->trans("Status").' ('.$langs->trans("Buy").')</td><td>';
print $object->getLibStatut(2, 1);
print '</td></tr>';
print '</table>';
dol_fiche_end();
}
if ($action == 'add' || ($action == 'edit')) {
if ($action == 'add') {
$title = $langs->trans('NewProductCombination');
} else {
$title = $langs->trans('EditProductCombination');
}
print_fiche_titre($title);
if ($action == 'add') {
$prodattr_all = $prodattr->fetchAll();
if (!$selected) {
$selected = $prodattr_all[key($prodattr_all)]->id;
}
$prodattr_alljson = array();
foreach ($prodattr_all as $each) {
$prodattr_alljson[$each->id] = $each;
}
?>
<script>
attributes_available = <?php echo json_encode($prodattr_alljson) ?>;
attributes_selected = {
index: [],
info: []
};
<?php foreach ($productCombination2ValuePairs1 as $pc2v):
$prodattr_val->fetch($pc2v->fk_prod_attr_val);
?>
attributes_selected.index.push(<?php echo $pc2v->fk_prod_attr ?>);
attributes_selected.info[<?php echo $pc2v->fk_prod_attr ?>] = {
attribute: attributes_available[<?php echo $pc2v->fk_prod_attr ?>],
value: {
id: <?php echo $pc2v->fk_prod_attr_val ?>,
label: '<?php echo $prodattr_val->value ?>'
}
};
<?php endforeach ?>
restoreAttributes = function() {
jQuery("select[name=attribute]").empty().append('<option value=""></option>');
jQuery.each(attributes_available, function (key, val) {
if (jQuery.inArray(val.id, attributes_selected.index) == -1) {
jQuery("select[name=attribute]").append('<option value="' + val.id + '">' + val.label + '</option>');
}
});
};
paintAttributes = function() {
var select = jQuery("select#features");
select.empty();
jQuery("form#combinationform input[type=hidden]").detach();
jQuery.each(attributes_selected.index, function (key, val) {
var attr_info = attributes_selected.info[val];
var opt_key = val + ':' + attr_info.value.id;
var opt_label = attr_info.attribute.label + ': ' + attr_info.value.label;
//Add combination to the list
select.append('<option value="' + opt_key + '">' + opt_label + '</option>');
//Add hidden input to catch the new combination
jQuery("form#combinationform").append('<input type="hidden" name="features[]" value="' + opt_key + '">');
});
};
jQuery(document).ready(function() {
jQuery("select#attribute").change(function () {
var select = jQuery("select#value");
if (!jQuery(this).val().length) {
select.empty();
return;
}
select.empty().append('<option value="">Loading...</option>');
jQuery.getJSON("<?php echo dol_buildpath('/attributes/ajax/get_attribute_values.php', 2) ?>", {
id: jQuery(this).val()
}, function(data) {
if (data.error) {
jQuery("select#value").empty();
return alert(data.error);
}
select.empty();
jQuery(data).each(function (key, val) {
jQuery("select#value").append('<option value="' + val.id + '">' + val.value + '</option>');
});
});
});
jQuery("#addfeature").click(function () {
var selectedattr = jQuery("select[name=attribute] option:selected");
var selectedvalu = jQuery("select[name=value] option:selected");
if (!selectedattr.val().length || !selectedvalu.val().length) {
return;
}
var selectedattr_val = parseInt(selectedattr.val());
if (jQuery.inArray(selectedattr_val, attributes_selected.index) != -1) {
return;
}
attributes_selected.index.push(selectedattr_val);
attributes_selected.info[selectedattr_val] = {
attribute: attributes_available[selectedattr_val],
value: {
id: selectedvalu.val(),
label: selectedvalu.html()
}
};
paintAttributes();
selectedattr.detach();
jQuery("select[name=value] option").detach();
});
jQuery("#delfeature").click(function() {
jQuery("#features option:selected").each(function (key, val) {
var explode = jQuery(val).val().split(':');
var indexOf = attributes_selected.index.indexOf(parseInt(explode[0]));
if (indexOf != -1) {
attributes_selected.index.splice(indexOf, 1);
jQuery(attributes_selected.info[parseInt(explode[0])]).detach();
}
jQuery(val).detach();
});
restoreAttributes();
paintAttributes();
});
});
</script>
<?php } ?>
<form method="post" id="combinationform">
<table class="border" style="width: 100%">
<?php if ($action == 'add'): ?>
<tr>
<td style="width: 25%"><label for="attribute"><?php echo $langs->trans('ProductAttribute') ?></label></td>
<td colspan="2"><select id="attribute" name="attribute">
<option value=""></option>
<?php foreach ($prodattr_all as $attr): ?>
<option value="<?php echo $attr->id ?>"><?php echo $attr->label ?></option>
<?php endforeach ?>
</select></td>
</tr>
<tr>
<td style="width: 25%"><label for="value"><?php echo $langs->trans('Value') ?></label></td>
<td colspan="2">
<select id="value" name="value">
<option value=""></option>
</select>
</td>
</tr>
<?php endif; ?>
<tr>
<td style="width: 25%" class="fieldrequired"><label for="features"><?php echo $langs->trans('Features') ?></label></td>
<td><select multiple style="width: 100%" id="features">
<?php
foreach ($productCombination2ValuePairs1 as $pc2v): ?>
<option value="<?php echo $pc2v->fk_prod_attr ?>:<?php echo $pc2v->fk_prod_attr_val ?>"><?php echo dol_htmlentities($pc2v) ?></option>
<?php endforeach ?>
</select></td>
<td>
<?php if ($action == 'add'): ?>
<a href="#" class="button" id="addfeature"><?php echo img_edit_add() ?></a><br><br>
<a href="#" class="button" id="delfeature"><?php echo img_edit_remove() ?></a>
<?php endif; ?>
</td>
</tr>
<tr>
<td style="width: 25%"><label for="price_impact"><?php echo $langs->trans('PriceImpact') ?></label></td>
<td colspan="2"><input type="text" id="price_impact" name="price_impact" value="<?php echo price($price_impact) ?>">
<input type="checkbox" id="price_impact_percent" name="price_impact_percent" <?php echo $price_impact_percent ? ' checked' : '' ?>> <label for="price_impact_percent"><?php echo $langs->trans('PercentageVariation') ?></label></td>
</tr>
<tr>
<td style="width: 25%"><label for="weight_impact"><?php echo $langs->trans('WeightImpact') ?></label></td>
<td colspan="2"><input type="text" id="weight_impact" name="weight_impact" value="<?php echo price($weight_impact) ?>"></td>
</tr>
</table>
<br>
<div style="text-align: center"><input type="submit" value="<?php echo $action == 'add' ? $langs->trans('Create') : $langs->trans('Save') ?>" class="button"></div>
<?php foreach ($productCombination2ValuePairs1 as $pc2v): ?>
<input type="hidden" name="features[]" value="<?php echo $pc2v->fk_prod_attr.':'.$pc2v->fk_prod_attr_val ?>">
<?php endforeach; ?>
</form>
<?php
} else {
if ($action === 'delete') {
if ($prodcomb->fetch($valueid) > 0) {
$form = new Form($db);
$prodstatic->fetch($prodcomb->fk_product_child);
print $form->formconfirm(
"combinations.php?id=".$id."&valueid=".$valueid,
$langs->trans('Delete'),
$langs->trans('ProductCombinationDeleteDialog', $prodstatic->getNomUrl(1)),
"confirm_deletecombination",
'',
0,
1
);
}
} elseif ($action === 'copy') {
$form = new Form($db);
print $form->formconfirm(
'combinations.php?id='.$id,
$langs->trans('CloneCombinationsProduct'),
$langs->trans('ConfirmCloneProductCombinations'),
'confirm_copycombination',
array(
array(
'type' => 'text',
'label' => $langs->trans('CloneDestinationReference'),
'name' => 'dest_product'
)
),
0,
1
);
}
$comb2val = new ProductCombination2ValuePair($db);
if ($productCombinations): ?>
<script type="text/javascript">
jQuery(document).ready(function() {
jQuery('input[name="select_all"]').click(function() {
if (jQuery(this).prop('checked')) {
var checked = true;
} else {
var checked = false;
}
jQuery('table.liste input[type="checkbox"]').prop('checked', checked);
});
jQuery('input[name^="select["]').click(function() {
jQuery('input[name="select_all"]').prop('checked', false);
});
});
</script>
<form method="post">
<label for="bulk_action"><?php echo $langs->trans('BulkActions') ?></label>
<select id="bulk_action" name="bulk_action" class="flat">
<option value="not_buy"><?php echo $langs->trans('ProductStatusNotOnBuy') ?></option>
<option value="not_sell"><?php echo $langs->trans('ProductStatusNotOnSell') ?></option>
<option value="on_buy"><?php echo $langs->trans('ProductStatusOnBuy') ?></option>
<option value="on_sell"><?php echo $langs->trans('ProductStatusOnSell') ?></option>
<option value="delete"><?php echo $langs->trans('Delete') ?></option>
</select>
<input type="hidden" name="action" value="bulk_actions">
<input type="submit" value="Aplicar" class="button">
<br>
<br>
<?php endif; ?>
<table class="liste">
<tr class="liste_titre">
<th class="liste_titre">
<?php if ($productCombinations): ?>
<input type="checkbox" name="select_all">
<?php endif ?>
</th>
<th class="liste_titre"><?php echo $langs->trans('Product') ?></th>
<th class="liste_titre"><?php echo $langs->trans('Combination') ?></th>
<th class="liste_titre" style="text-align: center"><?php echo $langs->trans('PriceImpact') ?></th>
<th class="liste_titre" style="text-align: center"><?php echo $langs->trans('WeightImpact') ?></th>
<th class="liste_titre" style="text-align: center;"><?php echo $langs->trans('OnSell') ?></th>
<th class="liste_titre" style="text-align: center;"><?php echo $langs->trans('OnBuy') ?></th>
<th class="liste_titre"></th>
</tr>
<?php foreach ($productCombinations as $currcomb):
$prodstatic->fetch($currcomb->fk_product_child); ?>
<tr <?php echo $bc[!$var] ?>>
<td><input type="checkbox" name="select[<?php echo $prodstatic->id ?>]"></td>
<td><?php echo $prodstatic->getNomUrl(1) ?></td>
<td>
<?php
$productCombination2ValuePairs = $comb2val->fetchByFkCombination($currcomb->id);
$iMax = count($productCombination2ValuePairs);
for ($i = 0; $i < $iMax; $i++) {
echo dol_htmlentities($productCombination2ValuePairs[$i]);
if ($i !== ($iMax - 1)) {
echo ', ';
}
} ?>
</td>
<td style="text-align: right"><?php echo ($currcomb->variation_price >= 0 ? '+' : '').price($currcomb->variation_price).($currcomb->variation_price_percentage ? ' %' : '') ?></td>
<td style="text-align: right"><?php echo ($currcomb->variation_weight >= 0 ? '+' : '').price($currcomb->variation_weight).' '.measuring_units_string($prodstatic->weight_units, 'weight') ?></td>
<td style="text-align: center;"><?php echo $prodstatic->getLibStatut(2, 0) ?></td>
<td style="text-align: center;"><?php echo $prodstatic->getLibStatut(2, 1) ?></td>
<td style="text-align: right">
<a href="<?php echo dol_buildpath('/attributes/combinations.php?id='.$id.'&action=edit&valueid='.$currcomb->id, 2) ?>"><?php echo img_edit() ?></a>
<a href="<?php echo dol_buildpath('/attributes/combinations.php?id='.$id.'&action=delete&valueid='.$currcomb->id, 2) ?>"><?php echo img_delete() ?></a>
</td>
</tr>
<?php $var = !$var; endforeach ?>
</table>
<?php if ($productCombinations): ?>
</form>
<?php endif ?>
<?php
print '<div class="tabsAction">';
print ' <div class="inline-block divButAction">';
if ($productCombinations) {
print ' <a href="combinations.php?id='.$id.'&action=copy" class="butAction">'.$langs->trans('Copy').'</a>';
}
print ' <a href="generator.php?id='.$id.'" class="butAction">'.$langs->trans('ProductCombinationGenerator').'</a>
<a href="combinations.php?id='.$id.'&action=add" class="butAction">'.$langs->trans('NewProductCombination').'</a>';
print ' </div>';
print '</div>';
}
}
llxFooter();

View File

@@ -0,0 +1,73 @@
<?php
/* Copyright (C) 2016 Marcos García <marcosgdf@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
require '../main.inc.php';
require 'class/ProductAttribute.class.php';
$ref = GETPOST('ref');
$label = GETPOST('label');
if ($_POST) {
if (empty($ref) || empty($label)) {
setEventMessage($langs->trans('ErrorFieldsRequired'), 'errors');
} else {
$prodattr = new ProductAttribute($db);
$prodattr->label = $label;
$prodattr->ref = $ref;
if ($prodattr->create()) {
setEventMessage($langs->trans('RecordSaved'));
header('Location: '.dol_buildpath('/attributes/list.php', 2));
} else {
setEventMessage($langs->trans('ErrorRecordAlreadyExists'), 'errors');
}
}
}
$langs->load('products');
$title = $langs->trans('NewProductAttribute');
llxHeader('', $title);
print_fiche_titre($title);
dol_fiche_head();
?>
<form method="post">
<table class="border" style="width: 100%">
<tr>
<td class="fieldrequired"><label for="ref"><?php echo $langs->trans('Ref') ?></label></td>
<td><input type="text" id="ref" name="ref" value="<?php echo $ref ?>"></td>
</tr>
<tr>
<td class="fieldrequired"><label for="label"><?php echo $langs->trans('Label') ?></label></td>
<td><input type="text" id="label" name="label" value="<?php echo $label ?>"></td>
</tr>
</table>
<?php
dol_fiche_end();
print '<div class="center"><input type="submit" class="button" value="'.$langs->trans("Create").'"></div></form>';
llxFooter();

View File

@@ -0,0 +1,102 @@
<?php
/* Copyright (C) 2016 Marcos García <marcosgdf@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
require '../main.inc.php';
require 'class/ProductAttribute.class.php';
require 'class/ProductAttributeValue.class.php';
$id = GETPOST('id');
$ref = GETPOST('ref');
$value = GETPOST('value');
$prodattr = new ProductAttribute($db);
$prodattrval = new ProductAttributeValue($db);
if ($prodattr->fetch($id) < 1) {
dol_print_error($db, $langs->trans('ErrorRecordNotFound'));
die;
}
if ($_POST) {
if (empty($ref) || empty($value)) {
setEventMessage($langs->trans('ErrorFieldsRequired'), 'errors');
} else {
$prodattrval->fk_product_attribute = $prodattr->id;
$prodattrval->ref = $ref;
$prodattrval->value = $value;
if ($prodattrval->create() > 0) {
setEventMessage($langs->trans('RecordSaved'));
header('Location: '.dol_buildpath('/attributes/card.php?id='.$prodattr->id, 2));
die;
} else {
setEventMessage($langs->trans('ErrorCreatingProductAttributeValue'), 'errors');
}
}
}
$langs->load('products');
$title = $langs->trans('ProductAttributeName', dol_htmlentities($prodattr->label));
llxHeader('', $title);
print_fiche_titre($title);
dol_fiche_head();
?>
<table class="border" style="width: 100%">
<tr>
<td style="width: 15%" class="fieldrequired"><?php echo $langs->trans('Ref') ?></td>
<td><?php echo dol_htmlentities($prodattr->ref) ?>
</tr>
<tr>
<td style="width: 15%" class="fieldrequired"><?php echo $langs->trans('Label') ?></td>
<td><?php echo dol_htmlentities($prodattr->label) ?></td>
</tr>
</table>
<?php
dol_fiche_end();
print_fiche_titre($langs->trans('NewProductAttributeValue'));
dol_fiche_head();
?>
<form method="post">
<table class="border" style="width: 100%">
<tr>
<td style="width: 15%" class="fieldrequired"><label for="ref"><?php echo $langs->trans('Ref') ?></label></td>
<td><input id="ref" type="text" name="ref" value="<?php echo $ref ?>"></td>
</tr>
<tr>
<td style="width: 15%" class="fieldrequired"><label for="value"><?php echo $langs->trans('Value') ?></label></td>
<td><input id="value" type="text" name="value" value="<?php echo $value ?>"></td>
</tr>
</table>
<?php
dol_fiche_end();
print '<div class="center"><input type="submit" class="button" value="'.$langs->trans("Create").'"></div></form>';
llxFooter();

View File

@@ -0,0 +1,393 @@
<?php
/* Copyright (C) 2016 Marcos García <marcosgdf@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
require '../main.inc.php';
require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductAttribute.class.php';
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductAttributeValue.class.php';
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductCombination.class.php';
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductCombination2ValuePair.class.php';
$langs->load("products");
$langs->load('other');
$id = GETPOST('id', 'int');
$ref = GETPOST('ref');
$form = new Form($db);
// Security check
$fieldvalue = (! empty($id) ? $id : $ref);
$fieldtype = (! empty($ref) ? 'ref' : 'rowid');
$result=restrictedArea($user,'produit|service',$fieldvalue,'product&product','','',$fieldtype);
$prodattr = new ProductAttribute($db);
$prodattrval = new ProductAttributeValue($db);
$product = new Product($db);
$product->fetch($id);
if (!$product->isProduct()) {
header('Location: '.dol_buildpath('/product/card.php?id='.$product->id, 2));
die;
}
/**
* Posible combinations. Format.
* attrval => array(
* valueid => array(
* price => ''
* weight => ''
* )
* )
*/
$combinations = GETPOST('combinations', 'array');
$price_var_percent = (bool) GETPOST('price_var_percent');
$donotremove = true;
if ($_POST) {
$donotremove = (bool) GETPOST('donotremove');
//We must check if all those given combinations actually exist
$sanitized_values = array();
foreach ($combinations as $attr => $val) {
if ($prodattr->fetch($attr) > 0) {
foreach ($val as $valueid => $content) {
if ($prodattrval->fetch($valueid) > 0) {
$sanitized_values[$attr][$valueid] = $content;
}
}
}
}
if ($sanitized_values) {
require DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
$adapted_values = array();
//Adapt the array to the cartesian function
foreach ($sanitized_values as $attr => $val) {
$adapted_values[$attr] = array_keys($val);
}
$db->begin();
$combination = new ProductCombination($db);
$delete_prev_comb_res = 1;
if (!$donotremove) {
$delete_prev_comb_res = $combination->deleteByFkProductParent($id);
}
//Current combinations will be deleted
if ($delete_prev_comb_res > 0) {
$res = 1;
foreach (cartesianArray($adapted_values) as $currcomb) {
$res = ProductCombination::createProductCombination($product, $currcomb, $sanitized_values, $price_var_percent);
if ($res < 0) {
break;
}
}
if ($res > 0) {
$db->commit();
setEventMessage($langs->trans('RecordSaved'));
header('Location: '.dol_buildpath('/attributes/combinations.php?id='.$id, 2));
die;
} else {
setEventMessage($langs->trans('CoreErrorMessage'), 'errors');
}
} else {
setEventMessage($langs->trans('ErrorDeletingGeneratedProducts'), 'errors');
}
$db->rollback();
} else {
setEventMessage($langs->trans('ErrorFieldsRequired'), 'errors');
}
}
/*
* View
*/
if (! empty($id) || ! empty($ref)) {
$object = new Product($db);
$result = $object->fetch($id, $ref);
llxHeader("", "", $langs->trans("CardProduct".$object->type));
if ($result) {
$head = product_prepare_head($object);
$titre = $langs->trans("CardProduct".$object->type);
$picto = ($object->type == Product::TYPE_SERVICE ? 'service' : 'product');
dol_fiche_head($head, 'combinations', $titre, 0, $picto);
print '<table class="border" width="100%">';
// Reference
print '<tr>';
print '<td width="30%">'.$langs->trans("Ref").'</td><td colspan="3">';
print $form->showrefnav($object, 'id', '', 0);
print '</td>';
print '</tr>';
// Label
print '<tr><td>'.$langs->trans("Label").'</td><td colspan="3">'.$object->label.'</td></tr>';
// Status (to sell)
print '<tr><td>'.$langs->trans("Status").' ('.$langs->trans("Sell").')</td><td>';
print $object->getLibStatut(2, 0);
print '</td></tr>';
// Status (to buy)
print '<tr><td>'.$langs->trans("Status").' ('.$langs->trans("Buy").')</td><td>';
print $object->getLibStatut(2, 1);
print '</td></tr>';
print '</table>';
dol_fiche_end();
}
print_fiche_titre($langs->trans('ProductCombinationGenerator'));
$dictionnary_attr = array();
foreach ($prodattr->fetchAll() as $attr) {
$dictionnary_attr[$attr->id] = $attr;
foreach ($prodattrval->fetchAllByProductAttribute($attr->id) as $attrval) {
$dictionnary_attr[$attr->id]->values[$attrval->id] = $attrval;
}
}
?>
<script>
dictionnary_attr = <?php echo json_encode($dictionnary_attr) ?>;
weight_units = '<?php echo measuring_units_string($object->weight_units, 'weight') ?>';
attr_selected = {};
percentage_variation = jQuery('input#price_var_percent').prop('checked');
function parseSelectedFeatures(attr, val, inputs) {
var price = '';
var weight = '';
if (typeof(inputs) == 'object') {
price = inputs.price;
weight = inputs.weight;
}
if (!attr_selected.hasOwnProperty(attr)) {
var label = dictionnary_attr[attr].label;
var table = jQuery(document.createElement('table'))
.attr('id', 'combinations_'+attr)
.css('width', '100%')
.addClass('liste')
.append(
jQuery(document.createElement('thead'))
.append(jQuery(document.createElement('tr'))
.addClass('liste_titre')
.append(
jQuery(document.createElement('th'))
.addClass('liste_titre')
.css('width', '40%')
.text(label),
jQuery(document.createElement('th')).addClass('liste_titre').css('text-align', 'center').html('<?php echo $langs->trans('PriceImpact') ?>'),
jQuery(document.createElement('th')).addClass('liste_titre').css('text-align', 'center').html('<?php echo $langs->trans('WeightImpact') ?>')
)
)
)
;
jQuery('form#combinationsform').prepend(table);
attr_selected[attr] = [];
} else {
if (jQuery.inArray(val, attr_selected[attr]) != -1) {
return;
}
}
var combinations_table = jQuery('table#combinations_' + attr);
var html = jQuery(document.createElement('tr'));
if (combinations_table.children('tbody').children('tr').length % 2 === 0) {
html.addClass('pair');
} else {
html.addClass('impair');
}
var percent_symbol_html = jQuery(document.createElement('span')).attr('id', 'percentsymbol').html(' %');
if (!percentage_variation) {
percent_symbol_html.hide();
}
html.append(
jQuery(document.createElement('td')).text(dictionnary_attr[attr].values[val].value),
jQuery(document.createElement('td')).css('text-align', 'center').append(
jQuery(document.createElement('input')).attr('type', 'text').css('width', '50px').attr('name', 'combinations[' + attr + '][' + val + '][price]').val(price),
percent_symbol_html
),
jQuery(document.createElement('td')).css('text-align', 'center').append(
jQuery(document.createElement('input')).attr('type', 'text').css('width', '50px').attr('name', 'combinations[' + attr + '][' + val + '][weight]').val(weight),
' ' + weight_units
)
);
combinations_table.append(html);
attr_selected[attr].push(val);
}
function showHidePercentageSymbol(checked) {
percentage_variation = checked;
if (checked) {
jQuery('span#percentsymbol').show();
} else {
jQuery('span#percentsymbol').hide();
}
}
jQuery(document).ready(function() {
var input_price_var_percent = jQuery('input#price_var_percent');
jQuery.each(<?php echo json_encode($combinations) ?>, function(key, val) {
jQuery.each(val, function(valkey, valcontent) {
parseSelectedFeatures(key, valkey, valcontent);
});
});
jQuery('#addfeature').click(function() {
jQuery('#features option:selected').each(function(selector) {
var explode = jQuery(this).val().split(':');
parseSelectedFeatures(explode[0], explode[1]);
});
});
jQuery('#delfeature').click(function() {
jQuery('#features option:selected').each(function(selector) {
var explode = jQuery(this).val().split(':');
if (attr_selected.hasOwnProperty(explode[0])) {
var tr = jQuery('input[name="combinations[' + explode[0] + '][' + explode[1] + '][price]"').parent().parent();
var index_value = jQuery.inArray(explode[1], attr_selected[explode[0]]);
attr_selected[explode[0]].splice(index_value, 1);
if (tr.parent().children('tr').length === 1) {
tr.parent().parent().detach();
delete attr_selected[explode[0]]
} else {
tr.detach();
}
}
});
});
input_price_var_percent.click(function() {
showHidePercentageSymbol(jQuery(this).prop('checked'));
});
jQuery('input#donotremove').click(function() {
if (jQuery(this).prop('checked')) {
jQuery('div#info_donotremove').hide();
} else {
jQuery('div#info_donotremove').show();
}
});
});
</script>
<div style="width: 100%;display:block; height: 300px">
<div style="float:right; width: 79%; margin-left: 1%">
<form method="post" id="combinationsform">
<p><?php echo $langs->trans('TooMuchCombinationsWarning', $langs->trans('DoNotRemovePreviousCombinations')) ?></p>
<input type="checkbox" name="price_var_percent"
id="price_var_percent"<?php echo $price_var_percent ? ' checked' : '' ?>> <label
for="price_var_percent"><?php echo $langs->trans('UsePercentageVariations') ?></label>
<br>
<input type="checkbox" name="donotremove"
id="donotremove"<?php echo $donotremove ? ' checked' : '' ?>> <label for="donotremove"><?php echo $langs->trans('DoNotRemovePreviousCombinations') ?></label>
<br>
<div id="info_donotremove" class="info" style="<?php echo $donotremove ? 'display: none' : '' ?>">
<?php echo img_warning() ?>
<?php echo $langs->trans('ProductCombinationGeneratorWarning') ?>
</div>
<br>
<div style="text-align: center">
<input type="submit" value="<?php echo $langs->trans('Generate') ?>" class="button" name="submit">
</div>
</form>
</div>
<div style="float:left; width: 20%">
<select id="features" multiple style="width: 100%; height: 300px; overflow: auto">
<?php foreach ($dictionnary_attr as $attr): ?>
<optgroup label="<?php echo $attr->label ?>">
<?php foreach ($attr->values as $attrval): ?>
<option value="<?php echo $attr->id.':'.$attrval->id ?>"<?php
if (isset($combinations[$attr->id][$attrval->id])) {
echo ' selected';
}
?>><?php echo dol_htmlentities($attrval->value) ?></option>
<?php endforeach ?>
</optgroup>
<?php endforeach ?>
</select>
<br><br>
<a class="button" id="delfeature" style="float: right"><?php echo img_edit_remove() ?></a>
<a class="button" id="addfeature"><?php echo img_edit_add() ?></a>
</div>
</div>
<?php
llxFooter();
}

View File

View File

View File

@@ -0,0 +1,31 @@
<?php
/* Copyright (C) 2016 Marcos García <marcosgdf@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
/**
* Returns the entity of the products.
* @param DoliDB $db Database handler
* @return mixed
*/
function getProductEntities(DoliDB $db)
{
require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
$product = new Product($db);
return getEntity($product->element, 1);
}

140
htdocs/attributes/list.php Normal file
View File

@@ -0,0 +1,140 @@
<?php
/* Copyright (C) 2016 Marcos García <marcosgdf@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
require '../main.inc.php';
require 'class/ProductAttribute.class.php';
$rowid = GETPOST('rowid');
$action = GETPOST('action');
$object = new ProductAttribute($db);
if ($action == 'up') {
$object->fetch($rowid);
$object->moveUp();
header('Location: '.$_SERVER['PHP_SELF']);
die;
} elseif ($action == 'down') {
$object->fetch($rowid);
$object->moveDown();
header('Location: '.$_SERVER['PHP_SELF']);
die;
}
$langs->load('products');
$var = false;
$title = $langs->trans($langs->trans('ProductAttributes'));
$attributes = $object->fetchAll();
llxHeader('', $title);
print_fiche_titre($title);
$forcereloadpage=empty($conf->global->MAIN_FORCE_RELOAD_PAGE)?0:1;
?>
<script type="text/javascript">
$(document).ready(function(){
$(".imgupforline, .imgdownforline").hide();
$(".lineupdown").removeAttr('href');
$(".tdlineupdown")
.css("background-image", 'url(<?php echo DOL_URL_ROOT.'/theme/'.$conf->theme.'/img/grip.png'; ?>)')
.css("background-repeat", "no-repeat")
.css("background-position", "center center")
.hover(
function () {
$(this).addClass('showDragHandle');
}, function () {
$(this).removeClass('showDragHandle');
}
);
$("#tablelines").tableDnD({
onDrop: function(table, row) {
console.log('drop');
var reloadpage = "<?php echo $forcereloadpage; ?>";
var roworder = cleanSerialize($("#tablelines").tableDnDSerialize());
$.post("<?php echo DOL_URL_ROOT; ?>/attributes/ajax/orderAttribute.php",
{
roworder: roworder
},
function() {
if (reloadpage == 1) {
location.href = '<?php echo $_SERVER['PHP_SELF'].'?'.$_SERVER['QUERY_STRING']; ?>';
} else {
$("#tablelines .drag").each(
function( intIndex ) {
$(this).removeClass("pair impair");
if (intIndex % 2 == 0) $(this).addClass('impair');
if (intIndex % 2 == 1) $(this).addClass('pair');
});
}
});
},
onDragClass: "dragClass",
dragHandle: "tdlineupdown"
});
});
</script>
<table class="liste" id="tablelines">
<tr class="liste_titre nodrag nodrop">
<th class="liste_titre"><?php print $langs->trans('Ref') ?></th>
<th class="liste_titre"><?php print $langs->trans('Label') ?></th>
<th class="liste_titre"><?php print $langs->trans('NbProducts') ?></th>
<th class="liste_titre" colspan="2"></th>
</tr>
<?php foreach ($attributes as $key => $attribute): ?>
<tr id="row-<?php echo $attribute->id ?>" <?php echo $bcdd[$var] ?>>
<td><a href="card.php?id=<?php echo $attribute->id ?>"><?php echo dol_htmlentities($attribute->ref) ?></a></td>
<td><a href="card.php?id=<?php echo $attribute->id ?>"><?php echo dol_htmlentities($attribute->label) ?></a></td>
<td><?php echo $attribute->countChildProducts() ?></td>
<td style="text-align: right">
<a href="card.php?id=<?php echo $attribute->id ?>&action=edit"><?php echo img_edit() ?></a>
<a href="card.php?id=<?php echo $attribute->id ?>&action=delete"><?php echo img_delete() ?></a>
</td>
<td align="center" class="linecolmove tdlineupdown">
<?php if ($key > 0): ?>
<a class="lineupdown"
href="<?php echo $_SERVER['PHP_SELF'] ?>?action=up&amp;rowid=<?php echo $attribute->id ?>"><?php echo img_up('default', 0, 'imgupforline'); ?></a>
<?php endif ?>
<?php if ($key < count($attributes)-1): ?>
<a class="lineupdown"
href="<?php echo $_SERVER['PHP_SELF'] ?>?action=down&amp;rowid=<?php echo $attribute->id ?>"><?php echo img_down('default', 0, 'imgdownforline'); ?></a>
<?php endif ?>
</td>
</tr>
<?php
$var = !$var;
endforeach
?>
</table>
<br>
<div class="tabsAction">
<div class="inline-block divButAction">
<a href="create.php" class="butAction"><?php echo $langs->trans('Create') ?></a>
</div>
</div>
<?php
llxFooter();

View File

@@ -11,6 +11,7 @@
* Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr> * Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr>
* Copyright (C) 2013-2014 Florian Henry <florian.henry@open-concept.pro> * Copyright (C) 2013-2014 Florian Henry <florian.henry@open-concept.pro>
* Copyright (C) 2014 Ferran Marcet <fmarcet@2byte.es> * Copyright (C) 2014 Ferran Marcet <fmarcet@2byte.es>
* Copyright (C) 2016 Marcos García <marcosgdf@gmail.com>
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@@ -48,6 +49,10 @@ if (! empty($conf->projet->enabled)) {
require_once DOL_DOCUMENT_ROOT . '/core/class/html.formprojet.class.php'; require_once DOL_DOCUMENT_ROOT . '/core/class/html.formprojet.class.php';
} }
if (!empty($conf->attributes->enabled)) {
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductCombination.class.php';
}
$langs->load('companies'); $langs->load('companies');
$langs->load('propal'); $langs->load('propal');
$langs->load('compta'); $langs->load('compta');
@@ -655,7 +660,8 @@ if (empty($reshook))
$predef=''; $predef='';
$product_desc=(GETPOST('dp_desc')?GETPOST('dp_desc'):''); $product_desc=(GETPOST('dp_desc')?GETPOST('dp_desc'):'');
$price_ht = GETPOST('price_ht'); $price_ht = GETPOST('price_ht');
if (GETPOST('prod_entry_mode') == 'free') $prod_entry_mode = GETPOST('prod_entry_mode');
if ($prod_entry_mode == 'free')
{ {
$idprod=0; $idprod=0;
$tva_tx = (GETPOST('tva_tx') ? GETPOST('tva_tx') : 0); $tva_tx = (GETPOST('tva_tx') ? GETPOST('tva_tx') : 0);
@@ -681,21 +687,35 @@ if (empty($reshook))
} }
} }
if (GETPOST('prod_entry_mode') == 'free' && empty($idprod) && GETPOST('type') < 0) { if ($prod_entry_mode == 'free' && empty($idprod) && GETPOST('type') < 0) {
setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Type")), null, 'errors'); setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Type")), null, 'errors');
$error ++; $error ++;
} }
if (GETPOST('prod_entry_mode') == 'free' && empty($idprod) && $price_ht == '') // Unit price can be 0 but not ''. Also price can be negative for proposal. if ($prod_entry_mode == 'free' && empty($idprod) && $price_ht == '') // Unit price can be 0 but not ''. Also price can be negative for proposal.
{ {
setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("UnitPriceHT")), null, 'errors'); setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("UnitPriceHT")), null, 'errors');
$error ++; $error ++;
} }
if (GETPOST('prod_entry_mode') == 'free' && empty($idprod) && empty($product_desc)) { if ($prod_entry_mode == 'free' && empty($idprod) && empty($product_desc)) {
setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Description")), null, 'errors'); setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Description")), null, 'errors');
$error ++; $error ++;
} }
if (!$error && !empty($conf->attributes->enabled) && $prod_entry_mode != 'free') {
if ($combinations = GETPOST('combinations', 'array')) {
//Check if there is a product with the given combination
$prodcomb = new ProductCombination($db);
if ($res = $prodcomb->fetchByProductCombination2ValuePairs($idprod, $combinations)) {
$idprod = $res->fk_product_child;
} else {
setEventMessage($langs->trans('ErrorProductCombinationNotFound'), 'errors');
$error ++;
}
}
}
if (! $error && ($qty >= 0) && (! empty($product_desc) || ! empty($idprod))) { if (! $error && ($qty >= 0) && (! empty($product_desc) || ! empty($idprod))) {
$pu_ht = 0; $pu_ht = 0;
$pu_ttc = 0; $pu_ttc = 0;

View File

@@ -7,7 +7,7 @@
* Copyright (C) 2010-2013 Juanjo Menent <jmenent@2byte.es> * Copyright (C) 2010-2013 Juanjo Menent <jmenent@2byte.es>
* Copyright (C) 2011-2016 Philippe Grand <philippe.grand@atoo-net.com> * Copyright (C) 2011-2016 Philippe Grand <philippe.grand@atoo-net.com>
* Copyright (C) 2012-2013 Christophe Battarel <christophe.battarel@altairis.fr> * Copyright (C) 2012-2013 Christophe Battarel <christophe.battarel@altairis.fr>
* Copyright (C) 2012 Marcos García <marcosgdf@gmail.com> * Copyright (C) 2012-2016 Marcos García <marcosgdf@gmail.com>
* Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr> * Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr>
* Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro> * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
* Copyright (C) 2014 Ferran Marcet <fmarcet@2byte.es> * Copyright (C) 2014 Ferran Marcet <fmarcet@2byte.es>
@@ -51,6 +51,10 @@ if (! empty($conf->projet->enabled)) {
} }
require_once DOL_DOCUMENT_ROOT . '/core/class/doleditor.class.php'; require_once DOL_DOCUMENT_ROOT . '/core/class/doleditor.class.php';
if (!empty($conf->attributes->enabled)) {
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductCombination.class.php';
}
$langs->load('orders'); $langs->load('orders');
$langs->load('sendings'); $langs->load('sendings');
$langs->load('companies'); $langs->load('companies');
@@ -621,7 +625,8 @@ if (empty($reshook))
$predef=''; $predef='';
$product_desc=(GETPOST('dp_desc')?GETPOST('dp_desc'):''); $product_desc=(GETPOST('dp_desc')?GETPOST('dp_desc'):'');
$price_ht = GETPOST('price_ht'); $price_ht = GETPOST('price_ht');
if (GETPOST('prod_entry_mode') == 'free') $prod_entry_mode = GETPOST('prod_entry_mode');
if ($prod_entry_mode == 'free')
{ {
$idprod=0; $idprod=0;
$tva_tx = (GETPOST('tva_tx') ? GETPOST('tva_tx') : 0); $tva_tx = (GETPOST('tva_tx') ? GETPOST('tva_tx') : 0);
@@ -651,11 +656,11 @@ if (empty($reshook))
setEventMessages($langs->trans('ErrorBothFieldCantBeNegative', $langs->transnoentitiesnoconv('UnitPriceHT'), $langs->transnoentitiesnoconv('Qty')), null, 'errors'); setEventMessages($langs->trans('ErrorBothFieldCantBeNegative', $langs->transnoentitiesnoconv('UnitPriceHT'), $langs->transnoentitiesnoconv('Qty')), null, 'errors');
$error++; $error++;
} }
if (GETPOST('prod_entry_mode') == 'free' && empty($idprod) && GETPOST('type') < 0) { if ($prod_entry_mode == 'free' && empty($idprod) && GETPOST('type') < 0) {
setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('Type')), null, 'errors'); setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('Type')), null, 'errors');
$error++; $error++;
} }
if (GETPOST('prod_entry_mode') == 'free' && empty($idprod) && (! ($price_ht >= 0) || $price_ht == '')) // Unit price can be 0 but not '' if ($prod_entry_mode == 'free' && empty($idprod) && (! ($price_ht >= 0) || $price_ht == '')) // Unit price can be 0 but not ''
{ {
setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("UnitPriceHT")), null, 'errors'); setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("UnitPriceHT")), null, 'errors');
$error++; $error++;
@@ -664,11 +669,25 @@ if (empty($reshook))
setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('Qty')), null, 'errors'); setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('Qty')), null, 'errors');
$error++; $error++;
} }
if (GETPOST('prod_entry_mode') == 'free' && empty($idprod) && empty($product_desc)) { if ($prod_entry_mode == 'free' && empty($idprod) && empty($product_desc)) {
setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('Description')), null, 'errors'); setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('Description')), null, 'errors');
$error++; $error++;
} }
if (!$error && !empty($conf->attributes->enabled) && $prod_entry_mode != 'free') {
if ($combinations = GETPOST('combinations', 'array')) {
//Check if there is a product with the given combination
$prodcomb = new ProductCombination($db);
if ($res = $prodcomb->fetchByProductCombination2ValuePairs($idprod, $combinations)) {
$idprod = $res->fk_product_child;
} else {
setEventMessage($langs->trans('ErrorProductCombinationNotFound'), 'errors');
$error ++;
}
}
}
if (! $error && ($qty >= 0) && (! empty($product_desc) || ! empty($idprod))) { if (! $error && ($qty >= 0) && (! empty($product_desc) || ! empty($idprod))) {
// Clean parameters // Clean parameters
$date_start=dol_mktime(GETPOST('date_start'.$predef.'hour'), GETPOST('date_start'.$predef.'min'), GETPOST('date_start'.$predef.'sec'), GETPOST('date_start'.$predef.'month'), GETPOST('date_start'.$predef.'day'), GETPOST('date_start'.$predef.'year')); $date_start=dol_mktime(GETPOST('date_start'.$predef.'hour'), GETPOST('date_start'.$predef.'min'), GETPOST('date_start'.$predef.'sec'), GETPOST('date_start'.$predef.'month'), GETPOST('date_start'.$predef.'day'), GETPOST('date_start'.$predef.'year'));

View File

@@ -13,7 +13,7 @@
* Copyright (C) 2013-2014 Florian Henry <florian.henry@open-concept.pro> * Copyright (C) 2013-2014 Florian Henry <florian.henry@open-concept.pro>
* Copyright (C) 2013 Cédric Salvador <csalvador@gpcsolutions.fr> * Copyright (C) 2013 Cédric Salvador <csalvador@gpcsolutions.fr>
* Copyright (C) 2014 Ferran Marcet <fmarcet@2byte.es> * Copyright (C) 2014 Ferran Marcet <fmarcet@2byte.es>
* Copyright (C) 2015 Marcos García <marcosgdf@gmail.com> * Copyright (C) 2015-2016 Marcos García <marcosgdf@gmail.com>
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@@ -56,6 +56,10 @@ if (! empty($conf->projet->enabled)) {
} }
require_once DOL_DOCUMENT_ROOT . '/core/class/doleditor.class.php'; require_once DOL_DOCUMENT_ROOT . '/core/class/doleditor.class.php';
if (!empty($conf->attributes->enabled)) {
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductCombination.class.php';
}
$langs->load('bills'); $langs->load('bills');
$langs->load('companies'); $langs->load('companies');
$langs->load('compta'); $langs->load('compta');
@@ -1322,7 +1326,8 @@ if (empty($reshook))
$predef=''; $predef='';
$product_desc=(GETPOST('dp_desc')?GETPOST('dp_desc'):''); $product_desc=(GETPOST('dp_desc')?GETPOST('dp_desc'):'');
$price_ht = GETPOST('price_ht'); $price_ht = GETPOST('price_ht');
if (GETPOST('prod_entry_mode') == 'free') $prod_entry_mode = GETPOST('prod_entry_mode');
if ($prod_entry_mode == 'free')
{ {
$idprod=0; $idprod=0;
$tva_tx = (GETPOST('tva_tx') ? GETPOST('tva_tx') : 0); $tva_tx = (GETPOST('tva_tx') ? GETPOST('tva_tx') : 0);
@@ -1352,11 +1357,11 @@ if (empty($reshook))
setEventMessages($langs->trans('ErrorBothFieldCantBeNegative', $langs->transnoentitiesnoconv('UnitPriceHT'), $langs->transnoentitiesnoconv('Qty')), null, 'errors'); setEventMessages($langs->trans('ErrorBothFieldCantBeNegative', $langs->transnoentitiesnoconv('UnitPriceHT'), $langs->transnoentitiesnoconv('Qty')), null, 'errors');
$error ++; $error ++;
} }
if (GETPOST('prod_entry_mode') == 'free' && empty($idprod) && GETPOST('type') < 0) { if ($prod_entry_mode == 'free' && empty($idprod) && GETPOST('type') < 0) {
setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('Type')), null, 'errors'); setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('Type')), null, 'errors');
$error ++; $error ++;
} }
if (GETPOST('prod_entry_mode') == 'free' && empty($idprod) && (! ($price_ht >= 0) || $price_ht == '')) // Unit price can be 0 but not '' if ($prod_entry_mode == 'free' && empty($idprod) && (! ($price_ht >= 0) || $price_ht == '')) // Unit price can be 0 but not ''
{ {
setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("UnitPriceHT")), null, 'errors'); setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("UnitPriceHT")), null, 'errors');
$error ++; $error ++;
@@ -1365,7 +1370,7 @@ if (empty($reshook))
setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('Qty')), null, 'errors'); setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('Qty')), null, 'errors');
$error ++; $error ++;
} }
if (GETPOST('prod_entry_mode') == 'free' && empty($idprod) && empty($product_desc)) { if ($prod_entry_mode == 'free' && empty($idprod) && empty($product_desc)) {
setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('Description')), null, 'errors'); setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('Description')), null, 'errors');
$error ++; $error ++;
} }
@@ -1374,7 +1379,23 @@ if (empty($reshook))
setEventMessages($langs->trans('ErrorQtyForCustomerInvoiceCantBeNegative'), null, 'errors'); setEventMessages($langs->trans('ErrorQtyForCustomerInvoiceCantBeNegative'), null, 'errors');
$error ++; $error ++;
} }
if (!$error && !empty($conf->attributes->enabled) && $prod_entry_mode != 'free') {
if ($combinations = GETPOST('combinations', 'array')) {
//Check if there is a product with the given combination
$prodcomb = new ProductCombination($db);
if ($res = $prodcomb->fetchByProductCombination2ValuePairs($idprod, $combinations)) {
$idprod = $res->fk_product_child;
} else {
setEventMessage($langs->trans('ErrorProductCombinationNotFound'), 'errors');
$error ++;
}
}
}
if (! $error && ($qty >= 0) && (! empty($product_desc) || ! empty($idprod))) { if (! $error && ($qty >= 0) && (! empty($product_desc) || ! empty($idprod))) {
$ret = $object->fetch($id); $ret = $object->fetch($id);
if ($ret < 0) { if ($ret < 0) {
dol_print_error($db, $object->error); dol_print_error($db, $object->error);

View File

@@ -7,7 +7,7 @@
* Copyright (C) 2013 Christophe Battarel <christophe.battarel@altairis.fr> * Copyright (C) 2013 Christophe Battarel <christophe.battarel@altairis.fr>
* Copyright (C) 2013-2014 Florian Henry <florian.henry@open-concept.pro> * Copyright (C) 2013-2014 Florian Henry <florian.henry@open-concept.pro>
* Copyright (C) 2014-2016 Ferran Marcet <fmarcet@2byte.es> * Copyright (C) 2014-2016 Ferran Marcet <fmarcet@2byte.es>
* Copyright (C) 2014 Marcos García <marcosgdf@gmail.com> * Copyright (C) 2014-2016 Marcos García <marcosgdf@gmail.com>
* Copyright (C) 2015 Jean-François Ferry <jfefe@aternatik.fr> * Copyright (C) 2015 Jean-François Ferry <jfefe@aternatik.fr>
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
@@ -471,7 +471,7 @@ if (empty($reshook))
unset($_POST["options_" . $key]); unset($_POST["options_" . $key]);
} }
} }
if (! $error) if (! $error)
{ {
// Clean parameters // Clean parameters

View File

@@ -12,7 +12,7 @@
* Copyright (C) 2010 Juanjo Menent <jmenent@2byte.es> * Copyright (C) 2010 Juanjo Menent <jmenent@2byte.es>
* Copyright (C) 2010-2014 Philippe Grand <philippe.grand@atoo-net.com> * Copyright (C) 2010-2014 Philippe Grand <philippe.grand@atoo-net.com>
* Copyright (C) 2011 Herve Prot <herve.prot@symeos.com> * Copyright (C) 2011 Herve Prot <herve.prot@symeos.com>
* Copyright (C) 2012-2014 Marcos García <marcosgdf@gmail.com> * Copyright (C) 2012-2016 Marcos García <marcosgdf@gmail.com>
* Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr> * Copyright (C) 2012 Cedric Salvador <csalvador@gpcsolutions.fr>
* Copyright (C) 2012-2015 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr> * Copyright (C) 2012-2015 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr>
* Copyright (C) 2014 Alexandre Spangaro <aspangaro.dolibarr@gmail.com> * Copyright (C) 2014 Alexandre Spangaro <aspangaro.dolibarr@gmail.com>
@@ -1605,9 +1605,10 @@ class Form
* @param int $hidelabel Hide label (0=no, 1=yes, 2=show search icon (before) and placeholder, 3 search icon after) * @param int $hidelabel Hide label (0=no, 1=yes, 2=show search icon (before) and placeholder, 3 search icon after)
* @param array $ajaxoptions Options for ajax_autocompleter * @param array $ajaxoptions Options for ajax_autocompleter
* @param int $socid Thirdparty Id (to get also price dedicated to this customer) * @param int $socid Thirdparty Id (to get also price dedicated to this customer)
* @param array $selected_combinations Selected combinations. Format: array([attrid] => attrval, [...])
* @return void * @return void
*/ */
function select_produits($selected='', $htmlname='productid', $filtertype='', $limit=20, $price_level=0, $status=1, $finished=2, $selected_input_value='', $hidelabel=0, $ajaxoptions=array(), $socid=0) function select_produits($selected='', $htmlname='productid', $filtertype='', $limit=20, $price_level=0, $status=1, $finished=2, $selected_input_value='', $hidelabel=0, $ajaxoptions=array(), $socid=0, $selected_combinations = array())
{ {
global $langs,$conf; global $langs,$conf;
@@ -1632,6 +1633,80 @@ class Form
$urloption.='&socid='.$socid; $urloption.='&socid='.$socid;
} }
print ajax_autocompleter($selected, $htmlname, DOL_URL_ROOT.'/product/ajax/products.php', $urloption, $conf->global->PRODUIT_USE_SEARCH_TO_SELECT, 0, $ajaxoptions); print ajax_autocompleter($selected, $htmlname, DOL_URL_ROOT.'/product/ajax/products.php', $urloption, $conf->global->PRODUIT_USE_SEARCH_TO_SELECT, 0, $ajaxoptions);
if (!empty($conf->attributes->enabled)) {
?>
<script>
selected = <?php echo json_encode($selected_combinations) ?>;
combvalues = {};
jQuery(document).ready(function () {
jQuery("input[name='prod_entry_mode']").change(function () {
if (jQuery(this).val() == 'free') {
jQuery('div#attributes_box').empty();
}
});
jQuery("input#<?php echo $htmlname ?>").change(function () {
if (!jQuery(this).val()) {
jQuery('div#attributes_box').empty();
return;
}
jQuery.getJSON("<?php echo dol_buildpath('/attributes/ajax/getCombinations.php', 2) ?>", {
id: jQuery(this).val()
}, function (data) {
jQuery('div#attributes_box').empty();
jQuery.each(data, function (key, val) {
combvalues[val.id] = val.values;
var span = jQuery(document.createElement('div')).css({
'display': 'table-row'
});
span.append(
jQuery(document.createElement('div')).text(val.label).css({
'font-weight': 'bold',
'display': 'table-cell',
'text-align': 'right'
})
);
var html = jQuery(document.createElement('select')).attr('name', 'combinations[' + val.id + ']').css({
'margin-left': '15px',
'white-space': 'pre'
}).append(
jQuery(document.createElement('option')).val('')
);
jQuery.each(combvalues[val.id], function (key, val) {
var tag = jQuery(document.createElement('option')).val(val.id).html(val.value);
if (selected[val.fk_product_attribute] == val.id) {
tag.attr('selected', 'selected');
}
html.append(tag);
});
span.append(html);
jQuery('div#attributes_box').append(span);
});
})
});
<?php if ($selected): ?>
jQuery("input#<?php echo $htmlname ?>").change();
<?php endif ?>
});
</script>
<?php
}
if (empty($hidelabel)) print $langs->trans("RefOrLabel").' : '; if (empty($hidelabel)) print $langs->trans("RefOrLabel").' : ';
else if ($hidelabel > 1) { else if ($hidelabel > 1) {
if (! empty($conf->global->MAIN_HTML5_PLACEHOLDER)) $placeholder=' placeholder="'.$langs->trans("RefOrLabel").'"'; if (! empty($conf->global->MAIN_HTML5_PLACEHOLDER)) $placeholder=' placeholder="'.$langs->trans("RefOrLabel").'"';
@@ -1709,7 +1784,17 @@ class Form
{ {
$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."product_lang as pl ON pl.fk_product = p.rowid AND pl.lang='". $langs->getDefaultLang() ."'"; $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."product_lang as pl ON pl.fk_product = p.rowid AND pl.lang='". $langs->getDefaultLang() ."'";
} }
if (!empty($conf->global->PRODUIT_ATTRIBUTES_HIDECHILD)) {
$sql .= " LEFT JOIN llx_product_attribute_combination pac ON pac.fk_product_child = p.rowid";
}
$sql.= ' WHERE p.entity IN ('.getEntity('product', 1).')'; $sql.= ' WHERE p.entity IN ('.getEntity('product', 1).')';
if (!empty($conf->global->PRODUIT_ATTRIBUTES_HIDECHILD)) {
$sql .= " AND pac.rowid IS NULL";
}
if ($finished == 0) if ($finished == 0)
{ {
$sql.= " AND p.finished = ".$finished; $sql.= " AND p.finished = ".$finished;
@@ -1881,7 +1966,7 @@ class Form
$outlabel=$objp->label; $outlabel=$objp->label;
$outdesc=$objp->description; $outdesc=$objp->description;
$outbarcode=$objp->barcode; $outbarcode=$objp->barcode;
$outtype=$objp->fk_product_type; $outtype=$objp->fk_product_type;
$outdurationvalue=$outtype == Product::TYPE_SERVICE?substr($objp->duration,0,dol_strlen($objp->duration)-1):''; $outdurationvalue=$outtype == Product::TYPE_SERVICE?substr($objp->duration,0,dol_strlen($objp->duration)-1):'';
$outdurationunit=$outtype == Product::TYPE_SERVICE?substr($objp->duration,-1):''; $outdurationunit=$outtype == Product::TYPE_SERVICE?substr($objp->duration,-1):'';
@@ -1898,13 +1983,13 @@ class Form
$opt.= $objp->ref; $opt.= $objp->ref;
if ($outbarcode) $opt.=' ('.$outbarcode.')'; if ($outbarcode) $opt.=' ('.$outbarcode.')';
$opt.=' - '.dol_trunc($label,$maxlengtharticle).' - '; $opt.=' - '.dol_trunc($label,$maxlengtharticle).' - ';
$objRef = $objp->ref; $objRef = $objp->ref;
if (! empty($filterkey) && $filterkey != '') $objRef=preg_replace('/('.preg_quote($filterkey).')/i','<strong>$1</strong>',$objRef,1); if (! empty($filterkey) && $filterkey != '') $objRef=preg_replace('/('.preg_quote($filterkey).')/i','<strong>$1</strong>',$objRef,1);
$outval.=$objRef; $outval.=$objRef;
if ($outbarcode) $outval.=' ('.$outbarcode.')'; if ($outbarcode) $outval.=' ('.$outbarcode.')';
$outval.=' - '.dol_trunc($label,$maxlengtharticle).' - '; $outval.=' - '.dol_trunc($label,$maxlengtharticle).' - ';
$found=0; $found=0;
// Multiprice // Multiprice
@@ -2066,7 +2151,7 @@ class Form
{ {
global $langs,$conf; global $langs,$conf;
global $price_level, $status, $finished; global $price_level, $status, $finished;
$selected_input_value=''; $selected_input_value='';
if (! empty($conf->use_javascript_ajax) && ! empty($conf->global->PRODUIT_USE_SEARCH_TO_SELECT)) if (! empty($conf->use_javascript_ajax) && ! empty($conf->global->PRODUIT_USE_SEARCH_TO_SELECT))
{ {
@@ -3174,7 +3259,7 @@ class Form
{ {
dol_syslog(__METHOD__ . ': using numeric value for parameter type is deprecated. Use string code instead.', LOG_WARNING); dol_syslog(__METHOD__ . ': using numeric value for parameter type is deprecated. Use string code instead.', LOG_WARNING);
} }
$cat = new Categorie($this->db); $cat = new Categorie($this->db);
$cate_arbo = $cat->get_full_arbo($type,$excludeafterid); $cate_arbo = $cat->get_full_arbo($type,$excludeafterid);
@@ -3837,7 +3922,7 @@ class Form
function form_multicurrency_rate($page, $rate='', $htmlname='multicurrency_tx', $currency='') function form_multicurrency_rate($page, $rate='', $htmlname='multicurrency_tx', $currency='')
{ {
global $langs, $mysoc, $conf; global $langs, $mysoc, $conf;
if ($htmlname != "none") if ($htmlname != "none")
{ {
print '<form method="POST" action="'.$page.'">'; print '<form method="POST" action="'.$page.'">';
@@ -4107,7 +4192,7 @@ class Form
$out.= '</option>'; $out.= '</option>';
} }
} }
} }
$out.= '</select>'; $out.= '</select>';
@@ -4204,7 +4289,7 @@ class Form
$defaulttx=preg_replace('/\s*\(.*\)/','',$defaulttx); $defaulttx=preg_replace('/\s*\(.*\)/','',$defaulttx);
} }
//var_dump($selectedrate.'-'.$defaulttx.'-'.$defaultnpr.'-'.$defaultcode); //var_dump($selectedrate.'-'.$defaulttx.'-'.$defaultnpr.'-'.$defaultcode);
// Check parameters // Check parameters
if (is_object($societe_vendeuse) && ! $societe_vendeuse->country_code) if (is_object($societe_vendeuse) && ! $societe_vendeuse->country_code)
{ {
@@ -4263,7 +4348,7 @@ class Form
// Now we get list // Now we get list
$num = $this->load_cache_vatrates($code_country); // If no vat defined, return -1 with message into this->error $num = $this->load_cache_vatrates($code_country); // If no vat defined, return -1 with message into this->error
if ($num > 0) if ($num > 0)
{ {
// Definition du taux a pre-selectionner (si defaulttx non force et donc vaut -1 ou '') // Definition du taux a pre-selectionner (si defaulttx non force et donc vaut -1 ou '')
@@ -4304,7 +4389,7 @@ class Form
$return.= '"'; $return.= '"';
if ($defaultcode) // If defaultcode is defined, we used it in priority to select combo option instead of using rate+npr flag if ($defaultcode) // If defaultcode is defined, we used it in priority to select combo option instead of using rate+npr flag
{ {
if ($defaultcode == $rate['code']) $return.= ' selected'; if ($defaultcode == $rate['code']) $return.= ' selected';
} }
elseif ($rate['txtva'] == $defaulttx && $rate['nprtva'] == $defaultnpr) elseif ($rate['txtva'] == $defaulttx && $rate['nprtva'] == $defaultnpr)
{ {
@@ -5036,7 +5121,7 @@ class Form
/* var_dump($val); /* var_dump($val);
var_dump(array_key_exists('enabled', $val)); var_dump(array_key_exists('enabled', $val));
var_dump(!$val['enabled']);*/ var_dump(!$val['enabled']);*/
if (array_key_exists('enabled', $val) && isset($val['enabled']) && ! $val['enabled']) if (array_key_exists('enabled', $val) && isset($val['enabled']) && ! $val['enabled'])
{ {
unset($array[$key]); // We don't want this field unset($array[$key]); // We don't want this field
continue; continue;
@@ -5221,17 +5306,17 @@ class Form
else if ($objecttype == 'subscription') { else if ($objecttype == 'subscription') {
$tplpath = 'adherents'; $tplpath = 'adherents';
} }
global $linkedObjectBlock; global $linkedObjectBlock;
$linkedObjectBlock = $objects; $linkedObjectBlock = $objects;
if (empty($numoutput)) if (empty($numoutput))
{ {
$numoutput++; $numoutput++;
print '<br>'; print '<br>';
print load_fiche_titre($langs->trans('RelatedObjects'), '', ''); print load_fiche_titre($langs->trans('RelatedObjects'), '', '');
print '<table class="noborder allwidth">'; print '<table class="noborder allwidth">';
print '<tr class="liste_titre">'; print '<tr class="liste_titre">';
@@ -5244,7 +5329,7 @@ class Form
print '<td></td>'; print '<td></td>';
print '</tr>'; print '</tr>';
} }
// Output template part (modules that overwrite templates must declare this into descriptor) // Output template part (modules that overwrite templates must declare this into descriptor)
$dirtpls=array_merge($conf->modules_parts['tpl'],array('/'.$tplpath.'/tpl')); $dirtpls=array_merge($conf->modules_parts['tpl'],array('/'.$tplpath.'/tpl'));
foreach($dirtpls as $reldir) foreach($dirtpls as $reldir)
@@ -5254,11 +5339,11 @@ class Form
} }
} }
if ($numoutput) if ($numoutput)
{ {
print '</table>'; print '</table>';
} }
return $num; return $num;
} }
} }
@@ -5627,7 +5712,7 @@ class Form
$ret.='</div>'; $ret.='</div>';
if ($morehtmlright) $ret.='<div class="inline-block floatleft">'.$morehtmlright.'</div>'; if ($morehtmlright) $ret.='<div class="inline-block floatleft">'.$morehtmlright.'</div>';
if ($previous_ref || $next_ref || $morehtml) if ($previous_ref || $next_ref || $morehtml)
{ {
$ret.='<div class="pagination"><ul>'; $ret.='<div class="pagination"><ul>';

View File

@@ -2,7 +2,7 @@
/* Copyright (C) 2008-2011 Laurent Destailleur <eldy@users.sourceforge.net> /* Copyright (C) 2008-2011 Laurent Destailleur <eldy@users.sourceforge.net>
* Copyright (C) 2008-2012 Regis Houssin <regis.houssin@capnetworks.com> * Copyright (C) 2008-2012 Regis Houssin <regis.houssin@capnetworks.com>
* Copyright (C) 2008 Raphael Bertrand (Resultic) <raphael.bertrand@resultic.fr> * Copyright (C) 2008 Raphael Bertrand (Resultic) <raphael.bertrand@resultic.fr>
* Copyright (C) 2014 Marcos García <marcosgdf@gmail.com> * Copyright (C) 2014-2016 Marcos García <marcosgdf@gmail.com>
* Copyright (C) 2015 Ferran Marcet <fmarcet@2byte.es> * Copyright (C) 2015 Ferran Marcet <fmarcet@2byte.es>
* Copyright (C) 2015-2016 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr> * Copyright (C) 2015-2016 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr>
* *
@@ -2058,4 +2058,31 @@ function colorStringToArray($stringcolor,$colorifnotfound=array(88,88,88))
return array(hexdec($reg[1]),hexdec($reg[2]),hexdec($reg[3])); return array(hexdec($reg[1]),hexdec($reg[2]),hexdec($reg[3]));
} }
/**
* Applies the Cartesian product algorithm to an array
* Source: http://stackoverflow.com/a/15973172
*
* @param array $input Array of products
* @return array Array of combinations
*/
function cartesianArray(array $input) {
// filter out empty values
$input = array_filter($input);
$result = array(array());
foreach ($input as $key => $values) {
$append = array();
foreach($result as $product) {
foreach($values as $item) {
$product[$key] = $item;
$append[] = $product;
}
}
$result = $append;
}
return $result;
}

View File

@@ -3,7 +3,7 @@
* Copyright (C) 2007 Rodolphe Quiedeville <rodolphe@quiedeville.org> * Copyright (C) 2007 Rodolphe Quiedeville <rodolphe@quiedeville.org>
* Copyright (C) 2009-2010 Regis Houssin <regis.houssin@capnetworks.com> * Copyright (C) 2009-2010 Regis Houssin <regis.houssin@capnetworks.com>
* Copyright (C) 2015 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr> * Copyright (C) 2015 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr>
* Copyright (C) 2015 Marcos García <marcosgdf@gmail.com> * Copyright (C) 2015-2016 Marcos García <marcosgdf@gmail.com>
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@@ -106,6 +106,23 @@ function product_prepare_head($object)
$head[$h][2] = 'referers'; $head[$h][2] = 'referers';
$h++; $h++;
if (!empty($conf->attributes->enabled) && $object->isProduct()) {
global $db;
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductCombination.class.php';
$prodcomb = new ProductCombination($db);
if ($prodcomb->fetchByFkProductChild($object->id) == -1) {
$head[$h][0] = DOL_URL_ROOT."/attributes/combinations.php?id=".$object->id;
$head[$h][1] = $langs->trans('ProductCombinations');
$head[$h][2] = 'combinations';
}
$h++;
}
if ($object->isProduct() || ($object->isService() && ! empty($conf->global->STOCK_SUPPORTS_SERVICES))) // If physical product we can stock (or service with option) if ($object->isProduct() || ($object->isService() && ! empty($conf->global->STOCK_SUPPORTS_SERVICES))) // If physical product we can stock (or service with option)
{ {
if (! empty($conf->stock->enabled) && $user->rights->stock->lire) if (! empty($conf->stock->enabled) && $user->rights->stock->lire)

View File

@@ -5,7 +5,7 @@
* Copyright (C) 2012 Cédric Salvador <csalvador@gpcsolutions.fr> * Copyright (C) 2012 Cédric Salvador <csalvador@gpcsolutions.fr>
* Copyright (C) 2014 Florian Henry <florian.henry@open-concept.pro> * Copyright (C) 2014 Florian Henry <florian.henry@open-concept.pro>
* Copyright (C) 2014 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr> * Copyright (C) 2014 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr>
* Copyright (C) 2015 Marcos García <marcosgdf@gmail.com> * Copyright (C) 2015-2016 Marcos García <marcosgdf@gmail.com>
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@@ -52,7 +52,7 @@ if (in_array($object->element,array('propal', 'supplier_proposal','facture','fac
<!-- BEGIN PHP TEMPLATE objectline_create.tpl.php --> <!-- BEGIN PHP TEMPLATE objectline_create.tpl.php -->
<tr class="liste_titre liste_titre_add nodrag nodrop"> <tr id="addline" class="liste_titre liste_titre_add nodrag nodrop">
<td class="linecoldescription" <?php echo (! empty($conf->global->MAIN_VIEW_LINE_NUMBER) ? ' colspan="2"' : ''); ?>> <td class="linecoldescription" <?php echo (! empty($conf->global->MAIN_VIEW_LINE_NUMBER) ? ' colspan="2"' : ''); ?>>
<div id="add"></div><span class="hideonsmartphone"><?php echo $langs->trans('AddNewLine'); ?></span><?php // echo $langs->trans("FreeZone"); ?> <div id="add"></div><span class="hideonsmartphone"><?php echo $langs->trans('AddNewLine'); ?></span><?php // echo $langs->trans("FreeZone"); ?>
</td> </td>
@@ -178,7 +178,7 @@ else {
if (empty($senderissupplier)) if (empty($senderissupplier))
{ {
$form->select_produits(GETPOST('idprod'), 'idprod', $filtertype, $conf->product->limit_size, $buyer->price_level, 1, 2, '', 1, array(),$buyer->id); $form->select_produits(GETPOST('idprod'), 'idprod', $filtertype, $conf->product->limit_size, $buyer->price_level, 1, 2, '', 1, array(),$buyer->id, GETPOST('combinations', 'array'));
} }
else else
{ {
@@ -210,7 +210,14 @@ else {
} }
if (! empty($conf->product->enabled) || ! empty($conf->service->enabled)) echo '<br>'; if (! empty($conf->product->enabled) || ! empty($conf->service->enabled)) {
if (!empty($conf->attributes->enabled)) {
echo '<div id="attributes_box"></div>';
}
echo '<br>';
}
// Editor wysiwyg // Editor wysiwyg
require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/doleditor.class.php';
@@ -532,9 +539,9 @@ jQuery(document).ready(function() {
$("#idprod, #idprodfournprice").change(function() $("#idprod, #idprodfournprice").change(function()
{ {
console.log("#idprod, #idprodfournprice change triggered"); console.log("#idprod, #idprodfournprice change triggered");
setforpredef(); // TODO Keep vat combo visible and set it to first entry into list that match result of get_default_tva setforpredef(); // TODO Keep vat combo visible and set it to first entry into list that match result of get_default_tva
jQuery('#trlinefordates').show(); jQuery('#trlinefordates').show();
<?php <?php
@@ -555,13 +562,13 @@ jQuery(document).ready(function() {
var defaultkey = ''; var defaultkey = '';
var defaultprice = ''; var defaultprice = '';
var bestpricefound = 0; var bestpricefound = 0;
var bestpriceid = 0; var bestpricevalue = 0; var bestpriceid = 0; var bestpricevalue = 0;
var pmppriceid = 0; var pmppricevalue = 0; var pmppriceid = 0; var pmppricevalue = 0;
var costpriceid = 0; var costpricevalue = 0; var costpriceid = 0; var costpricevalue = 0;
/* setup of margin calculation */ /* setup of margin calculation */
var defaultbuyprice = '<?php var defaultbuyprice = '<?php
if (isset($conf->global->MARGIN_TYPE)) if (isset($conf->global->MARGIN_TYPE))
{ {
if ($conf->global->MARGIN_TYPE == '1') print 'bestsupplierprice'; if ($conf->global->MARGIN_TYPE == '1') print 'bestsupplierprice';
@@ -569,7 +576,7 @@ jQuery(document).ready(function() {
if ($conf->global->MARGIN_TYPE == 'costprice') print 'costprice'; if ($conf->global->MARGIN_TYPE == 'costprice') print 'costprice';
} ?>'; } ?>';
console.log("we will set the field for margin. defaultbuyprice="+defaultbuyprice); console.log("we will set the field for margin. defaultbuyprice="+defaultbuyprice);
var i = 0; var i = 0;
$(data).each(function() { $(data).each(function() {
if (this.id != 'pmpprice' && this.id != 'costprice') if (this.id != 'pmpprice' && this.id != 'costprice')
@@ -586,7 +593,7 @@ jQuery(document).ready(function() {
//console.log("id="+this.id+"-price="+this.price); //console.log("id="+this.id+"-price="+this.price);
if ('pmp' == defaultbuyprice || 'costprice' == defaultbuyprice) if ('pmp' == defaultbuyprice || 'costprice' == defaultbuyprice)
{ {
if (this.price > 0) { if (this.price > 0) {
defaultkey = this.id; defaultprice = this.price; pmppriceid = this.id; pmppricevalue = this.price; defaultkey = this.id; defaultprice = this.price; pmppriceid = this.id; pmppricevalue = this.price;
//console.log("pmppricevalue="+pmppricevalue); //console.log("pmppricevalue="+pmppricevalue);
} }
@@ -605,22 +612,22 @@ jQuery(document).ready(function() {
options += '<option value="'+this.id+'" price="'+this.price+'">'+this.label+'</option>'; options += '<option value="'+this.id+'" price="'+this.price+'">'+this.label+'</option>';
}); });
options += '<option value="inputprice" price="'+defaultprice+'"><?php echo $langs->trans("InputPrice"); ?></option>'; options += '<option value="inputprice" price="'+defaultprice+'"><?php echo $langs->trans("InputPrice"); ?></option>';
console.log("finally selected defaultkey="+defaultkey+" defaultprice="+defaultprice); console.log("finally selected defaultkey="+defaultkey+" defaultprice="+defaultprice);
$("#fournprice_predef").html(options).show(); $("#fournprice_predef").html(options).show();
if (defaultkey != '') if (defaultkey != '')
{ {
$("#fournprice_predef").val(defaultkey); $("#fournprice_predef").val(defaultkey);
} }
/* At loading, no product are yet selected, so we hide field of buying_price */ /* At loading, no product are yet selected, so we hide field of buying_price */
$("#buying_price").hide(); $("#buying_price").hide();
/* Define default price at loading */ /* Define default price at loading */
var defaultprice = $("#fournprice_predef").find('option:selected').attr("price"); var defaultprice = $("#fournprice_predef").find('option:selected').attr("price");
$("#buying_price").val(defaultprice); $("#buying_price").val(defaultprice);
$("#fournprice_predef").change(function() { $("#fournprice_predef").change(function() {
console.log("change on fournprice_predef"); console.log("change on fournprice_predef");
/* Hide field buying_price according to choice into list (if 'inputprice' or not) */ /* Hide field buying_price according to choice into list (if 'inputprice' or not) */
@@ -672,8 +679,8 @@ function setforfree() {
jQuery("#idprod").val(''); jQuery("#idprod").val('');
jQuery("#idprodfournprice").val('0'); // Set cursor on not selected product jQuery("#idprodfournprice").val('0'); // Set cursor on not selected product
jQuery("#search_idprodfournprice").val(''); jQuery("#search_idprodfournprice").val('');
jQuery("#prod_entry_mode_free").prop('checked',true); jQuery("#prod_entry_mode_free").prop('checked',true).change();
jQuery("#prod_entry_mode_predef").prop('checked',false); jQuery("#prod_entry_mode_predef").prop('checked',false).change();
jQuery("#price_ht").show(); jQuery("#price_ht").show();
jQuery("#price_ttc").show(); // May no exists jQuery("#price_ttc").show(); // May no exists
jQuery("#tva_tx").show(); jQuery("#tva_tx").show();
@@ -691,8 +698,8 @@ function setforfree() {
function setforpredef() { function setforpredef() {
console.log("Call setforpredef. We hide some fields"); console.log("Call setforpredef. We hide some fields");
jQuery("#select_type").val(-1); jQuery("#select_type").val(-1);
jQuery("#prod_entry_mode_free").prop('checked',false); jQuery("#prod_entry_mode_free").prop('checked',false).change();
jQuery("#prod_entry_mode_predef").prop('checked',true); jQuery("#prod_entry_mode_predef").prop('checked',true).change();
jQuery("#price_ht").hide(); jQuery("#price_ht").hide();
jQuery("#price_ttc").hide(); // May no exists jQuery("#price_ttc").hide(); // May no exists
jQuery("#tva_tx").hide(); jQuery("#tva_tx").hide();

View File

@@ -5,7 +5,7 @@
* Copyright (C) 2005-2016 Regis Houssin <regis.houssin@capnetworks.com> * Copyright (C) 2005-2016 Regis Houssin <regis.houssin@capnetworks.com>
* Copyright (C) 2010-2015 Juanjo Menent <jmenent@2byte.es> * Copyright (C) 2010-2015 Juanjo Menent <jmenent@2byte.es>
* Copyright (C) 2011-2015 Philippe Grand <philippe.grand@atoo-net.com> * Copyright (C) 2011-2015 Philippe Grand <philippe.grand@atoo-net.com>
* Copyright (C) 2012 Marcos García <marcosgdf@gmail.com> * Copyright (C) 2012-2016 Marcos García <marcosgdf@gmail.com>
* Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro> * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
* Copyright (C) 2014 Ion Agorria <ion@agorria.com> * Copyright (C) 2014 Ion Agorria <ion@agorria.com>
* *
@@ -50,6 +50,10 @@ if (!empty($conf->projet->enabled)) {
} }
require_once NUSOAP_PATH.'/nusoap.php'; // Include SOAP require_once NUSOAP_PATH.'/nusoap.php'; // Include SOAP
if (!empty($conf->attributes->enabled)) {
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductCombination.class.php';
}
$langs->load('admin'); $langs->load('admin');
$langs->load('orders'); $langs->load('orders');
$langs->load('sendings'); $langs->load('sendings');
@@ -285,7 +289,7 @@ if (empty($reshook))
$product_desc=(GETPOST('dp_desc')?GETPOST('dp_desc'):''); $product_desc=(GETPOST('dp_desc')?GETPOST('dp_desc'):'');
$date_start=dol_mktime(GETPOST('date_start'.$predef.'hour'), GETPOST('date_start'.$predef.'min'), GETPOST('date_start' . $predef . 'sec'), GETPOST('date_start'.$predef.'month'), GETPOST('date_start'.$predef.'day'), GETPOST('date_start'.$predef.'year')); $date_start=dol_mktime(GETPOST('date_start'.$predef.'hour'), GETPOST('date_start'.$predef.'min'), GETPOST('date_start' . $predef . 'sec'), GETPOST('date_start'.$predef.'month'), GETPOST('date_start'.$predef.'day'), GETPOST('date_start'.$predef.'year'));
$date_end=dol_mktime(GETPOST('date_end'.$predef.'hour'), GETPOST('date_end'.$predef.'min'), GETPOST('date_end' . $predef . 'sec'), GETPOST('date_end'.$predef.'month'), GETPOST('date_end'.$predef.'day'), GETPOST('date_end'.$predef.'year')); $date_end=dol_mktime(GETPOST('date_end'.$predef.'hour'), GETPOST('date_end'.$predef.'min'), GETPOST('date_end' . $predef . 'sec'), GETPOST('date_end'.$predef.'month'), GETPOST('date_end'.$predef.'day'), GETPOST('date_end'.$predef.'year'));
if (GETPOST('prod_entry_mode') == 'free') if ($prod_entry_mode == 'free')
{ {
$idprod=0; $idprod=0;
$price_ht = GETPOST('price_ht'); $price_ht = GETPOST('price_ht');
@@ -313,22 +317,22 @@ if (empty($reshook))
} }
} }
if (GETPOST('prod_entry_mode')=='free' && GETPOST('price_ht') < 0 && $qty < 0) if ($prod_entry_mode =='free' && GETPOST('price_ht') < 0 && $qty < 0)
{ {
setEventMessages($langs->trans('ErrorBothFieldCantBeNegative', $langs->transnoentitiesnoconv('UnitPrice'), $langs->transnoentitiesnoconv('Qty')), null, 'errors'); setEventMessages($langs->trans('ErrorBothFieldCantBeNegative', $langs->transnoentitiesnoconv('UnitPrice'), $langs->transnoentitiesnoconv('Qty')), null, 'errors');
$error++; $error++;
} }
if (GETPOST('prod_entry_mode')=='free' && ! GETPOST('idprodfournprice') && GETPOST('type') < 0) if ($prod_entry_mode =='free' && ! GETPOST('idprodfournprice') && GETPOST('type') < 0)
{ {
setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('Type')), null, 'errors'); setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('Type')), null, 'errors');
$error++; $error++;
} }
if (GETPOST('prod_entry_mode')=='free' && GETPOST('price_ht')==='' && GETPOST('price_ttc')==='') // Unit price can be 0 but not '' if ($prod_entry_mode =='free' && GETPOST('price_ht')==='' && GETPOST('price_ttc')==='') // Unit price can be 0 but not ''
{ {
setEventMessages($langs->trans($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('UnitPrice'))), null, 'errors'); setEventMessages($langs->trans($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('UnitPrice'))), null, 'errors');
$error++; $error++;
} }
if (GETPOST('prod_entry_mode')=='free' && ! GETPOST('dp_desc')) if ($prod_entry_mode =='free' && ! GETPOST('dp_desc'))
{ {
setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('Description')), null, 'errors'); setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('Description')), null, 'errors');
$error++; $error++;
@@ -339,10 +343,24 @@ if (empty($reshook))
$error++; $error++;
} }
// Ecrase $pu par celui du produit if (!$error && !empty($conf->attributes->enabled) && $prod_entry_mode != 'free') {
if ($combinations = GETPOST('combinations', 'array')) {
//Check if there is a product with the given combination
$prodcomb = new ProductCombination($db);
if ($res = $prodcomb->fetchByProductCombination2ValuePairs($idprod, $combinations)) {
$idprod = $res->fk_product_child;
} else {
setEventMessage($langs->trans('ErrorProductCombinationNotFound'), 'errors');
$error ++;
}
}
}
// Ecrase $pu par celui du produit
// Ecrase $desc par celui du produit // Ecrase $desc par celui du produit
// Ecrase $txtva par celui du produit // Ecrase $txtva par celui du produit
if ((GETPOST('prod_entry_mode') != 'free') && empty($error)) // With combolist mode idprodfournprice is > 0 or -1. With autocomplete, idprodfournprice is > 0 or '' if (($prod_entry_mode != 'free') && empty($error)) // With combolist mode idprodfournprice is > 0 or -1. With autocomplete, idprodfournprice is > 0 or ''
{ {
$productsupplier = new ProductFournisseur($db); $productsupplier = new ProductFournisseur($db);

View File

@@ -7,7 +7,7 @@
* Copyright (C) 2010-2014 Juanjo Menent <jmenent@2byte.es> * Copyright (C) 2010-2014 Juanjo Menent <jmenent@2byte.es>
* Copyright (C) 2013-2015 Philippe Grand <philippe.grand@atoo-net.com> * Copyright (C) 2013-2015 Philippe Grand <philippe.grand@atoo-net.com>
* Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro> * Copyright (C) 2013 Florian Henry <florian.henry@open-concept.pro>
* Copyright (C) 2014 Marcos García <marcosgdf@gmail.com> * Copyright (C) 2014-2016 Marcos García <marcosgdf@gmail.com>
* *
* This program is free software; you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@@ -45,6 +45,10 @@ if (!empty($conf->projet->enabled)) {
require_once DOL_DOCUMENT_ROOT.'/core/class/html.formprojet.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/html.formprojet.class.php';
} }
if (!empty($conf->attributes->enabled)) {
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductCombination.class.php';
}
$langs->load('bills'); $langs->load('bills');
$langs->load('compta'); $langs->load('compta');
@@ -668,7 +672,8 @@ if (empty($reshook))
// Set if we used free entry or predefined product // Set if we used free entry or predefined product
$predef=''; $predef='';
$product_desc=(GETPOST('dp_desc')?GETPOST('dp_desc'):''); $product_desc=(GETPOST('dp_desc')?GETPOST('dp_desc'):'');
if (GETPOST('prod_entry_mode') == 'free') $prod_entry_mode = GETPOST('prod_entry_mode');
if ($prod_entry_mode == 'free')
{ {
$idprod=0; $idprod=0;
$price_ht = GETPOST('price_ht'); $price_ht = GETPOST('price_ht');
@@ -699,22 +704,22 @@ if (empty($reshook))
} }
} }
if (GETPOST('prod_entry_mode')=='free' && GETPOST('price_ht') < 0 && $qty < 0) if ($prod_entry_mode =='free' && GETPOST('price_ht') < 0 && $qty < 0)
{ {
setEventMessages($langs->trans('ErrorBothFieldCantBeNegative', $langs->transnoentitiesnoconv('UnitPrice'), $langs->transnoentitiesnoconv('Qty')), null, 'errors'); setEventMessages($langs->trans('ErrorBothFieldCantBeNegative', $langs->transnoentitiesnoconv('UnitPrice'), $langs->transnoentitiesnoconv('Qty')), null, 'errors');
$error++; $error++;
} }
if (GETPOST('prod_entry_mode')=='free' && ! GETPOST('idprodfournprice') && GETPOST('type') < 0) if ($prod_entry_mode =='free' && ! GETPOST('idprodfournprice') && GETPOST('type') < 0)
{ {
setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('Type')), null, 'errors'); setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('Type')), null, 'errors');
$error++; $error++;
} }
if (GETPOST('prod_entry_mode')=='free' && GETPOST('price_ht')==='' && GETPOST('price_ttc')==='') // Unit price can be 0 but not '' if ($prod_entry_mode =='free' && GETPOST('price_ht')==='' && GETPOST('price_ttc')==='') // Unit price can be 0 but not ''
{ {
setEventMessages($langs->trans($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('UnitPrice'))), null, 'errors'); setEventMessages($langs->trans($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('UnitPrice'))), null, 'errors');
$error++; $error++;
} }
if (GETPOST('prod_entry_mode')=='free' && ! GETPOST('dp_desc')) if ($prod_entry_mode =='free' && ! GETPOST('dp_desc'))
{ {
setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('Description')), null, 'errors'); setEventMessages($langs->trans('ErrorFieldRequired', $langs->transnoentitiesnoconv('Description')), null, 'errors');
$error++; $error++;
@@ -725,7 +730,21 @@ if (empty($reshook))
$error++; $error++;
} }
if (GETPOST('prod_entry_mode') != 'free') // With combolist mode idprodfournprice is > 0 or -1. With autocomplete, idprodfournprice is > 0 or '' if (!$error && !empty($conf->attributes->enabled) && $prod_entry_mode != 'free') {
if ($combinations = GETPOST('combinations', 'array')) {
//Check if there is a product with the given combination
$prodcomb = new ProductCombination($db);
if ($res = $prodcomb->fetchByProductCombination2ValuePairs($idprod, $combinations)) {
$idprod = $res->fk_product_child;
} else {
setEventMessage($langs->trans('ErrorProductCombinationNotFound'), 'errors');
$error ++;
}
}
}
if ($prod_entry_mode != 'free') // With combolist mode idprodfournprice is > 0 or -1. With autocomplete, idprodfournprice is > 0 or ''
{ {
$idprod=0; $idprod=0;
$productsupplier=new ProductFournisseur($db); $productsupplier=new ProductFournisseur($db);

View File

@@ -515,4 +515,3 @@ CREATE TABLE llx_oauth_state (
ALTER TABLE llx_product_batch ADD UNIQUE INDEX uk_product_batch (fk_product_stock, batch); ALTER TABLE llx_product_batch ADD UNIQUE INDEX uk_product_batch (fk_product_stock, batch);

View File

@@ -39,3 +39,39 @@ DROP TABLE llx_ecm_documents;
ALTER TABLE llx_notify ADD COLUMN type_target varchar(16) NULL; ALTER TABLE llx_notify ADD COLUMN type_target varchar(16) NULL;
-- Product attributes
CREATE TABLE llx_product_attribute
(
rowid INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
ref VARCHAR(255) NOT NULL,
label VARCHAR(255) NOT NULL,
rang INT DEFAULT 0 NOT NULL,
entity INT DEFAULT 1 NOT NULL
);
ALTER TABLE llx_product_attribute ADD CONSTRAINT unique_ref UNIQUE (ref);
CREATE TABLE llx_product_attribute_combination
(
rowid INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
fk_product_parent INT NOT NULL,
fk_product_child INT NOT NULL,
variation_price FLOAT NOT NULL,
variation_price_percentage INT NULL,
variation_weight FLOAT NOT NULL,
entity INT DEFAULT 1 NOT NULL
);
CREATE TABLE llx_product_attribute_combination2val
(
rowid INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
fk_prod_combination INT NOT NULL,
fk_prod_attr INT NOT NULL,
fk_prod_attr_val INT NOT NULL
);
CREATE TABLE llx_product_attribute_value
(
rowid INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
fk_product_attribute INT NOT NULL,
ref VARCHAR(255) DEFAULT NULL,
value VARCHAR(255) DEFAULT NULL,
entity INT DEFAULT 1 NOT NULL
);
ALTER TABLE llx_product_attribute_value ADD CONSTRAINT unique_ref UNIQUE (fk_product_attribute,ref);

View File

@@ -1,5 +1,5 @@
-- =================================================================== -- ============================================================================
-- Copyright (C) 2012 Laurent Destailleur <eldy@users.sourceforge.net> -- Copyright (C) 2016 Marcos García <marcosgdf@gmail.com>
-- --
-- This program is free software; you can redistribute it and/or modify -- 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 -- it under the terms of the GNU General Public License as published by
@@ -14,6 +14,6 @@
-- You should have received a copy of the GNU General Public License -- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>. -- along with this program. If not, see <http://www.gnu.org/licenses/>.
-- --
-- =================================================================== -- ============================================================================
ALTER TABLE llx_holiday_events ADD UNIQUE INDEX uk_holiday_name (name, entity); ALTER TABLE llx_product_attribute ADD CONSTRAINT unique_ref UNIQUE (ref);

View File

@@ -1,5 +1,5 @@
-- =================================================================== -- ============================================================================
-- Copyright (C) 2012 Laurent Destailleur <eldy@users.sourceforge.net> -- Copyright (C) 2016 Marcos García <marcosgdf@gmail.com>
-- --
-- This program is free software; you can redistribute it and/or modify -- 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 -- it under the terms of the GNU General Public License as published by
@@ -14,13 +14,13 @@
-- You should have received a copy of the GNU General Public License -- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>. -- along with this program. If not, see <http://www.gnu.org/licenses/>.
-- --
-- =================================================================== -- ============================================================================
CREATE TABLE llx_holiday_events CREATE TABLE llx_product_attribute
( (
rowid integer NOT NULL PRIMARY KEY AUTO_INCREMENT, rowid INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
entity integer DEFAULT 1 NOT NULL, -- multi company id ref VARCHAR(255) NOT NULL,
name VARCHAR( 255 ) NOT NULL, label VARCHAR(255) NOT NULL,
value TEXT NOT NULL rang INT DEFAULT 0 NOT NULL,
) entity INT DEFAULT 1 NOT NULL
ENGINE=innodb; );

View File

@@ -0,0 +1,28 @@
-- ============================================================================
-- Copyright (C) 2016 Marcos García <marcosgdf@gmail.com>
--
-- 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 <http://www.gnu.org/licenses/>.
--
-- ============================================================================
CREATE TABLE llx_product_attribute_combination
(
rowid INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
fk_product_parent INT NOT NULL,
fk_product_child INT NOT NULL,
variation_price FLOAT NOT NULL,
variation_price_percentage INT NULL,
variation_weight FLOAT NOT NULL,
entity INT DEFAULT 1 NOT NULL
);

View File

@@ -0,0 +1,25 @@
-- ============================================================================
-- Copyright (C) 2016 Marcos García <marcosgdf@gmail.com>
--
-- 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 <http://www.gnu.org/licenses/>.
--
-- ============================================================================
CREATE TABLE llx_product_attribute_combination2val
(
rowid INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
fk_prod_combination INT NOT NULL,
fk_prod_attr INT NOT NULL,
fk_prod_attr_val INT NOT NULL
);

View File

@@ -0,0 +1,19 @@
-- ============================================================================
-- Copyright (C) 2016 Marcos García <marcosgdf@gmail.com>
--
-- 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 <http://www.gnu.org/licenses/>.
--
-- ============================================================================
ALTER TABLE llx_product_attribute_value ADD CONSTRAINT unique_ref UNIQUE (fk_product_attribute,ref);

View File

@@ -0,0 +1,26 @@
-- ============================================================================
-- Copyright (C) 2016 Marcos García <marcosgdf@gmail.com>
--
-- 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 <http://www.gnu.org/licenses/>.
--
-- ============================================================================
CREATE TABLE llx_product_attribute_value
(
rowid INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
fk_product_attribute INT NOT NULL,
ref VARCHAR(255) DEFAULT NULL,
value VARCHAR(255) DEFAULT NULL,
entity INT DEFAULT 1 NOT NULL
);

View File

@@ -759,3 +759,5 @@ SearchIntoContracts=Contracts
SearchIntoCustomerShipments=Customer shipments SearchIntoCustomerShipments=Customer shipments
SearchIntoExpenseReports=Expense reports SearchIntoExpenseReports=Expense reports
SearchIntoLeaves=Leaves SearchIntoLeaves=Leaves
BulkActions=Bulk actions

View File

@@ -137,6 +137,7 @@ ConfirmCloneProduct=Are you sure you want to clone product or service <b>%s</b>
CloneContentProduct=Clone all main informations of product/service CloneContentProduct=Clone all main informations of product/service
ClonePricesProduct=Clone main informations and prices ClonePricesProduct=Clone main informations and prices
CloneCompositionProduct=Clone packaged product/service CloneCompositionProduct=Clone packaged product/service
CloneCombinationsProduct=Clone product combinations
ProductIsUsed=This product is used ProductIsUsed=This product is used
NewRefForClone=Ref. of new product/service NewRefForClone=Ref. of new product/service
SellingPrices=Selling prices SellingPrices=Selling prices
@@ -226,7 +227,7 @@ ComposedProduct=Sub-product
MinSupplierPrice=Minimum supplier price MinSupplierPrice=Minimum supplier price
MinCustomerPrice=Minimum customer price MinCustomerPrice=Minimum customer price
DynamicPriceConfiguration=Dynamic price configuration DynamicPriceConfiguration=Dynamic price configuration
DynamicPriceDesc=On product card, with this module enabled, you should be able to set mathematic functions to calculate Customer or Supplier prices. Such function can use all mathematic operators, some constants and variables. You can set here the variables you want to be able and if the variable need an automatic update, the external URL to use to ask Dolibarr to update automaticaly the value. DynamicPriceDesc=On product card, with this module enabled, you should be able to set mathematic functions to calculate Customer or Supplier prices. Such function can use all mathematic operators, some constants and variables. You can set here the variables you want to be able and if the variable need an automatic update, the external URL to use to ask Dolibarr to update automaticaly the value.
AddVariable=Add Variable AddVariable=Add Variable
AddUpdater=Add Updater AddUpdater=Add Updater
GlobalVariables=Global variables GlobalVariables=Global variables
@@ -254,3 +255,38 @@ SizeUnits=Size unit
DeleteProductBuyPrice=Delete buying price DeleteProductBuyPrice=Delete buying price
ConfirmDeleteProductBuyPrice=Are you sure you want to delete this buying price? ConfirmDeleteProductBuyPrice=Are you sure you want to delete this buying price?
#Attributes
ProductAttributes=Product attributes
ProductAttributeName=Attribute %s
ProductAttribute=Attribute
ProductAttributeDeleteDialog=Are you sure you want to delete this attribute? All values will be deleted
ProductAttributeValueDeleteDialog=Are you sure you want to delete the value "%s" with reference "%s" of this attribute?
ProductCombinationDeleteDialog=Are you sure want to delete the combination of the product "%s"?
ProductCombinationAlreadyUsed=There was an error while deleting the combination. Please check it is not being used in any object
ProductCombinations=Combinations
HideProductCombinations=Hide derived products from combinations in the product selector
ProductCombination=Combination
NewProductCombination=New combination
EditProductCombination=Editing combination
ProductCombinationGenerator=Combinations generator
Features=Features
PriceImpact=Price impact
WeightImpact=Weight impact
NewProductAttribute=New attribute
NewProductAttributeValue=New attribute value
ErrorCreatingProductAttributeValue=There was an error while creating the attribute value. It could be because there is already an existing value with that reference
ProductCombinationGeneratorWarning=If you continue, before generating new combinations, all previous ones will be DELETED. Already existing ones will be updated with the new values
TooMuchCombinationsWarning=Generating lots of combinations may result in high CPU, memory usage and Dolibarr not able to create them. Enabling the option "%s" may help reduce memory usage.
DoNotRemovePreviousCombinations=Do not remove previous combinations
UsePercentageVariations=Use percentage variations
PercentageVariation=Percentage variation
ErrorDeletingGeneratedProducts=There was an error while trying to delete existing product combinations
NbProducts=No. of products
ParentProduct=Parent product
HideChildProducts=Hide child products
ConfirmCloneProductCombinations=Would you like to copy all the product combinations to the product with the given reference?
CloneDestinationReference=Destination product reference
ErrorCopyProductCombinations=There was an error while copying the product combinations
ErrorDestinationProductNotFound=Destination product not found
ErrorProductCombinationNotFound=Product combination not found

View File

@@ -6,7 +6,7 @@
* Copyright (C) 2006 Andre Cianfarani <acianfa@free.fr> * Copyright (C) 2006 Andre Cianfarani <acianfa@free.fr>
* Copyright (C) 2006 Auguria SARL <info@auguria.org> * Copyright (C) 2006 Auguria SARL <info@auguria.org>
* Copyright (C) 2010-2015 Juanjo Menent <jmenent@2byte.es> * Copyright (C) 2010-2015 Juanjo Menent <jmenent@2byte.es>
* Copyright (C) 2013-2014 Marcos García <marcosgdf@gmail.com> * Copyright (C) 2013-2016 Marcos García <marcosgdf@gmail.com>
* Copyright (C) 2012-2013 Cédric Salvador <csalvador@gpcsolutions.fr> * Copyright (C) 2012-2013 Cédric Salvador <csalvador@gpcsolutions.fr>
* Copyright (C) 2011-2016 Alexandre Spangaro <aspangaro.dolibarr@gmail.com> * Copyright (C) 2011-2016 Alexandre Spangaro <aspangaro.dolibarr@gmail.com>
* Copyright (C) 2014 Cédric Gross <c.gross@kreiz-it.fr> * Copyright (C) 2014 Cédric Gross <c.gross@kreiz-it.fr>
@@ -1574,6 +1574,22 @@ else
print dol_print_url($object->url); print dol_print_url($object->url);
print '</td></tr>'; print '</td></tr>';
//Parent product.
if (!empty($conf->attributes->enabled) && $object->isProduct()) {
$combination = new ProductCombination($db);
if ($combination->fetchByFkProductChild($object->id) > 0) {
$prodstatic = new Product($db);
$prodstatic->fetch($combination->fk_product_parent);
// Parent product
print '<tr><td>'.$langs->trans("ParentProduct").'</td><td colspan="2">';
print $prodstatic->getNomUrl(1);
print '</td></tr>';
}
}
print '</table>'; print '</table>';
print '</div>'; print '</div>';
print '<div class="fichehalfright"><div class="ficheaddleft">'; print '<div class="fichehalfright"><div class="ficheaddleft">';

View File

@@ -839,6 +839,17 @@ class Product extends CommonObject
if (! $error) if (! $error)
{ {
if ($conf->attributes->enabled) {
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductCombination.class.php';
$comb = new ProductCombination($this->db);
foreach ($comb->fetchAllByFkProductParent($this->id) as $currcomb) {
$currcomb->updateProperties($this);
}
}
$this->db->commit(); $this->db->commit();
return 1; return 1;
} }
@@ -944,6 +955,25 @@ class Product extends CommonObject
} }
} }
if (!$error) {
require_once DOL_DOCUMENT_ROOT.'/attributes/class/ProductCombination.class.php';
//If it is a parent product, then we remove the association with child products
$prodcomb = new ProductCombination($this->db);
if ($prodcomb->deleteByFkProductParent($id) < 0) {
$error++;
$this->errors[] = 'Error deleting combinations';
}
//We also check if it is a child product
if (!$error && ($prodcomb->fetchByFkProductChild($id) > 0) && ($prodcomb->delete() < 0)) {
$error++;
$this->errors[] = 'Error deleting child combination';
}
}
// Delete product // Delete product
if (! $error) if (! $error)
{ {

View File

@@ -2,7 +2,7 @@
/* Copyright (C) 2001-2006 Rodolphe Quiedeville <rodolphe@quiedeville.org> /* Copyright (C) 2001-2006 Rodolphe Quiedeville <rodolphe@quiedeville.org>
* Copyright (C) 2004-2016 Laurent Destailleur <eldy@users.sourceforge.net> * Copyright (C) 2004-2016 Laurent Destailleur <eldy@users.sourceforge.net>
* Copyright (C) 2005-2012 Regis Houssin <regis.houssin@capnetworks.com> * Copyright (C) 2005-2012 Regis Houssin <regis.houssin@capnetworks.com>
* Copyright (C) 2012-2013 Marcos García <marcosgdf@gmail.com> * Copyright (C) 2012-2016 Marcos García <marcosgdf@gmail.com>
* Copyright (C) 2013-2016 Juanjo Menent <jmenent@2byte.es> * Copyright (C) 2013-2016 Juanjo Menent <jmenent@2byte.es>
* Copyright (C) 2013-2015 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr> * Copyright (C) 2013-2015 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr>
* Copyright (C) 2013 Jean Heimburger <jean@tiaris.info> * Copyright (C) 2013 Jean Heimburger <jean@tiaris.info>
@@ -50,7 +50,7 @@ $sref=GETPOST("sref");
$sbarcode=GETPOST("sbarcode"); $sbarcode=GETPOST("sbarcode");
$snom=GETPOST("snom"); $snom=GETPOST("snom");
$sall=GETPOST("sall"); $sall=GETPOST("sall");
$type=GETPOST("type","int"); $type= (int) GETPOST("type","int");
$search_sale = GETPOST("search_sale"); $search_sale = GETPOST("search_sale");
$search_categ = GETPOST("search_categ",'int'); $search_categ = GETPOST("search_categ",'int');
$tosell = GETPOST("tosell", 'int'); $tosell = GETPOST("tosell", 'int');
@@ -62,6 +62,13 @@ $search_accountancy_code_sell = GETPOST("search_accountancy_code_sell",'alpha');
$search_accountancy_code_buy = GETPOST("search_accountancy_code_buy",'alpha'); $search_accountancy_code_buy = GETPOST("search_accountancy_code_buy",'alpha');
$optioncss = GETPOST('optioncss','alpha'); $optioncss = GETPOST('optioncss','alpha');
//Show/hide child products. Hidden by default
if (!$_POST) {
$search_hidechildproducts = 'on';
} else {
$search_hidechildproducts = GETPOST('search_hidechildproducts');
}
$limit = GETPOST("limit")?GETPOST("limit","int"):$conf->liste_limit; $limit = GETPOST("limit")?GETPOST("limit","int"):$conf->liste_limit;
$sortfield = GETPOST("sortfield",'alpha'); $sortfield = GETPOST("sortfield",'alpha');
$sortorder = GETPOST("sortorder",'alpha'); $sortorder = GETPOST("sortorder",'alpha');
@@ -170,7 +177,7 @@ if (is_array($extrafields->attribute_label) && count($extrafields->attribute_lab
} }
/* /*
* Actions * Actions
*/ */
@@ -230,6 +237,9 @@ else
$sql.= ' p.datec as date_creation, p.tms as date_update,'; $sql.= ' p.datec as date_creation, p.tms as date_update,';
//$sql.= ' pfp.ref_fourn as ref_supplier, '; //$sql.= ' pfp.ref_fourn as ref_supplier, ';
$sql.= ' MIN(pfp.unitprice) as minsellprice'; $sql.= ' MIN(pfp.unitprice) as minsellprice';
if (!empty($conf->attributes->enabled) && $search_hidechildproducts && ($type === 0)) {
$sql .= ', pac.rowid prod_comb_id';
}
// Add fields from extrafields // Add fields from extrafields
foreach ($extrafields->attribute_label as $key => $val) $sql.=($extrafields->attribute_type[$key] != 'separate' ? ",ef.".$key.' as options_'.$key : ''); foreach ($extrafields->attribute_label as $key => $val) $sql.=($extrafields->attribute_type[$key] != 'separate' ? ",ef.".$key.' as options_'.$key : '');
// Add fields from hooks // Add fields from hooks
@@ -242,6 +252,10 @@ else
$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."product_fournisseur_price as pfp ON p.rowid = pfp.fk_product"; $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."product_fournisseur_price as pfp ON p.rowid = pfp.fk_product";
// multilang // multilang
if (! empty($conf->global->MAIN_MULTILANGS)) $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."product_lang as pl ON pl.fk_product = p.rowid AND pl.lang = '".$langs->getDefaultLang() ."'"; if (! empty($conf->global->MAIN_MULTILANGS)) $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."product_lang as pl ON pl.fk_product = p.rowid AND pl.lang = '".$langs->getDefaultLang() ."'";
if (!empty($conf->attributes->enabled) && $search_hidechildproducts && ($type === 0)) {
$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product_attribute_combination pac ON pac.fk_product_child = p.rowid";
}
$sql.= ' WHERE p.entity IN ('.getEntity('product', 1).')'; $sql.= ' WHERE p.entity IN ('.getEntity('product', 1).')';
if ($sall) $sql .= natural_search(array_keys($fieldstosearchall), $sall); if ($sall) $sql .= natural_search(array_keys($fieldstosearchall), $sall);
// if the type is not 1, we show all products (type = 0,2,3) // if the type is not 1, we show all products (type = 0,2,3)
@@ -265,6 +279,12 @@ else
if ($search_accountancy_code_sell) $sql.= natural_search('p.accountancy_code_sell', $search_accountancy_code_sell); if ($search_accountancy_code_sell) $sql.= natural_search('p.accountancy_code_sell', $search_accountancy_code_sell);
if ($search_accountancy_code_sell) $sql.= natural_search('p.accountancy_code_buy', $search_accountancy_code_buy); if ($search_accountancy_code_sell) $sql.= natural_search('p.accountancy_code_buy', $search_accountancy_code_buy);
// Add where from extra fields // Add where from extra fields
if (!empty($conf->attributes->enabled) && $search_hidechildproducts && ($type === 0)) {
$sql .= " AND pac.rowid IS NULL";
}
// Add where from extra fields
foreach ($search_array_options as $key => $val) foreach ($search_array_options as $key => $val)
{ {
$crit=$val; $crit=$val;
@@ -284,6 +304,9 @@ else
$sql.= " GROUP BY p.rowid, p.ref, p.label, p.barcode, p.price, p.price_ttc, p.price_base_type,"; $sql.= " GROUP BY p.rowid, p.ref, p.label, p.barcode, p.price, p.price_ttc, p.price_base_type,";
$sql.= " p.fk_product_type, p.duration, p.tosell, p.tobuy, p.seuil_stock_alerte, p.desiredstock,"; $sql.= " p.fk_product_type, p.duration, p.tosell, p.tobuy, p.seuil_stock_alerte, p.desiredstock,";
$sql.= ' p.datec, p.tms, p.entity, p.tobatch, p.accountancy_code_sell, p.accountancy_code_buy'; $sql.= ' p.datec, p.tms, p.entity, p.tobatch, p.accountancy_code_sell, p.accountancy_code_buy';
if (!empty($conf->attributes->enabled) && $search_hidechildproducts && ($type === 0)) {
$sql .= ', pac.rowid';
}
// Add fields from extrafields // Add fields from extrafields
foreach ($extrafields->attribute_label as $key => $val) $sql.=($extrafields->attribute_type[$key] != 'separate' ? ",ef.".$key : ''); foreach ($extrafields->attribute_label as $key => $val) $sql.=($extrafields->attribute_type[$key] != 'separate' ? ",ef.".$key : '');
// Add fields from hooks // Add fields from hooks
@@ -415,6 +438,15 @@ else
$moreforfilter.=$htmlother->select_categories(Categorie::TYPE_PRODUCT,$search_categ,'search_categ',1); $moreforfilter.=$htmlother->select_categories(Categorie::TYPE_PRODUCT,$search_categ,'search_categ',1);
$moreforfilter.='</div>'; $moreforfilter.='</div>';
} }
//Show/hide child products. Hidden by default
if (!empty($conf->attributes->enabled) && $type === 0) {
$moreforfilter.='<div class="divsearchfield">';
$moreforfilter.= '<input type="checkbox" id="search_hidechildproducts" name="search_hidechildproducts" value="on"'.($search_hidechildproducts ? 'checked="checked"' : '').'>';
$moreforfilter.= ' <label for="search_hidechildproducts">'.$langs->trans('HideChildProducts').'</label>';
$moreforfilter.='</div>';
}
if ($moreforfilter) if ($moreforfilter)
{ {
print '<div class="liste_titre liste_titre_bydiv centpercent">'; print '<div class="liste_titre liste_titre_bydiv centpercent">';
@@ -614,7 +646,7 @@ else
$product_static->status_buy = $objp->tobuy; $product_static->status_buy = $objp->tobuy;
$product_static->status = $objp->tosell; $product_static->status = $objp->tosell;
$product_static->entity = $objp->entity; $product_static->entity = $objp->entity;
if (! empty($conf->stock->enabled) && $user->rights->stock->lire && $type != 1) // To optimize call of load_stock if (! empty($conf->stock->enabled) && $user->rights->stock->lire && $type != 1) // To optimize call of load_stock
{ {
if ($objp->fk_product_type != 1) // Not a service if ($objp->fk_product_type != 1) // Not a service
@@ -622,8 +654,8 @@ else
$product_static->load_stock('nobatch'); // Load stock_reel + stock_warehouse. This also call load_virtual_stock() $product_static->load_stock('nobatch'); // Load stock_reel + stock_warehouse. This also call load_virtual_stock()
} }
} }
$var=!$var; $var=!$var;
print '<tr '.$bc[$var].'>'; print '<tr '.$bc[$var].'>';
@@ -752,7 +784,7 @@ else
print '<td align="center">'; print '<td align="center">';
print yn($objp->tobatch); print yn($objp->tobatch);
print '</td>'; print '</td>';
} }
// Accountancy code sell // Accountancy code sell
if (! empty($arrayfields['p.accountancy_code_sell']['checked'])) print '<td>'.$objp->accountancy_code_sell.'</td>'; if (! empty($arrayfields['p.accountancy_code_sell']['checked'])) print '<td>'.$objp->accountancy_code_sell.'</td>';
// Accountancy code sell // Accountancy code sell