dolibarr  16.0.1
combinations.php
1 <?php
2 /* Copyright (C) 2016 Marcos García <marcosgdf@gmail.com>
3  * Copyright (C) 2017 Laurent Destailleur <eldy@users.sourceforge.net>
4  * Copyright (C) 2018-2019 Frédéric France <frederic.france@netlogic.fr>
5  * Copyright (C) 2022 Open-Dsi <support@open-dsi.fr>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program. If not, see <https://www.gnu.org/licenses/>.
19  */
20 
21 require '../main.inc.php';
22 require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
23 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
24 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttribute.class.php';
25 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttributeValue.class.php';
26 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
27 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
28 
29 $langs->loadLangs(array("products", "other"));
30 
31 $id = GETPOST('id', 'int');
32 $valueid = GETPOST('valueid', 'int');
33 $ref = GETPOST('ref', 'alpha');
34 $weight_impact = price2num(GETPOST('weight_impact', 'alpha'), 2);
35 $price_impact_percent = (bool) GETPOST('price_impact_percent');
36 if ($price_impact_percent) {
37  $price_impact = price2num(GETPOST('price_impact', 'alpha'), 2);
38 } else {
39  $price_impact = price2num(GETPOST('price_impact', 'alpha'), 'MU');
40 }
41 $level_price_impact = GETPOST('level_price_impact', 'array');
42 $level_price_impact_percent = GETPOST('level_price_impact_percent', 'array');
43 
44 $reference = GETPOST('reference', 'alpha');
45 $form = new Form($db);
46 
47 $action = GETPOST('action', 'aZ09');
48 $massaction = GETPOST('massaction', 'alpha');
49 $show_files = GETPOST('show_files', 'int');
50 $confirm = GETPOST('confirm', 'alpha');
51 $toselect = GETPOST('toselect', 'array');
52 $cancel = GETPOST('cancel', 'alpha');
53 $delete_product = GETPOST('delete_product', 'alpha');
54 $subaction = GETPOST('subaction', 'aZ09');
55 $backtopage = GETPOST('backtopage', 'alpha');
56 $sortfield = GETPOST('sortfield', 'aZ09comma');
57 $sortorder = GETPOST('sortorder', 'aZ09comma');
58 
59 // Security check
60 $fieldvalue = (!empty($id) ? $id : $ref);
61 $fieldtype = (!empty($ref) ? 'ref' : 'rowid');
62 
63 $prodstatic = new Product($db);
64 $prodattr = new ProductAttribute($db);
65 $prodattr_val = new ProductAttributeValue($db);
66 
67 $object = new Product($db);
68 if ($id > 0 || $ref) {
69  $object->fetch($id, $ref);
70 }
71 
72 $selectedvariant = !empty($_SESSION['addvariant_'.$object->id]) ? $_SESSION['addvariant_'.$object->id] : array();
73 
74 // Security check
75 if (empty($conf->variants->enabled)) {
76  accessforbidden('Module not enabled');
77 }
78 if ($user->socid > 0) { // Protection if external user
80 }
81 
82 if ($object->id > 0) {
83  if ($object->type == $object::TYPE_PRODUCT) {
84  restrictedArea($user, 'produit', $object->id, 'product&product', '', '');
85  }
86  if ($object->type == $object::TYPE_SERVICE) {
87  restrictedArea($user, 'service', $object->id, 'product&product', '', '');
88  }
89 } else {
90  restrictedArea($user, 'produit|service', $fieldvalue, 'product&product', '', '', $fieldtype);
91 }
92 $usercanread = (($object->type == Product::TYPE_PRODUCT && $user->rights->produit->lire) || ($object->type == Product::TYPE_SERVICE && $user->rights->service->lire));
93 $usercancreate = (($object->type == Product::TYPE_PRODUCT && $user->rights->produit->creer) || ($object->type == Product::TYPE_SERVICE && $user->rights->service->creer));
94 $usercandelete = (($object->type == Product::TYPE_PRODUCT && $user->rights->produit->supprimer) || ($object->type == Product::TYPE_SERVICE && $user->rights->service->supprimer));
95 
96 
97 /*
98  * Actions
99  */
100 
101 if ($cancel) {
102  $action = '';
103  $massaction = '';
104  unset($_SESSION['addvariant_'.$object->id]);
105 }
106 
107 if (!$object->isProduct() && !$object->isService()) {
108  header('Location: '.dol_buildpath('/product/card.php?id='.$object->id, 2));
109  exit();
110 }
111 if ($action == 'add') {
112  unset($selectedvariant);
113  unset($_SESSION['addvariant_'.$object->id]);
114 }
115 if ($action == 'create' && GETPOST('selectvariant', 'alpha')) { // We click on select combination
116  $action = 'add';
117  $attribute_id = GETPOST('attribute', 'int');
118  $attribute_value_id = GETPOST('value', 'int');
119  if ($attribute_id> 0 && $attribute_value_id > 0) {
120  $feature = $attribute_id . '-' . $attribute_value_id;
121  $selectedvariant[$feature] = $feature;
122  $_SESSION['addvariant_'.$object->id] = $selectedvariant;
123  }
124 }
125 if ($action == 'create' && $subaction == 'delete') { // We click on select combination
126  $action = 'add';
127  $feature = GETPOST('feature', 'intcomma');
128  if (isset($selectedvariant[$feature])) {
129  unset($selectedvariant[$feature]);
130  $_SESSION['addvariant_'.$object->id] = $selectedvariant;
131  }
132 }
133 
134 
135 $prodcomb = new ProductCombination($db);
136 $prodcomb2val = new ProductCombination2ValuePair($db);
137 
138 $productCombination2ValuePairs1 = array();
139 
140 if (($action == 'add' || $action == 'create') && empty($massaction) && !GETPOST('selectvariant', 'alpha') && empty($subaction)) { // We click on Create all defined combinations
141  //$features = GETPOST('features', 'array');
142  $features = $_SESSION['addvariant_'.$object->id];
143 
144  if (!$features) {
145  if ($action == 'create') {
146  setEventMessages($langs->trans('ErrorFieldsRequired'), null, 'errors');
147  }
148  } else {
149  $reference = trim($reference);
150  if (empty($reference)) {
151  $reference = false;
152  }
153  $weight_impact = price2num($weight_impact);
154  $price_impact = price2num($price_impact);
155 
156  // for conf PRODUIT_MULTIPRICES
157  if ($conf->global->PRODUIT_MULTIPRICES) {
158  $level_price_impact = array_map('price2num', $level_price_impact);
159  } else {
160  $level_price_impact = array(1 => $price_impact);
161  $level_price_impact_percent = array(1 => $price_impact_percent);
162  }
163 
164  $sanit_features = array();
165 
166  //First, sanitize
167  foreach ($features as $feature) {
168  $explode = explode('-', $feature);
169  if ($prodattr->fetch($explode[0]) <= 0 || $prodattr_val->fetch($explode[1]) <= 0) {
170  continue;
171  }
172 
173  // Valuepair
174  $sanit_features[$explode[0]] = $explode[1];
175 
176  $tmp = new ProductCombination2ValuePair($db);
177  $tmp->fk_prod_attr = $explode[0];
178  $tmp->fk_prod_attr_val = $explode[1];
179 
180  $productCombination2ValuePairs1[] = $tmp;
181  }
182 
183  $db->begin();
184 
185  // sanit_feature is an array with 1 (and only 1) value per attribute.
186  // For example: Color->blue, Size->Small, Option->2
187  //var_dump($sanit_features);
188  if (!$prodcomb->fetchByProductCombination2ValuePairs($id, $sanit_features)) {
189  $result = $prodcomb->createProductCombination($user, $object, $sanit_features, array(), $level_price_impact_percent, $level_price_impact, $weight_impact, $reference);
190  if ($result > 0) {
191  setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
192  unset($_SESSION['addvariant_'.$object->id]);
193 
194  $db->commit();
195  header('Location: '.dol_buildpath('/variants/combinations.php?id='.$id, 2));
196  exit();
197  } else {
198  $langs->load("errors");
199  setEventMessages($prodcomb->error, $prodcomb->errors, 'errors');
200  }
201  } else {
202  setEventMessages($langs->trans('ErrorRecordAlreadyExists'), null, 'errors');
203  }
204 
205  $db->rollback();
206  }
207 } elseif (!empty($massaction)) {
208  $bulkaction = $massaction;
209  $error = 0;
210 
211  $db->begin();
212 
213  foreach ($toselect as $prodid) {
214  // need create new of Product to prevent rename dir behavior
215  $prodstatic = new Product($db);
216  if ($prodstatic->fetch($prodid) < 0) {
217  continue;
218  }
219 
220  if ($bulkaction == 'on_sell') {
221  $prodstatic->status = 1;
222  $res = $prodstatic->update($prodstatic->id, $user);
223  if ($res <= 0) {
224  setEventMessages($prodstatic->error, $prodstatic->errors, 'errors');
225  $error++;
226  break;
227  }
228  } elseif ($bulkaction == 'on_buy') {
229  $prodstatic->status_buy = 1;
230  $res = $prodstatic->update($prodstatic->id, $user);
231  if ($res <= 0) {
232  setEventMessages($prodstatic->error, $prodstatic->errors, 'errors');
233  $error++;
234  break;
235  }
236  } elseif ($bulkaction == 'not_sell') {
237  $prodstatic->status = 0;
238  $res = $prodstatic->update($prodstatic->id, $user);
239  if ($res <= 0) {
240  setEventMessages($prodstatic->error, $prodstatic->errors, 'errors');
241  $error++;
242  break;
243  }
244  } elseif ($bulkaction == 'not_buy') {
245  $prodstatic->status_buy = 0;
246  $res = $prodstatic->update($prodstatic->id, $user);
247  if ($res <= 0) {
248  setEventMessages($prodstatic->error, $prodstatic->errors, 'errors');
249  $error++;
250  break;
251  }
252  } elseif ($bulkaction == 'delete') {
253  $res = $prodstatic->delete($user, $prodstatic->id);
254  if ($res <= 0) {
255  setEventMessages($prodstatic->error, $prodstatic->errors, 'errors');
256  $error++;
257  break;
258  }
259  } else {
260  break;
261  }
262  }
263 
264  if ($error) {
265  $db->rollback();
266  if (empty($prodstatic->error)) {
267  setEventMessages($langs->trans('CoreErrorMessage'), null, 'errors');
268  }
269  } else {
270  $db->commit();
271  setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
272  }
273 } elseif ($action === 'update' && $valueid > 0) {
274  if ($prodcomb->fetch($valueid) < 0) {
275  dol_print_error($db, $langs->trans('ErrorRecordNotFound'));
276  exit();
277  }
278 
279  $prodcomb->variation_weight = price2num($weight_impact);
280 
281  // for conf PRODUIT_MULTIPRICES
282  if ($conf->global->PRODUIT_MULTIPRICES) {
283  $level_price_impact = array_map('price2num', $level_price_impact);
284 
285  $prodcomb->variation_price = $level_price_impact[1];
286  $prodcomb->variation_price_percentage = (bool) $level_price_impact_percent[1];
287  } else {
288  $level_price_impact = array(1 => $price_impact);
289  $level_price_impact_percent = array(1 => $price_impact_percent);
290 
291  $prodcomb->variation_price = $price_impact;
292  $prodcomb->variation_price_percentage = $price_impact_percent;
293  }
294 
295  if ($conf->global->PRODUIT_MULTIPRICES) {
296  $prodcomb->combination_price_levels = array();
297  for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
298  $productCombinationLevel = new ProductCombinationLevel($db);
299  $productCombinationLevel->fk_product_attribute_combination = $prodcomb->id;
300  $productCombinationLevel->fk_price_level = $i;
301  $productCombinationLevel->variation_price = $level_price_impact[$i];
302  $productCombinationLevel->variation_price_percentage = (bool) $level_price_impact_percent[$i];
303  $prodcomb->combination_price_levels[$i] = $productCombinationLevel;
304  }
305  }
306 
307  if ($prodcomb->update($user) > 0) {
308  setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
309  header('Location: '.dol_buildpath('/variants/combinations.php?id='.$id, 2));
310  exit();
311  } else {
312  setEventMessages($prodcomb->error, $prodcomb->errors, 'errors');
313  }
314 }
315 
316 
317 // Reload variants
318 $productCombinations = $prodcomb->fetchAllByFkProductParent($object->id);
319 
320 if ($action === 'confirm_deletecombination') {
321  if ($prodcomb->fetch($valueid) > 0) {
322  $db->begin();
323 
324  if ($prodcomb->delete($user) > 0 && (empty($delete_product) || ($delete_product == 'on' && $prodstatic->fetch($prodcomb->fk_product_child) > 0 && $prodstatic->delete($user) > 0))) {
325  $db->commit();
326  setEventMessages($langs->trans('RecordSaved'), null, 'mesgs');
327  header('Location: '.dol_buildpath('/variants/combinations.php?id='.$object->id, 2));
328  exit();
329  }
330 
331  $db->rollback();
332  setEventMessages($langs->trans('ProductCombinationAlreadyUsed'), null, 'errors');
333  $action = '';
334  }
335 } elseif ($action === 'edit') {
336  if ($prodcomb->fetch($valueid) < 0) {
337  dol_print_error($db, $langs->trans('ErrorRecordNotFound'));
338  exit();
339  }
340 
341  $weight_impact = $prodcomb->variation_weight;
342  $price_impact = $prodcomb->variation_price;
343  $price_impact_percent = $prodcomb->variation_price_percentage;
344 
345  $productCombination2ValuePairs1 = $prodcomb2val->fetchByFkCombination($valueid);
346 } elseif ($action === 'confirm_copycombination') {
347  //Check destination product
348  $dest_product = GETPOST('dest_product');
349 
350  if ($prodstatic->fetch('', $dest_product) > 0) {
351  //To prevent from copying to the same product
352  if ($prodstatic->ref != $object->ref) {
353  if ($prodcomb->copyAll($user, $object->id, $prodstatic) > 0) {
354  header('Location: '.dol_buildpath('/variants/combinations.php?id='.$prodstatic->id, 2));
355  exit();
356  } else {
357  setEventMessages($langs->trans('ErrorCopyProductCombinations'), null, 'errors');
358  }
359  }
360  } else {
361  setEventMessages($langs->trans('ErrorDestinationProductNotFound'), null, 'errors');
362  }
363 }
364 
365 
366 
367 /*
368  * View
369  */
370 
371 $form = new Form($db);
372 
373 if (!empty($id) || !empty($ref)) {
374  llxHeader("", "", $langs->trans("CardProduct".$object->type));
375 
376  $showbarcode = empty($conf->barcode->enabled) ? 0 : 1;
377  if (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && empty($user->rights->barcode->lire_advance)) {
378  $showbarcode = 0;
379  }
380 
381  $head = product_prepare_head($object);
382  $titre = $langs->trans("CardProduct".$object->type);
383  $picto = ($object->type == Product::TYPE_SERVICE ? 'service' : 'product');
384 
385  print dol_get_fiche_head($head, 'combinations', $titre, -1, $picto);
386 
387  $linkback = '<a href="'.DOL_URL_ROOT.'/product/list.php?type='.$object->type.'">'.$langs->trans("BackToList").'</a>';
388  $object->next_prev_filter = " fk_product_type = ".$object->type;
389 
390  dol_banner_tab($object, 'ref', $linkback, ($user->socid ? 0 : 1), 'ref', '', '', '', 0, '', '');
391 
392  print '<div class="fichecenter">';
393 
394  print '<div class="underbanner clearboth"></div>';
395  print '<table class="border centpercent tableforfield">';
396 
397  // Type
398  if (!empty($conf->product->enabled) && !empty($conf->service->enabled)) {
399  $typeformat = 'select;0:'.$langs->trans("Product").',1:'.$langs->trans("Service");
400  print '<tr><td class="titlefieldcreate">';
401  print (empty($conf->global->PRODUCT_DENY_CHANGE_PRODUCT_TYPE)) ? $form->editfieldkey("Type", 'fk_product_type', $object->type, $object, $usercancreate, $typeformat) : $langs->trans('Type');
402  print '</td><td>';
403  print $form->editfieldval("Type", 'fk_product_type', $object->type, $object, $usercancreate, $typeformat);
404  print '</td></tr>';
405  }
406 
407  // TVA
408  print '<tr><td class="titlefieldcreate">'.$langs->trans("DefaultTaxRate").'</td><td>';
409 
410  $positiverates = '';
411  if (price2num($object->tva_tx)) {
412  $positiverates .= ($positiverates ? '/' : '').price2num($object->tva_tx);
413  }
414  if (price2num($object->localtax1_type)) {
415  $positiverates .= ($positiverates ? '/' : '').price2num($object->localtax1_tx);
416  }
417  if (price2num($object->localtax2_type)) {
418  $positiverates .= ($positiverates ? '/' : '').price2num($object->localtax2_tx);
419  }
420  if (empty($positiverates)) {
421  $positiverates = '0';
422  }
423  echo vatrate($positiverates.($object->default_vat_code ? ' ('.$object->default_vat_code.')' : ''), '%', $object->tva_npr);
424  /*
425  if ($object->default_vat_code)
426  {
427  print vatrate($object->tva_tx, true) . ' ('.$object->default_vat_code.')';
428  }
429  else print vatrate($object->tva_tx, true, $object->tva_npr, true);*/
430  print '</td></tr>';
431 
432  // Price
433  print '<tr><td>'.$langs->trans("SellingPrice").'</td><td>';
434  if ($object->price_base_type == 'TTC') {
435  print price($object->price_ttc).' '.$langs->trans($object->price_base_type);
436  } else {
437  print price($object->price).' '.$langs->trans($object->price_base_type);
438  }
439  print '</td></tr>';
440 
441  // Price minimum
442  print '<tr><td>'.$langs->trans("MinPrice").'</td><td>';
443  if ($object->price_base_type == 'TTC') {
444  print price($object->price_min_ttc).' '.$langs->trans($object->price_base_type);
445  } else {
446  print price($object->price_min).' '.$langs->trans($object->price_base_type);
447  }
448  print '</td></tr>';
449 
450  // Weight
451  print '<tr><td>'.$langs->trans("Weight").'</td><td>';
452  if ($object->weight != '') {
453  print $object->weight." ".measuringUnitString(0, "weight", $object->weight_units);
454  } else {
455  print '&nbsp;';
456  }
457  print "</td></tr>\n";
458 
459  print "</table>\n";
460 
461  print '</div>';
462  print '<div style="clear:both"></div>';
463 
464  print dol_get_fiche_end();
465 
466  $listofvariantselected = '';
467 
468  // Create or edit a varian
469  if ($action == 'add' || ($action == 'edit')) {
470  if ($action == 'add') {
471  $title = $langs->trans('NewProductCombination');
472  // print dol_get_fiche_head();
473  $features = $_SESSION['addvariant_'.$object->id];
474  //First, sanitize
475  $listofvariantselected = '<div id="parttoaddvariant">';
476  if (!empty($features)) {
477  $toprint = array();
478  foreach ($features as $feature) {
479  $explode = explode('-', $feature);
480  if ($prodattr->fetch($explode[0]) <= 0 || $prodattr_val->fetch($explode[1]) <= 0) {
481  continue;
482  }
483  $toprint[] = '<li class="select2-search-choice-dolibarr noborderoncategories" style="background: #ddd;">' . $prodattr->label.' : '.$prodattr_val->value .
484  ' <a class="reposition" href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=create&subaction=delete&feature='.urlencode($feature).'">' . img_delete() . '</a></li>';
485  }
486  $listofvariantselected .= '<div class="select2-container-multi-dolibarr" style="width: 90%;"><ul class="select2-choices-dolibarr">' . implode(' ', $toprint) . '</ul></div>';
487  }
488  $listofvariantselected .= '</div>';
489  //print dol_get_fiche_end();
490  } else {
491  $title = $langs->trans('EditProductCombination');
492  }
493 
494  if ($action == 'add') {
495  $prodattr_all = $prodattr->fetchAll();
496 
497  if (!$selected) {
498  $selected = $prodattr_all[key($prodattr_all)]->id;
499  }
500 
501  $prodattr_alljson = array();
502 
503  foreach ($prodattr_all as $each) {
504  $prodattr_alljson[$each->id] = $each;
505  }
506 
507  ?>
508 
509  <script type="text/javascript">
510 
511  variants_available = <?php echo json_encode($prodattr_alljson); ?>;
512  variants_selected = {
513  index: [],
514  info: []
515  };
516 
517  <?php
518  foreach ($productCombination2ValuePairs1 as $pc2v) {
519  $prodattr_val->fetch($pc2v->fk_prod_attr_val);
520  ?>
521  variants_selected.index.push(<?php echo $pc2v->fk_prod_attr ?>);
522  variants_selected.info[<?php echo $pc2v->fk_prod_attr ?>] = {
523  attribute: variants_available[<?php echo $pc2v->fk_prod_attr ?>],
524  value: {
525  id: <?php echo $pc2v->fk_prod_attr_val ?>,
526  label: '<?php echo $prodattr_val->value ?>'
527  }
528  };
529  <?php
530  }
531  ?>
532 
533  restoreAttributes = function() {
534  jQuery("select[name=attribute]").empty().append('<option value="-1">&nbsp;</option>');
535 
536  jQuery.each(variants_available, function (key, val) {
537  if (jQuery.inArray(val.id, variants_selected.index) == -1) {
538  jQuery("select[name=attribute]").append('<option value="' + val.id + '">' + val.label + '</option>');
539  }
540  });
541  };
542 
543 
544  jQuery(document).ready(function() {
545  jQuery("select#attribute").change(function () {
546  console.log("Change of field variant attribute");
547  var select = jQuery("select#value");
548 
549  if (!jQuery(this).val().length || jQuery(this).val() == '-1') {
550  select.empty();
551  select.append('<option value="-1">&nbsp;</option>');
552  return;
553  }
554 
555  select.empty().append('<option value="">Loading...</option>');
556 
557  jQuery.getJSON("ajax/get_attribute_values.php", {
558  id: jQuery(this).val()
559  }, function(data) {
560  if (data.error) {
561  select.empty();
562  select.append('<option value="-1">&nbsp;</option>');
563  return alert(data.error);
564  }
565 
566  select.empty();
567  select.append('<option value="-1">&nbsp;</option>');
568 
569  jQuery(data).each(function (key, val) {
570  keyforoption = val.id
571  valforoption = val.value
572  select.append('<option value="' + keyforoption + '">' + valforoption + '</option>');
573  });
574  });
575  });
576  });
577  </script>
578 
579  <?php
580  }
581 
582  print '<br>';
583 
584  print load_fiche_titre($title);
585 
586  print '<form method="post" id="combinationform" action="'.$_SERVER["PHP_SELF"] .'?id='.$object->id.'">'."\n";
587  print '<input type="hidden" name="token" value="'.newToken().'">';
588  print '<input type="hidden" name="action" value="'.(($valueid > 0) ? "update" : "create").'">'."\n";
589  if ($valueid > 0) {
590  print '<input type="hidden" name="valueid" value="'.$valueid.'">'."\n";
591  }
592 
593  print dol_get_fiche_head();
594 
595 
596  if ($action == 'add') {
597  print '<table class="border" style="width: 100%">';
598  print "<!-- Variant -->\n";
599  print '<tr>';
600  print '<td class="titlefieldcreate fieldrequired"><label for="attribute">'.$langs->trans('ProductAttribute').'</label></td>';
601  print '<td>';
602  if (is_array($prodattr_all)) {
603  print '<select class="flat minwidth100" id="attribute" name="attribute">';
604  print '<option value="-1">&nbsp;</option>';
605  foreach ($prodattr_all as $attr) {
606  //print '<option value="'.$attr->id.'"'.($attr->id == GETPOST('attribute', 'int') ? ' selected="selected"' : '').'>'.$attr->label.'</option>';
607  print '<option value="'.$attr->id.'">'.$attr->label.'</option>';
608  }
609  print '</select>';
610  }
611 
612  $htmltext = $langs->trans("GoOnMenuToCreateVairants", $langs->transnoentities("Product"), $langs->transnoentities("VariantAttributes"));
613  print $form->textwithpicto('', $htmltext);
614  /*print ' &nbsp; &nbsp; <a href="'.DOL_URL_ROOT.'/variants/create.php?action=create&backtopage='.urlencode($_SERVER["PHP_SELF"].'?action=add&token='.newToken().'&id='.$object->id).'">';
615  print $langs->trans("Create");
616  print '</a>';*/
617 
618  print '</td>';
619  print '</tr>';
620  ?>
621  <!-- Value -->
622  <tr>
623  <td class="fieldrequired"><label for="value"><?php echo $langs->trans('Value') ?></label></td>
624  <td>
625  <select class="flat minwidth100" id="value" name="value">
626  <option value="-1">&nbsp;</option>
627  </select>
628  <?php
629  $htmltext = $langs->trans("GoOnMenuToCreateVairants", $langs->transnoentities("Product"), $langs->transnoentities("VariantAttributes"));
630  print $form->textwithpicto('', $htmltext);
631  /*
632  print ' &nbsp; &nbsp; <a href="'.DOL_URL_ROOT.'/variants/create.php?action=create&backtopage='.urlencode($_SERVER["PHP_SELF"].'?action=add&token='.newToken().'&id='.$object->id).'">';
633  print $langs->trans("Create");
634  print '</a>';
635  */
636  ?>
637  </td>
638  </tr>
639  <tr>
640  <td></td><td>
641  <input type="submit" class="button" name="selectvariant" id="selectvariant" value="<?php echo dol_escape_htmltag($langs->trans("SelectCombination")); ?>">
642  </td>
643  </tr>
644  <?php
645  print '<tr><td></td><td>';
646  print $listofvariantselected;
647  print '</td>';
648  print '</tr>';
649 
650  print '</table>';
651  print '<hr>';
652  }
653 
654  if (is_array($productCombination2ValuePairs1)) {
655  print '<table class="border" style="width: 100%">';
656 
657  // When in edit mode
658  if (is_array($productCombination2ValuePairs1) && count($productCombination2ValuePairs1)) {
659  ?>
660  <tr>
661  <td class="titlefieldcreate tdtop"><label for="features"><?php echo $langs->trans('Combination') ?></label></td>
662  <td class="tdtop">
663  <div class="inline-block valignmiddle quatrevingtpercent">
664  <?php
665  foreach ($productCombination2ValuePairs1 as $key => $val) {
666  $result1 = $prodattr->fetch($val->fk_prod_attr);
667  $result2 = $prodattr_val->fetch($val->fk_prod_attr_val);
668  //print 'rr'.$result1.' '.$result2;
669  if ($result1 > 0 && $result2 > 0) {
670  print $prodattr->label.' - '.$prodattr_val->value.'<br>';
671  // TODO Add delete link
672  }
673  }
674  ?>
675  </div>
676  <!-- <div class="inline-block valignmiddle">
677  <a href="#" class="inline-block valignmiddle button" id="delfeature"><?php echo img_edit_remove() ?></a>
678  </div>-->
679  </td>
680  <td>
681  </td>
682  </tr>
683  <?php
684  }
685  ?>
686  <tr>
687  <td><label for="reference"><?php echo $langs->trans('Reference') ?></label></td>
688  <td><input type="text" id="reference" name="reference" value="<?php echo trim($reference) ?>"></td>
689  </tr>
690  <?php
691  if (empty($conf->global->PRODUIT_MULTIPRICES)) {
692  ?>
693  <tr>
694  <td><label for="price_impact"><?php echo $langs->trans('PriceImpact') ?></label></td>
695  <td><input type="text" id="price_impact" name="price_impact" value="<?php echo price($price_impact) ?>">
696  <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>
697  </td>
698  </tr>
699  <?php
700  } else {
701  $prodcomb->fetchCombinationPriceLevels();
702 
703  for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
704  print '<tr>';
705  print '<td><label for="level_price_impact_'.$i.'">'.$langs->trans('ImpactOnPriceLevel', $i).'</label>';
706  if ($i === 1) {
707  print ' <a id="apply-price-impact-to-all-level" class="classfortooltip" href="#" title="'.$langs->trans('ApplyToAllPriceImpactLevelHelp').'">('.$langs->trans('ApplyToAllPriceImpactLevel').')</a>';
708  }
709  print '</td>';
710  print '<td><input type="text" class="level_price_impact" id="level_price_impact_'.$i.'" name="level_price_impact['.$i.']" value="'.price($prodcomb->combination_price_levels[$i]->variation_price).'">';
711  print '<input type="checkbox" class="level_price_impact_percent" id="level_price_impact_percent_'.$i.'" name="level_price_impact_percent['.$i.']" '.(!empty($prodcomb->combination_price_levels[$i]->variation_price_percentage) ? ' checked' : '').'> <label for="level_price_impact_percent_'.$i.'">'.$langs->trans('PercentageVariation').'</label>';
712 
713  print '</td>';
714  print '</tr>';
715  }
716  }
717 
718  if ($object->isProduct()) {
719  print '<tr>';
720  print '<td><label for="weight_impact">'.$langs->trans('WeightImpact').'</label></td>';
721  print '<td><input type="text" id="weight_impact" name="weight_impact" value="'.price($weight_impact).'"></td>';
722  print '</tr>';
723  }
724 
725  print '</table>';
726  }
727 
728  if (!empty($conf->global->PRODUIT_MULTIPRICES)) {
729  ?>
730  <script>
731  $(document).ready(function() {
732  // Apply level 1 impact to all prices impact levels
733  $('body').on('click', '#apply-price-impact-to-all-level', function(e) {
734  e.preventDefault();
735  let priceImpact = $( "#level_price_impact_1" ).val();
736  let priceImpactPrecent = $( "#level_price_impact_percent_1" ).prop("checked");
737 
738  var multipricelimit = <?php print intval($conf->global->PRODUIT_MULTIPRICES_LIMIT); ?>
739 
740  for (let i = 2; i <= multipricelimit; i++) {
741  $( "#level_price_impact_" + i ).val(priceImpact);
742  $( "#level_price_impact_percent_" + i ).prop("checked", priceImpactPrecent);
743  }
744  });
745  });
746  </script>
747  <?php
748  }
749 
750  print dol_get_fiche_end();
751  ?>
752 
753  <div style="text-align: center">
754  <input type="submit" name="create" <?php if (!is_array($productCombination2ValuePairs1)) {
755  print ' disabled="disabled"';
756  } ?> value="<?php echo $action == 'add' ? $langs->trans('Create') : $langs->trans("Save") ?>" class="button button-save">
757  &nbsp;
758  <input type="submit" name="cancel" value="<?php echo $langs->trans("Cancel"); ?>" class="button button-cancel">
759  </div>
760 
761  <?php
762 
763  print '</form>';
764  } else {
765  if ($action === 'delete') {
766  if ($prodcomb->fetch($valueid) > 0) {
767  $prodstatic->fetch($prodcomb->fk_product_child);
768 
769  print $form->formconfirm(
770  "combinations.php?id=".urlencode($id)."&valueid=".urlencode($valueid),
771  $langs->trans('Delete'),
772  $langs->trans('ProductCombinationDeleteDialog', $prodstatic->ref),
773  "confirm_deletecombination",
774  array(array('label'=> $langs->trans('DeleteLinkedProduct'), 'type'=> 'checkbox', 'name' => 'delete_product', 'value' => false)),
775  0,
776  1
777  );
778  }
779  } elseif ($action === 'copy') {
780  print $form->formconfirm('combinations.php?id='.$id, $langs->trans('ToClone'), $langs->trans('ConfirmCloneProductCombinations'), 'confirm_copycombination', array(array('type' => 'text', 'label' => $langs->trans('CloneDestinationReference'), 'name' => 'dest_product')), 0, 1);
781  }
782 
783  $comb2val = new ProductCombination2ValuePair($db);
784 
785  if ($productCombinations) {
786  ?>
787 
788  <script type="text/javascript">
789  jQuery(document).ready(function() {
790 
791  jQuery('input[name="select_all"]').click(function() {
792 
793  if (jQuery(this).prop('checked')) {
794  var checked = true;
795  } else {
796  var checked = false;
797  }
798 
799  jQuery('table.liste input[type="checkbox"]').prop('checked', checked);
800  });
801 
802  jQuery('input[name^="select["]').click(function() {
803  jQuery('input[name="select_all"]').prop('checked', false);
804  });
805 
806  });
807  </script>
808 
809  <?php
810  }
811 
812  // Buttons
813  print '<div class="tabsAction">';
814 
815  print ' <div class="inline-block divButAction">';
816 
817  print '<a href="combinations.php?id='.$object->id.'&action=add&token='.newToken().'" class="butAction">'.$langs->trans('NewProductCombination').'</a>'; // NewVariant
818 
819  if ($productCombinations) {
820  print '<a href="combinations.php?id='.$object->id.'&action=copy&token='.newToken().'" class="butAction">'.$langs->trans('PropagateVariant').'</a>';
821  }
822 
823  print ' </div>';
824 
825  print '</div>';
826 
827 
828 
829  $arrayofselected = is_array($toselect) ? $toselect : array();
830 
831 
832  // List of variants
833  print '<form method="POST" action="'.$_SERVER["PHP_SELF"] .'?id='.$object->id.'">';
834  print '<input type="hidden" name="token" value="'.newToken().'">';
835  print '<input type="hidden" name="action" value="massaction">';
836  print '<input type="hidden" name="backtopage" value="'.$backtopage.'">';
837 
838  // List of mass actions available
839  /*
840  $arrayofmassactions = array(
841  'presend'=>$langs->trans("SendByMail"),
842  'builddoc'=>$langs->trans("PDFMerge"),
843  );
844  if ($user->rights->product->supprimer) $arrayofmassactions['predelete']='<span class="fa fa-trash paddingrightonly"></span>'.$langs->trans("Delete");
845  if (in_array($massaction, array('presend','predelete'))) $arrayofmassactions=array();
846  $massactionbutton=$form->selectMassAction('', $arrayofmassactions);
847  */
848 
849  $aaa = '';
850  if (count($productCombinations)) {
851  $aaa = '<label for="massaction">'.$langs->trans('BulkActions').'</label>';
852  $aaa .= '<select id="bulk_action" name="massaction" class="flat">';
853  $aaa .= ' <option value="nothing">&nbsp;</option>';
854  $aaa .= ' <option value="not_buy">'.$langs->trans('ProductStatusNotOnBuy').'</option>';
855  $aaa .= ' <option value="not_sell">'.$langs->trans('ProductStatusNotOnSell').'</option>';
856  $aaa .= ' <option value="on_buy">'.$langs->trans('ProductStatusOnBuy').'</option>';
857  $aaa .= ' <option value="on_sell">'.$langs->trans('ProductStatusOnSell').'</option>';
858  $aaa .= ' <option value="delete">'.$langs->trans('Delete').'</option>';
859  $aaa .= '</select>';
860  $aaa .= '<input type="submit" value="'.dol_escape_htmltag($langs->trans("Apply")).'" class="button small">';
861  }
862  $massactionbutton = $aaa;
863 
864  $title = $langs->trans("ProductCombinations");
865 
866  print_barre_liste($title, 0, $_SERVER["PHP_SELF"], '', $sortfield, $sortorder, $aaa, 0);
867 
868  print '<div class="div-table-responsive">';
869  ?>
870  <table class="liste">
871  <tr class="liste_titre">
872  <td class="liste_titre"><?php echo $langs->trans('Product') ?></td>
873  <td class="liste_titre"><?php echo $langs->trans('Combination') ?></td>
874  <td class="liste_titre right"><?php echo $langs->trans('PriceImpact') ?></td>
875  <?php if ($object->isProduct()) {
876  print'<td class="liste_titre right">'.$langs->trans('WeightImpact').'</td>';
877  } ?>
878  <td class="liste_titre center"><?php echo $langs->trans('OnSell') ?></td>
879  <td class="liste_titre center"><?php echo $langs->trans('OnBuy') ?></td>
880  <td class="liste_titre"></td>
881  <?php
882  print '<td class="liste_titre center">';
883  $searchpicto = $form->showCheckAddButtons('checkforselect', 1);
884  print $searchpicto;
885  print '</td>';
886  ?>
887  </tr>
888  <?php
889 
890  if (count($productCombinations)) {
891  foreach ($productCombinations as $currcomb) {
892  $prodstatic->fetch($currcomb->fk_product_child);
893  print '<tr class="oddeven">';
894  print '<td>'.$prodstatic->getNomUrl(1).'</td>';
895  print '<td>';
896 
897  $productCombination2ValuePairs = $comb2val->fetchByFkCombination($currcomb->id);
898  $iMax = count($productCombination2ValuePairs);
899 
900  for ($i = 0; $i < $iMax; $i++) {
901  echo dol_htmlentities($productCombination2ValuePairs[$i]);
902  if ($i !== ($iMax - 1)) {
903  echo ', ';
904  }
905  }
906  print '</td>';
907  print '<td class="right">'.($currcomb->variation_price >= 0 ? '+' : '').price($currcomb->variation_price).($currcomb->variation_price_percentage ? ' %' : '').'</td>';
908  if ($object->isProduct()) {
909  print '<td class="right">'.($currcomb->variation_weight >= 0 ? '+' : '').price($currcomb->variation_weight).' '.measuringUnitString(0, 'weight', $prodstatic->weight_units).'</td>';
910  }
911  print '<td class="center">'.$prodstatic->getLibStatut(2, 0).'</td>';
912  print '<td class="center">'.$prodstatic->getLibStatut(2, 1).'</td>';
913  print '<td class="right">';
914  print '<a class="paddingleft paddingright editfielda" href="'.$_SERVER["PHP_SELF"].'?id='.$id.'&action=edit&token='.newToken().'&valueid='.$currcomb->id.'">'.img_edit().'</a>';
915  print '<a class="paddingleft paddingright" href="'.$_SERVER["PHP_SELF"].'?id='.$id.'&action=delete&token='.newToken().'&valueid='.$currcomb->id.'">'.img_delete().'</a>';
916  print '</td>';
917  print '<td class="nowrap center">';
918  if (!empty($productCombinations) || $massactionbutton || $massaction) { // If we are in select mode (massactionbutton defined) or if we have already selected and sent an action ($massaction) defined
919  $selected = 0;
920  if (in_array($prodstatic->id, $arrayofselected)) {
921  $selected = 1;
922  }
923  print '<input id="cb'.$prodstatic->id.'" class="flat checkforselect" type="checkbox" name="toselect[]" value="'.$prodstatic->id.'"'.($selected ? ' checked="checked"' : '').'>';
924  }
925  print '</td>';
926  print '</tr>';
927  }
928  } else {
929  print '<tr><td colspan="8"><span class="opacitymedium">'.$langs->trans("None").'</span></td></tr>';
930  }
931  print '</table>';
932  print '</div>';
933  print '</form>';
934  }
935 } else {
936  llxHeader();
937  // not found
938 }
939 
940 // End of page
941 llxFooter();
942 $db->close();
GETPOST($paramname, $check= 'alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
img_edit($titlealt= 'default', $float=0, $other= '')
Show logo editer/modifier fiche.
if($cancel &&!$id) if($action== 'add'&&!$cancel) if($action== 'delete') if($id) $form
Actions.
Definition: card.php:142
dol_htmlentities($string, $flags=ENT_QUOTES|ENT_SUBSTITUTE, $encoding= 'UTF-8', $double_encode=false)
Replace htmlentities functions.
Class to manage products or services.
if(!defined('NOREQUIRESOC')) if(!defined('NOREQUIRETRAN')) if(!defined('NOCSRFCHECK')) if(!defined('NOTOKENRENEWAL')) if(!defined('NOREQUIREMENU')) if(!defined('NOREQUIREHTML')) if(!defined('NOREQUIREAJAX')) llxHeader()
Empty header.
Definition: wrapper.php:59
const TYPE_SERVICE
Service.
const TYPE_PRODUCT
Regular product.
Class ProductAttributeValue Used to represent a product attribute value.
$conf db name
Only used if Module[ID]Name translation string is not found.
Definition: repair.php:122
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
Class ProductAttribute Used to represent a product attribute.
price($amount, $form=0, $outlangs= '', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code= '')
Function to format a value into an amount for visual output Function used into PDF and HTML pages...
setEventMessages($mesg, $mesgs, $style= 'mesgs', $messagekey= '')
Set event messages in dol_events session object.
print_barre_liste($titre, $page, $file, $options= '', $sortfield= '', $sortorder= '', $morehtmlcenter= '', $num=-1, $totalnboflines= '', $picto= 'generic', $pictoisfullpath=0, $morehtmlright= '', $morecss= '', $limit=-1, $hideselectlimit=0, $hidenavigation=0, $pagenavastextinput=0, $morehtmlrightbeforearrow= '')
Print a title with navigation controls for pagination.
Class to manage generation of HTML components Only common components must be here.
load_fiche_titre($titre, $morehtmlright= '', $picto= 'generic', $pictoisfullpath=0, $id= '', $morecssontable= '', $morehtmlcenter= '')
Load a title with picto.
price2num($amount, $rounding= '', $option=0)
Function that return a number with universal decimal format (decimal separator is &#39;...
Class ProductCombinationLevel Used to represent a product combination Level.
accessforbidden($message= '', $printheader=1, $printfooter=1, $showonlymessage=0, $params=null)
Show a message to say access is forbidden and stop program Calling this function terminate execution ...
Class ProductCombination Used to represent a product combination.
restrictedArea($user, $features, $objectid=0, $tableandshare= '', $feature2= '', $dbt_keyfield= 'fk_soc', $dbt_select= 'rowid', $isdraft=0, $mode=0)
Check permissions of a user to show a page and an object.
img_edit_remove($titlealt= 'default', $other= '')
Show logo -.
dol_get_fiche_head($links=array(), $active= '', $title= '', $notab=0, $picto= '', $pictoisfullpath=0, $morehtmlright= '', $morecss= '', $limittoshow=0, $moretabssuffix= '')
Show tabs of a record.
Class ProductCombination2ValuePair Used to represent the relation between a product combination...
dol_print_error($db= '', $error= '', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
newToken()
Return the value of token currently saved into session with name &#39;newtoken&#39;.
dol_get_fiche_end($notab=0)
Return tab footer of a card.
dol_banner_tab($object, $paramid, $morehtml= '', $shownav=1, $fieldid= 'rowid', $fieldref= 'ref', $morehtmlref= '', $moreparam= '', $nodbprefix=0, $morehtmlleft= '', $morehtmlstatus= '', $onlybanner=0, $morehtmlright= '')
Show tab footer of a card.
llxFooter()
Empty footer.
Definition: wrapper.php:73
img_delete($titlealt= 'default', $other= 'class="pictodelete"', $morecss= '')
Show delete logo.
if(preg_match('/crypted:/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
Definition: repair.php:119
product_prepare_head($object)
Prepare array with list of tabs.
Definition: product.lib.php:35
measuringUnitString($unit, $measuring_style= '', $scale= '', $use_short_label=0, $outputlangs=null)
Return translation label of a unit key.
vatrate($rate, $addpercent=false, $info_bits=0, $usestarfornpr=0, $html=0)
Return a string with VAT rate label formated for view output Used into pdf and HTML pages...