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

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;

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';
@@ -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++;
} }
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 $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
@@ -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');
@@ -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)
@@ -264,6 +278,12 @@ else
if ($search_tobatch != '' && $search_tobatch >= 0) $sql.= " AND p.tobatch = ".$db->escape($search_tobatch); if ($search_tobatch != '' && $search_tobatch >= 0) $sql.= " AND p.tobatch = ".$db->escape($search_tobatch);
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
if (!empty($conf->attributes->enabled) && $search_hidechildproducts && ($type === 0)) {
$sql .= " AND pac.rowid IS NULL";
}
// Add where from extra fields // Add where from extra fields
foreach ($search_array_options as $key => $val) foreach ($search_array_options as $key => $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">';