dolibarr 23.0.3
card.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2001-2007 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2004-2020 Laurent Destailleur <eldy@users.sourceforge.net>
4 * Copyright (C) 2005 Eric Seigne <eric.seigne@ryxeo.com>
5 * Copyright (C) 2005-2018 Regis Houssin <regis.houssin@inodbox.com>
6 * Copyright (C) 2006 Andre Cianfarani <acianfa@free.fr>
7 * Copyright (C) 2011-2014 Juanjo Menent <jmenent@2byte.es>
8 * Copyright (C) 2015 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr>
9 * Copyright (C) 2023 Benjamin Falière <benjamin.faliere@altairis.fr>
10 * Copyright (C) 2024-2025 Frédéric France <frederic.france@free.fr>
11 * Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
12 *
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 3 of the License, or
16 * (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program. If not, see <https://www.gnu.org/licenses/>.
25 */
26
33// Load Dolibarr environment
34require '../../main.inc.php';
42require_once DOL_DOCUMENT_ROOT.'/product/class/html.formproduct.class.php';
43require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
44require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
45require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
46
47// Load translation files required by the page
48$langs->loadLangs(array('bills', 'products', 'stocks'));
49
50$id = GETPOSTINT('id');
51$ref = GETPOST('ref', 'alpha');
52$action = GETPOST('action', 'aZ09');
53$confirm = GETPOST('confirm', 'alpha');
54$cancel = GETPOST('cancel', 'alpha');
55$key = GETPOST('key');
56$parent = GETPOST('parent');
57
58// Security check
59if (!empty($user->socid)) {
60 $socid = $user->socid;
61}
62$fieldvalue = (!empty($id) ? $id : (!empty($ref) ? $ref : ''));
63$fieldtype = (!empty($ref) ? 'ref' : 'rowid');
64
65// Initialize a technical object to manage hooks of page. Note that conf->hooks_modules contains an array of hook context
66$hookmanager->initHooks(array('productcompositioncard', 'globalcard'));
67
68$object = new Product($db);
69$objectid = 0;
70if ($id > 0 || !empty($ref)) {
71 $result = $object->fetch($id, $ref);
72 $objectid = $object->id;
73 $id = $object->id;
74}
75
76$result = restrictedArea($user, 'produit|service', $fieldvalue, 'product&product', '', '', $fieldtype);
77
78if ($object->id > 0) {
79 if ($object->type == $object::TYPE_PRODUCT) {
80 restrictedArea($user, 'produit', $object->id, 'product&product', '', '');
81 }
82 if ($object->type == $object::TYPE_SERVICE) {
83 restrictedArea($user, 'service', $object->id, 'product&product', '', '');
84 }
85} else {
86 restrictedArea($user, 'produit|service', $fieldvalue, 'product&product', '', '', $fieldtype);
87}
88$usercanread = (($object->type == Product::TYPE_PRODUCT && $user->hasRight('produit', 'lire')) || ($object->type == Product::TYPE_SERVICE && $user->hasRight('service', 'lire')));
89$usercancreate = (($object->type == Product::TYPE_PRODUCT && $user->hasRight('produit', 'creer')) || ($object->type == Product::TYPE_SERVICE && $user->hasRight('service', 'creer')));
90$usercandelete = (($object->type == Product::TYPE_PRODUCT && $user->hasRight('produit', 'supprimer')) || ($object->type == Product::TYPE_SERVICE && $user->hasRight('service', 'supprimer')));
91
92
93/*
94 * Actions
95 */
96
97if ($cancel) {
98 $action = '';
99}
100
101$reshook = $hookmanager->executeHooks('doActions', [], $object, $action); // Note that $action and $object may have been modified by some hooks
102if ($reshook < 0) {
103 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
104}
105
106if (empty($reshook)) {
107 // Add subproduct to product
108 if ($action == 'add_prod' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
109 $error = 0;
110 $maxprod = GETPOSTINT("max_prod");
111
112 for ($i = 0; $i < $maxprod; $i++) {
113 $qty = price2num(GETPOST("prod_qty_" . $i, 'alpha'), 'MS');
114 if ($qty > 0) {
115 if ($object->add_sousproduit($id, GETPOSTINT("prod_id_" . $i), (float) $qty, GETPOSTINT("prod_incdec_" . $i)) > 0) {
116 //var_dump($i.' '.GETPOST("prod_id_".$i, 'int'), $qty, GETPOST("prod_incdec_".$i, 'int'));
117 $action = 'edit';
118 } else {
119 $error++;
120 $action = 're-edit';
121 if ($object->error == "isFatherOfThis") {
122 setEventMessages($langs->trans("ErrorAssociationIsFatherOfThis"), null, 'errors');
123 } else {
124 setEventMessages($object->error, $object->errors, 'errors');
125 }
126 }
127 } else {
128 if ($object->del_sousproduit($id, GETPOSTINT("prod_id_" . $i)) > 0) {
129 $action = 'edit';
130 } else {
131 $error++;
132 $action = 're-edit';
133 setEventMessages($object->error, $object->errors, 'errors');
134 }
135 }
136 }
137
138 if (!$error) {
139 header("Location: " . $_SERVER["PHP_SELF"] . '?id=' . $object->id);
140 exit;
141 }
142 } elseif ($action === 'save_composed_product') {
143 $TProduct = GETPOST('TProduct', 'array');
144 if (!empty($TProduct)) {
145 foreach ($TProduct as $id_product => $row) {
146 if ($row['qty'] > 0) {
147 $object->update_sousproduit($id, $id_product, $row['qty'], isset($row['incdec']) ? 1 : 0);
148 } else {
149 $object->del_sousproduit($id, $id_product);
150 }
151 }
152 setEventMessages('RecordSaved', null);
153 }
154 $action = '';
155 header("Location: " . $_SERVER["PHP_SELF"] . '?id=' . $object->id);
156 exit;
157 }
158}
159
160/*
161 * View
162 */
163
164$form = new Form($db);
165$formproduct = new FormProduct($db);
166$product_fourn = new ProductFournisseur($db);
167$productstatic = new Product($db);
168$resql = false;
169// action recherche des produits par mot-cle et/ou par categorie
170if ($action == 'search') {
171 $current_lang = $langs->getDefaultLang();
172
173 $sql = 'SELECT DISTINCT p.rowid, p.ref, p.label, p.fk_product_type as type, p.barcode, p.price, p.price_ttc, p.price_base_type, p.entity,';
174 $sql .= ' p.fk_product_type, p.tms as datem, p.tobatch';
175 $sql .= ', p.tosell as status, p.tobuy as status_buy';
176 if (getDolGlobalInt('MAIN_MULTILANGS')) {
177 $sql .= ', pl.label as labelm, pl.description as descriptionm';
178 }
179
180 $parameters = array();
181 $reshook = $hookmanager->executeHooks('printFieldListSelect', $parameters, $object); // Note that $action and $object may have been modified by hook
182 $sql .= $hookmanager->resPrint;
183
184 $sql .= ' FROM '.MAIN_DB_PREFIX.'product as p';
185 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'categorie_product as cp ON p.rowid = cp.fk_product';
186 if (getDolGlobalInt('MAIN_MULTILANGS')) {
187 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product_lang as pl ON pl.fk_product = p.rowid AND lang='".($current_lang)."'";
188 }
189 $sql .= ' WHERE p.entity IN ('.getEntity('product').')';
190
191 $parameters = array();
192 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $object); // Note that $action and $object may have been modified by hook
193 $sql .= $hookmanager->resPrint;
194
195 if ($key != "") {
196 // For natural search
197 $params = array('p.ref', 'p.label', 'p.description', 'p.note');
198 // multilang
199 if (getDolGlobalInt('MAIN_MULTILANGS')) {
200 $params[] = 'pl.label';
201 $params[] = 'pl.description';
202 $params[] = 'pl.note';
203 }
204 if (isModEnabled('barcode')) {
205 $params[] = 'p.barcode';
206 }
207 $sql .= natural_search($params, $key);
208 }
209 if (isModEnabled('category') && !empty($parent) && $parent != -1) {
210 $sql .= " AND cp.fk_categorie ='".$db->escape($parent)."'";
211 }
212 $sql .= " ORDER BY p.ref ASC";
213
214 $resql = $db->query($sql);
215}
216
217$title = $langs->trans('ProductServiceCard');
218$help_url = '';
219$shortlabel = dol_trunc($object->label, 16);
220if (GETPOST("type") == '0' || ($object->type == Product::TYPE_PRODUCT)) {
221 $title = $langs->trans('Product')." ".$shortlabel." - ".$langs->trans('AssociatedProducts');
222 $help_url = 'EN:Module_Products|FR:Module_Produits|ES:M&oacute;dulo_Productos|DE:Modul_Produkte';
223}
224if (GETPOST("type") == '1' || ($object->type == Product::TYPE_SERVICE)) {
225 $title = $langs->trans('Service')." ".$shortlabel." - ".$langs->trans('AssociatedProducts');
226 $help_url = 'EN:Module_Services_En|FR:Module_Services|ES:M&oacute;dulo_Servicios|DE:Modul_Leistungen';
227}
228
229llxHeader('', $title, $help_url, '', 0, 0, '', '', '', 'mod-product page-composition_card');
230
231$head = product_prepare_head($object);
232
233$titre = $langs->trans("CardProduct".$object->type);
234$picto = ($object->type == Product::TYPE_SERVICE ? 'service' : 'product');
235
236print dol_get_fiche_head($head, 'subproduct', $titre, -1, $picto);
237
238
239if ($id > 0 || !empty($ref)) {
240 /*
241 * Product card
242 */
243
244 $iskit = $object->hasFatherOrChild(1);
245
246 if ($user->hasRight('produit', 'lire') || $user->hasRight('service', 'lire')) {
247 $linkback = '<a href="'.DOL_URL_ROOT.'/product/list.php?restore_lastsearch_values=1&type='.$object->type.'">'.$langs->trans("BackToList").'</a>';
248
249 $shownav = 1;
250 if ($user->socid && !in_array('product', explode(',', getDolGlobalString('MAIN_MODULES_FOR_EXTERNAL')))) {
251 $shownav = 0;
252 }
253
254 dol_banner_tab($object, 'ref', $linkback, $shownav, 'ref', '');
255
256 if ($object->type != Product::TYPE_SERVICE || getDolGlobalString('STOCK_SUPPORTS_SERVICES') || (!getDolGlobalString('PRODUIT_MULTIPRICES') && !getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES'))) {
257 print '<div class="fichecenter">';
258 print '<div class="fichehalfleft">';
259 print '<div class="underbanner clearboth"></div>';
260
261 print '<table class="border centpercent tableforfield">';
262
263 // Type
264 if (isModEnabled("product") && isModEnabled("service")) {
265 $typeformat = 'select;0:'.$langs->trans("Product").',1:'.$langs->trans("Service");
266 print '<tr><td class="titlefieldmiddle">';
267 print (!getDolGlobalString('PRODUCT_DENY_CHANGE_PRODUCT_TYPE')) ? $form->editfieldkey("Type", 'fk_product_type', (string) $object->type, $object, 0, $typeformat) : $langs->trans('Type');
268 print '</td><td>';
269 print $form->editfieldval("Type", 'fk_product_type', $object->type, $object, 0, $typeformat);
270 print '</td></tr>';
271 }
272
273 // Stockable product / default warehouse
274 if (($object->isProduct() || getDolGlobalInt('STOCK_SUPPORTS_SERVICES')) && isModEnabled('stock')) { // Do not use isStockManaged here.We must sow info even if stock not managed
275 print '<tr><td>' . $form->textwithpicto($langs->trans("StockableProduct"), $langs->trans('StockableProductDescription')) . '</td>';
276 print '<td>';
277 if ($iskit) {
278 print '<input type="checkbox" readonly disabled> <span class="opacitymedium">' . $langs->trans("NotSupportedOnKits").'</span>';
279 } else {
280 print '<input type="checkbox" readonly disabled '.($object->stockable_product == 1 ? 'checked' : '').'>';
281 }
282 print '</td></tr>';
283
284 if ($object->isStockManaged() && !$iskit) {
285 $warehouse = new Entrepot($db);
286 $warehouse->fetch($object->fk_default_warehouse);
287
288 print '<tr><td>'.$langs->trans("DefaultWarehouse").'</td><td>';
289 print(!empty($warehouse->id) ? $warehouse->getNomUrl(1) : '');
290 print '</td>';
291 }
292 }
293
294 print '</table>';
295
296 print '</div><div class="fichehalfright">';
297 print '<div class="underbanner clearboth"></div>';
298
299 print '<table class="border centpercent tableforfield">';
300
301 // Nature
302 if ($object->type != Product::TYPE_SERVICE) {
303 if (!getDolGlobalString('PRODUCT_DISABLE_NATURE')) {
304 print '<tr><td>'.$form->textwithpicto($langs->trans("NatureOfProductShort"), $langs->trans("NatureOfProductDesc")).'</td><td>';
305 print $object->getLibFinished();
306 //print $formproduct->selectProductNature('finished', $object->finished);
307 print '</td></tr>';
308 }
309 }
310
311 if (!getDolGlobalString('PRODUIT_MULTIPRICES') && !getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) {
312 // Price
313 print '<tr><td class="titlefield">'.$langs->trans("SellingPrice").'</td><td>';
314 if ($object->price_base_type == 'TTC') {
315 print price($object->price_ttc).' '.$langs->trans($object->price_base_type);
316 } else {
317 print price($object->price).' '.$langs->trans($object->price_base_type ? $object->price_base_type : 'HT');
318 }
319 print '</td></tr>';
320
321 // Price minimum
322 print '<tr><td>'.$langs->trans("MinPrice").'</td><td>';
323 if ($object->price_base_type == 'TTC') {
324 print price($object->price_min_ttc).' '.$langs->trans($object->price_base_type);
325 } else {
326 print price($object->price_min).' '.$langs->trans($object->price_base_type ? $object->price_base_type : 'HT');
327 }
328 print '</td></tr>';
329 }
330
331 print '</table>';
332 print '</div>';
333 print '</div>';
334 }
335
336 print dol_get_fiche_end();
337
338
339 print '<div class="clearboth"></div><br>';
340
341
342 $prodsfather = $object->getFather(); // Parent Products
343 $object->get_sousproduits_arbo(); // Load $object->sousprods
344 $parent_label = $object->label;
345 $prods_arbo = $object->get_arbo_each_prod();
346
347 $tmpid = $id;
348 if (!empty($conf->use_javascript_ajax)) {
349 $nboflines = $prods_arbo;
350 $table_element_line = 'product_association';
351
352 include DOL_DOCUMENT_ROOT . '/core/tpl/ajaxrow.tpl.php';
353 }
354 $id = $tmpid;
355
356 $nbofsubsubproducts = count($prods_arbo); // This include sub sub product into nb
357 $prodschild = $object->getChildsArbo($id, 1);
358 $nbofsubproducts = count($prodschild); // This include only first level of children
359
360
361 print '<div class="fichecenter">';
362
363 print load_fiche_titre($langs->trans("ProductParentList"), '', '');
364
365 print '<table class="liste noborder">';
366 print '<tr class="liste_titre">';
367 print '<th>'.$langs->trans('ParentProducts').'</th>';
368 print '<th>'.$langs->trans('Label').'</th>';
369 print '<th class="right">'.$langs->trans('Qty').'</th>';
370 print '</td>';
371 if (count($prodsfather) > 0) {
372 foreach ($prodsfather as $value) {
373 $idprod = $value["id"];
374 $productstatic->id = $idprod; // $value["id"];
375 $productstatic->type = $value["fk_product_type"];
376 $productstatic->ref = $value['ref'];
377 $productstatic->label = $value['label'];
378 $productstatic->entity = $value['entity'];
379 $productstatic->status = $value['status'];
380 $productstatic->status_buy = $value['status_buy'];
381
382 print '<tr class="oddeven">';
383 print '<td>'.$productstatic->getNomUrl(1, 'composition').'</td>';
384 print '<td>'.dol_escape_htmltag($productstatic->label).'</td>';
385 print '<td class="right">'.dol_escape_htmltag((string) $value['qty']).'</td>';
386 print '</tr>';
387 }
388 } else {
389 print '<tr class="oddeven">';
390 print '<td colspan="3"><span class="opacitymedium">'.$langs->trans("None").'</span></td>';
391 print '</tr>';
392 }
393 print '</table>';
394 print '</div>';
395
396 print '<br>'."\n";
397
398
399 print '<div class="fichecenter">';
400
401 $atleastonenotdefined = 0; // at least on buying price not defined
402
403 $tmpurlforbutton = 'javascript:void(0);';
404 $newButtonParams = [
405 'attr' => [
406 'onclick' => 'console.log("click to add a product in kit");jQuery(".formtoaddinkit").toggle();return false;',
407 ]
408 ];
409 $morehtmlright = dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', $tmpurlforbutton, '', $usercancreate ? 1 : 0, $newButtonParams);
410
411 print load_fiche_titre($langs->trans("ProductAssociationList"), $morehtmlright, '');
412
413
414 // Form with product to add
415 if ((empty($action) || $action == 'view' || $action == 'edit' || $action == 'search' || $action == 're-edit') && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
416 //print '<br>';
417
418 $rowspan = 1;
419 if (isModEnabled('category')) {
420 $rowspan++;
421 }
422
423 print '<form action="'.DOL_URL_ROOT.'/product/composition/card.php?id='.$id.'" method="POST" class="formtoaddinkit'.($action != 'search' ?' hideobject' : '').'" name="formtoaddinkit" id="formtoaddinkit">';
424 print '<input type="hidden" name="token" value="'.newToken().'">';
425 print '<input type="hidden" name="action" value="search">';
426 print '<input type="hidden" name="id" value="'.$id.'">';
427
428 print '<div class="inline-block">';
429 print $langs->trans("KeywordFilter").': ';
430 print '<input type="text" name="key" value="'.$key.'"> &nbsp; ';
431 print '</div>';
432 if (isModEnabled('category')) {
433 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
434 print '<div class="inline-block">'.$langs->trans("CategoryFilter").': ';
435 print $form->select_all_categories(Categorie::TYPE_PRODUCT, $parent, 'parent').' &nbsp; </div>';
436 print ajax_combobox('parent');
437 }
438 print '<div class="inline-block">';
439 print '<input type="submit" class="button small" value="'.$langs->trans("Search").'">';
440 print '</div><br><br>';
441
442 print '</form>';
443 }
444
445
446 // List of products found to add
447 if ($action == 'search') {
448 print '<form action="'.DOL_URL_ROOT.'/product/composition/card.php?id='.$id.'" method="post" class="formtoaddinkit">';
449 print '<input type="hidden" name="token" value="'.newToken().'">';
450 print '<input type="hidden" name="action" value="add_prod">';
451 print '<input type="hidden" name="id" value="'.$id.'">';
452
453 print '<table class="noborder centpercent">';
454 print '<tr class="liste_titre">';
455 print '<th class="liste_titre">'.$langs->trans("ComposedProduct").'</td>';
456 print '<th class="liste_titre">'.$langs->trans("Label").'</td>';
457 //print '<th class="liste_titre center">'.$langs->trans("IsInPackage").'</td>';
458 print '<th class="liste_titre right">'.$langs->trans("Qty").'</td>';
459 print '<th class="center">'.$langs->trans('ComposedProductIncDecStock').'</th>';
460 print '</tr>';
461 $i = 0;
462 $num = 0;
463 if ($resql) {
464 $num = $db->num_rows($resql);
465
466 if ($num == 0) {
467 print '<tr><td colspan="4"><span class="opacitymedium">'.$langs->trans("NoMatchFound").'</span></td></tr>';
468 }
469
470 $MAX = 100;
471
472 while ($i < min($num, $MAX)) {
473 $objp = $db->fetch_object($resql);
474 if ($objp->rowid != $id) {
475 // check if a product is not already a parent product of this one
476 $prod_arbo = new Product($db);
477 $prod_arbo->id = $objp->rowid;
478 // This type is not supported (not required to have virtual products working).
479 if (getDolGlobalString('PRODUCT_USE_DEPRECATED_ASSEMBLY_AND_STOCK_KIT_TYPE')) {
480 if ($prod_arbo->type == 2 || $prod_arbo->type == 3) {
481 $is_pere = 0;
482 $prod_arbo->get_sousproduits_arbo();
483 // associations sousproduits
484 $prods_arbo = $prod_arbo->get_arbo_each_prod();
485 if (count($prods_arbo) > 0) {
486 foreach ($prods_arbo as $key => $value) {
487 // @phan-suppress-next-line PhanTypeInvalidDimOffset
488 if ($value[1] == $id) {
489 $is_pere = 1;
490 }
491 }
492 }
493 if ($is_pere == 1) {
494 $i++;
495 continue;
496 }
497 }
498 }
499
500 print "\n";
501 print '<tr class="oddeven">';
502
503 $productstatic->id = $objp->rowid;
504 $productstatic->ref = $objp->ref;
505 $productstatic->label = $objp->label;
506 $productstatic->type = $objp->type;
507 $productstatic->entity = $objp->entity;
508 $productstatic->status = $objp->status;
509 $productstatic->status_buy = $objp->status_buy;
510 $productstatic->status_batch = $objp->tobatch;
511
512 print '<td>'.$productstatic->getNomUrl(1, '', 24).'</td>';
513 $labeltoshow = $objp->label;
514 if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->labelm)) {
515 $labeltoshow = $objp->labelm;
516 }
517
518 print '<td>'.$labeltoshow.'</td>';
519
520
521 if ($object->is_sousproduit($id, $objp->rowid)) {
522 //$addchecked = ' checked';
523 $qty = $object->is_sousproduit_qty;
524 $incdec = $object->is_sousproduit_incdec;
525 } else {
526 //$addchecked = '';
527 $qty = 0;
528 $incdec = 0;
529 }
530 // Contained into package
531 /*print '<td class="center"><input type="hidden" name="prod_id_'.$i.'" value="'.$objp->rowid.'">';
532 print '<input type="checkbox" '.$addchecked.'name="prod_id_chk'.$i.'" value="'.$objp->rowid.'"></td>';*/
533 // Qty
534 print '<td class="right"><input type="hidden" name="prod_id_'.$i.'" value="'.$objp->rowid.'"><input type="text" size="2" name="prod_qty_'.$i.'" value="'.($qty ? $qty : '').'"></td>';
535
536 // Inc Dec
537 print '<td class="center">';
538 if ($qty) {
539 print '<input type="checkbox" name="prod_incdec_'.$i.'" value="1" '.($incdec ? 'checked' : '').'>';
540 } else {
541 // TODO Hide field and show it when setting a qty
542 print '<input type="checkbox" name="prod_incdec_'.$i.'" value="1" checked>';
543 //print '<input type="checkbox" disabled name="prod_incdec_'.$i.'" value="1" checked>';
544 }
545 print '</td>';
546
547 print '</tr>';
548 }
549 $i++;
550 }
551 if ($num > $MAX) {
552 print '<tr class="oddeven">';
553 print '<td><span class="opacitymedium">'.$langs->trans("More").'...</span></td>';
554 print '<td></td>';
555 print '<td></td>';
556 print '<td></td>';
557 print '</tr>';
558 }
559 } else {
560 dol_print_error($db);
561 }
562 print '</table>';
563 print '<input type="hidden" name="max_prod" value="'.$i.'">';
564
565 if ($num > 0) {
566 print '<div class="center">';
567 print '<input type="submit" class="button button-save" name="save" value="'.$langs->trans("Add").'/'.$langs->trans("Update").'">';
568 print '<input type="submit" class="button button-cancel" name="cancel" value="'.$langs->trans("Cancel").'">';
569 print '</div><br><br>';
570 }
571
572 print '</form>';
573 }
574
575
576 // Section of existing products in kit
577 print '<form name="formComposedProduct" action="'.$_SERVER['PHP_SELF'].'" method="post">';
578 print '<input type="hidden" name="token" value="'.newToken().'" />';
579 print '<input type="hidden" name="action" value="save_composed_product" />';
580 print '<input type="hidden" name="id" value="'.$id.'" />';
581
582 print '<div class="div-table-responsive-no-min">';
583 print '<table id="tablelines" class="ui-sortable liste noborder nobottom">';
584
585 print '<tr class="liste_titre nodrag nodrop">';
586 // Rank
587 print '<th>'.$langs->trans('Position').'</th>';
588 // Product ref
589 print '<th>'.$langs->trans('ComposedProduct').'</th>';
590 // Product label
591 print '<th>'.$langs->trans('Label').'</th>';
592 // Min supplier price
593 print '<th class="right" colspan="2">'.$langs->trans('MinSupplierPrice').'</th>';
594 // Min customer price
595 print '<th class="right" colspan="2">'.$langs->trans('MinCustomerPrice').'</th>';
596 // Stock
597 if (isModEnabled('stock')) {
598 print '<th class="right">'.$langs->trans('Stock').'</th>';
599 }
600 // Hook fields
601 $parameters = array();
602 $reshook = $hookmanager->executeHooks('printFieldListTitle', $parameters); // Note that $action and $object may have been modified by hook
603 print $hookmanager->resPrint;
604 // Qty in kit
605 print '<th class="right">'.$langs->trans('Qty').'</th>';
606 // Stoc inc/dev
607 print '<th class="center">'.$langs->trans('ComposedProductIncDecStock').'</th>';
608 // Move
609 print '<th class="linecolmove" style="width: 10px"></th>';
610 print '</tr>'."\n";
611
612 $totalsell = 0;
613 $total = 0;
614 if (count($prods_arbo)) {
615 foreach ($prods_arbo as $value) {
616 $productstatic->fetch($value['id']);
617
618 if ($value['level'] <= 1) {
619 print '<tr id="'.$object->sousprods[$parent_label][$value['id']][6].'" class="drag drop oddeven level1">';
620
621 // Rank
622 print '<td>'.$object->sousprods[$parent_label][$value['id']][7].'</td>';
623
624 $notdefined = 0;
625 $nb_of_subproduct = $value['nb'];
626
627 // Product ref
628 print '<td>'.$productstatic->getNomUrl(1, 'composition').'</td>';
629
630 // Product label
631 print '<td title="'.dol_escape_htmltag($productstatic->label).'" class="tdoverflowmax150">'.dol_escape_htmltag($productstatic->label).'</td>';
632
633 // Best buying price
634 print '<td class="right"><span class="small">';
635 if ($product_fourn->find_min_price_product_fournisseur($productstatic->id) > 0) {
636 print $langs->trans("BuyingPriceMinShort").': ';
637 if ($product_fourn->product_fourn_price_id > 0) {
638 print $product_fourn->display_price_product_fournisseur(0, 0);
639 } else {
640 print $langs->trans("NotDefined");
641 $notdefined++;
642 $atleastonenotdefined++;
643 }
644 }
645 print '</span>';
646 print '</td>';
647
648 // For avoid a non-numeric value
649 $fourn_unitprice = (!empty($product_fourn->fourn_unitprice) ? $product_fourn->fourn_unitprice : 0);
650 $fourn_remise_percent = (!empty($product_fourn->fourn_remise_percent) ? $product_fourn->fourn_remise_percent : 0);
651 $fourn_remise = (!empty($product_fourn->fourn_remise) ? $product_fourn->fourn_remise : 0);
652
653 $unitline = price2num(($fourn_unitprice * (1 - ($fourn_remise_percent / 100)) - $fourn_remise), 'MU');
654 $totalline = price2num($value['nb'] * ($fourn_unitprice * (1 - ($fourn_remise_percent / 100)) - $fourn_remise), 'MT');
655 $total += $totalline;
656
657 print '<td class="right nowraponall">';
658 print($notdefined ? '' : ($value['nb'] > 1 ? $value['nb'].'x ' : '').'<span class="amount">'.price($unitline, 0, '', 0, 0, -1, $conf->currency)).'</span>';
659 print '</td>';
660
661 // Best selling price
662 $pricesell = $productstatic->price;
663 if (getDolGlobalString('PRODUIT_MULTIPRICES')) {
664 $pricesell = 'Variable';
665 } else {
666 $totallinesell = price2num($value['nb'] * ($pricesell), 'MT');
667 $totalsell += $totallinesell;
668 }
669 print '<td class="right" colspan="2">';
670 print($notdefined ? '' : ($value['nb'] > 1 ? $value['nb'].'x ' : ''));
671 if (is_numeric($pricesell)) {
672 print '<span class="amount">'.price($pricesell, 0, '', 0, 0, -1, $conf->currency).'</span>';
673 } else {
674 print '<span class="opacitymedium">'.$langs->trans($pricesell).'</span>';
675 }
676 print '</td>';
677
678 // Stock
679 if (isModEnabled('stock')) {
680 print '<td class="right">'.$value['stock'].'</td>'; // Real stock
681 }
682
683 // Hook fields
684 $parameters = array();
685 $reshook = $hookmanager->executeHooks('printFieldListValue', $parameters, $productstatic); // Note that $action and $object may have been modified by hook
686 print $hookmanager->resPrint;
687
688 // Qty + IncDec
689 if ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer')) {
690 print '<td class="center"><input type="text" value="'.$nb_of_subproduct.'" name="TProduct['.$productstatic->id.'][qty]" class="right width40" /></td>';
691 print '<td class="center"><input type="checkbox" name="TProduct['.$productstatic->id.'][incdec]" value="1" '.($value['incdec'] == 1 ? 'checked' : '').' /></td>';
692 } else {
693 print '<td>'.$nb_of_subproduct.'</td>';
694 print '<td>'.($value['incdec'] == 1 ? 'x' : '').'</td>';
695 }
696
697 // Move action
698 print '<td class="linecolmove tdlineupdown center"></td>';
699
700 print '</tr>'."\n";
701 } else {
702 $hide = '';
703 if (!getDolGlobalString('PRODUCT_SHOW_SUB_SUB_PRODUCTS')) {
704 $hide = ' hideobject'; // By default, we do not show this. It makes screen very difficult to understand
705 }
706
707 print '<tr class="oddeven'.$hide.'" id="sub-'.$value['id_parent'].'" data-ignoreidfordnd=1>';
708
709 //$productstatic->ref=$value['label'];
710 $productstatic->ref = $value['ref'];
711
712 // Rankd
713 print '<td></td>';
714
715 // Product ref
716 print '<td>';
717 for ($i = 0; $i < $value['level']; $i++) {
718 print ' &nbsp; &nbsp; '; // Add indentation
719 }
720 print $productstatic->getNomUrl(1, 'composition');
721 print '</td>';
722
723 // Product label
724 print '<td>'.dol_escape_htmltag($productstatic->label).'</td>';
725
726 // Best buying price
727 print '<td>&nbsp;</td>';
728 print '<td>&nbsp;</td>';
729 // Best selling price
730 print '<td>&nbsp;</td>';
731 print '<td>&nbsp;</td>';
732
733 // Stock
734 if (isModEnabled('stock')) {
735 print '<td></td>'; // Real stock
736 }
737
738 // Hook fields
739 $parameters = array();
740 $reshook = $hookmanager->executeHooks('printFieldListValue', $parameters, $productstatic); // Note that $action and $object may have been modified by hook
741 print $hookmanager->resPrint;
742
743 // Qty in kit
744 print '<td class="right">'.dol_escape_htmltag((string) $value['nb']).'</td>';
745
746 // Inc/dec
747 print '<td>&nbsp;</td>';
748
749 // Action move
750 print '<td>&nbsp;</td>';
751
752 print '</tr>'."\n";
753 }
754 }
755
756
757 // Total
758
759 print '<tr class="liste_total">';
760
761 // Rank
762 print '<td></td>';
763
764 // Product ref
765 print '<td class="liste_total"></td>';
766
767 // Product label
768 print '<td class="liste_total"></td>';
769
770 // Minimum buying price
771 print '<td class="liste_total right">';
772 print $langs->trans("TotalBuyingPriceMinShort");
773 print '</td>';
774
775 print '<td class="liste_total right">';
776 if ($atleastonenotdefined) {
777 print $langs->trans("Unknown").' ('.$langs->trans("SomeSubProductHaveNoPrices").')';
778 }
779 print($atleastonenotdefined ? '' : price($total, 0, '', 0, 0, -1, $conf->currency));
780 print '</td>';
781
782 // Minimum selling price
783 print '<td class="liste_total right">';
784 print $langs->trans("TotalSellingPriceMinShort");
785 print '</td>';
786
787 print '<td class="liste_total right">';
788 if ($atleastonenotdefined) {
789 print $langs->trans("Unknown").' ('.$langs->trans("SomeSubProductHaveNoPrices").')';
790 }
791 print($atleastonenotdefined ? '' : price($totalsell, 0, '', 0, 0, -1, $conf->currency));
792 print '</td>';
793
794 // Stock
795 if (isModEnabled('stock')) {
796 print '<td class="liste_total right">&nbsp;</td>';
797 }
798
799 print '<td></td>';
800
801 print '<td class="center">';
802 if ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer')) {
803 print '<input type="submit" class="button button-save" value="'.$langs->trans("Save").'">';
804 }
805 print '</td>';
806
807 print '<td></td>';
808
809 print '</tr>'."\n";
810 } else {
811 $colspan = 10;
812 if (isModEnabled('stock')) {
813 $colspan++;
814 }
815
816 print '<tr class="oddeven">';
817 print '<td colspan="'.$colspan.'"><span class="opacitymedium">'.$langs->trans("None").'</span></td>';
818 print '</tr>';
819 }
820
821 print '</table>';
822 print '</div>';
823
824 /*if($user->rights->produit->creer || $user->hasRight('service', 'creer')) {
825 print '<input type="submit" class="button button-save" value="'.$langs->trans("Save").'">';
826 }*/
827
828 print '</form>';
829 print '</div>';
830 }
831}
832
833// End of page
834llxFooter();
835$db->close();
$id
Support class for third parties, contacts, members, users or resources.
Definition account.php:47
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
ajax_combobox($htmlname, $events=array(), $minLengthToAutocomplete=0, $forcefocus=0, $widthTypeOfAutocomplete='resolve', $idforemptyvalue='-1', $morecss='')
Convert a html select field into an ajax combobox.
Definition ajax.lib.php:475
llxFooter($comment='', $zone='private', $disabledoutputofmessages=0)
Empty footer.
Definition wrapper.php:91
if(!defined('NOREQUIRESOC')) if(!defined( 'NOREQUIRETRAN')) if(!defined('NOTOKENRENEWAL')) if(!defined( 'NOREQUIREMENU')) if(!defined('NOREQUIREHTML')) if(!defined( 'NOREQUIREAJAX')) llxHeader($head='', $title='', $help_url='', $target='', $disablejs=0, $disablehead=0, $arrayofjs='', $arrayofcss='', $morequerystring='', $morecssonbody='', $replacemainareaby='', $disablenofollow=0, $disablenoindex=0)
Empty header.
Definition wrapper.php:73
Class to manage warehouses.
Class to manage generation of HTML components Only common components must be here.
Class with static methods for building HTML components related to products Only components common to ...
Class to manage predefined suppliers products.
Class to manage products or services.
const TYPE_PRODUCT
Regular product.
const TYPE_SERVICE
Service.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
GETPOSTINT($paramname, $method=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
dol_get_fiche_head($links=array(), $active='', $title='', $notab=0, $picto='', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limittoshow=0, $moretabssuffix='', $dragdropfile=0, $morecssdiv='')
Show tabs of a record.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dolGetButtonTitle($label, $helpText='', $iconClass='fa fa-file', $url='', $id='', $status=1, $params=array())
Function dolGetButtonTitle : this kind of buttons are used in title in list.
dol_get_fiche_end($notab=0)
Return tab footer of a card.
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.
natural_search($fields, $value, $mode=0, $nofirstand=0, $sqltoadd='')
Generate natural SQL search string for a criteria (this criteria can be tested on one or several fiel...
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
load_fiche_titre($title, $morehtmlright='', $picto='generic', $pictoisfullpath=0, $id='', $morecssontable='', $morehtmlcenter='', $morecssonpicto='widthpictotitle')
Load a title with picto.
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '…' if string larger than length.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input f...
product_prepare_head($object)
Prepare array with list of tabs.
restrictedArea(User $user, $features, $object=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.