dolibarr 24.0.0-beta
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-2026 Frédéric France <frederic.france@free.fr>
11 * Copyright (C) 2024-2026 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 searching products by keywords and/or by category
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 print '<div class="clearboth"></div>';
339
340 print '<div class="clearboth"></div><br>';
341
342
343 $prodsfather = $object->getFather(); // Parent Products
344 $object->get_sousproduits_arbo(); // Load $object->sousprods
345 $parent_label = $object->label;
346 $prods_arbo = $object->get_arbo_each_prod();
347
348 $tmpid = $id;
349 if (!empty($conf->use_javascript_ajax)) {
350 $nboflines = $prods_arbo;
351 $table_element_line = 'product_association';
352
353 include DOL_DOCUMENT_ROOT . '/core/tpl/ajaxrow.tpl.php';
354 }
355 $id = $tmpid;
356
357 $nbofsubsubproducts = count($prods_arbo); // This include sub sub product into nb
358 $prodschild = $object->getChildsArbo($id, 1);
359 $nbofsubproducts = count($prodschild); // This include only first level of children
360
361
362 print '<div class="fichecenter">';
363
364 print load_fiche_titre($langs->trans("ProductParentList"), '', '');
365
366 print '<table class="liste noborder">';
367 print '<tr class="liste_titre">';
368 print '<th>'.$langs->trans('ParentProducts').'</th>';
369 print '<th>'.$langs->trans('Label').'</th>';
370 print '<th class="right">'.$langs->trans('Qty').'</th>';
371 print '</td>';
372 if (count($prodsfather) > 0) {
373 foreach ($prodsfather as $value) {
374 $idprod = $value["id"];
375 $productstatic->id = $idprod; // $value["id"];
376 $productstatic->type = $value["fk_product_type"];
377 $productstatic->ref = $value['ref'];
378 $productstatic->label = $value['label'];
379 $productstatic->entity = $value['entity'];
380 $productstatic->status = $value['status'];
381 $productstatic->status_buy = $value['status_buy'];
382
383 print '<tr class="oddeven">';
384 print '<td>'.$productstatic->getNomUrl(1, 'composition').'</td>';
385 print '<td>'.dol_escape_htmltag($productstatic->label).'</td>';
386 print '<td class="right">'.dol_escape_htmltag((string) $value['qty']).'</td>';
387 print '</tr>';
388 }
389 } else {
390 print '<tr class="oddeven">';
391 print '<td colspan="3"><span class="opacitymedium">'.$langs->trans("None").'</span></td>';
392 print '</tr>';
393 }
394 print '</table>';
395 print '</div>';
396
397
398 print '<br>'."\n";
399
400
401 print '<div class="fichecenter">';
402
403 $atleastonenotdefined = 0; // at least on buying price not defined
404
405 $tmpurlforbutton = 'javascript:void(0);';
406 $newButtonParams = [
407 'attr' => [
408 'onclick' => 'console.log("click to add a product in kit");jQuery(".formtoaddinkit").toggle();return false;',
409 ]
410 ];
411 $morehtmlright = dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', $tmpurlforbutton, '', $usercancreate ? 1 : 0, $newButtonParams);
412
413 print load_fiche_titre($langs->trans("ProductAssociationList"), $morehtmlright, '');
414
415
416 // Form with product to add
417 if ((empty($action) || $action == 'view' || $action == 'edit' || $action == 'search' || $action == 're-edit') && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
418 //print '<br>';
419
420 $rowspan = 1;
421 if (isModEnabled('category')) {
422 $rowspan++;
423 }
424
425 print '<form action="'.DOL_URL_ROOT.'/product/composition/card.php?id='.$id.'" method="POST" class="formtoaddinkit'.($action != 'search' ? ' hideobject' : '').'" name="formtoaddinkit" id="formtoaddinkit">';
426 print '<input type="hidden" name="token" value="'.newToken().'">';
427 print '<input type="hidden" name="action" value="search">';
428 print '<input type="hidden" name="id" value="'.$id.'">';
429
430 print '<div class="inline-block">';
431 print $langs->trans("KeywordFilter").': ';
432 print '<input type="text" name="key" value="'.$key.'"> &nbsp; ';
433 print '</div>';
434 if (isModEnabled('category')) {
435 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
436 print '<div class="inline-block">'.$langs->trans("CategoryFilter").': ';
437 print $form->select_all_categories(Categorie::TYPE_PRODUCT, $parent, 'parent').' &nbsp; </div>';
438 print ajax_combobox('parent');
439 }
440 print '<div class="inline-block">';
441 print '<input type="submit" class="button small" value="'.$langs->trans("Search").'">';
442 print '</div><br><br>';
443
444 print '</form>';
445 }
446
447
448 // List of products found to add
449 if ($action == 'search') {
450 print '<form action="'.DOL_URL_ROOT.'/product/composition/card.php?id='.$id.'" method="post" class="formtoaddinkit">';
451 print '<input type="hidden" name="token" value="'.newToken().'">';
452 print '<input type="hidden" name="action" value="add_prod">';
453 print '<input type="hidden" name="id" value="'.$id.'">';
454
455 print '<table class="noborder centpercent">';
456 print '<tr class="liste_titre">';
457 print '<th class="liste_titre">'.$langs->trans("ComposedProduct").'</td>';
458 print '<th class="liste_titre">'.$langs->trans("Label").'</td>';
459 //print '<th class="liste_titre center">'.$langs->trans("IsInPackage").'</td>';
460 print '<th class="liste_titre right">'.$langs->trans("Qty").'</td>';
461 print '<th class="center">'.$langs->trans('ComposedProductIncDecStock').'</th>';
462 print '</tr>';
463 $i = 0;
464 $num = 0;
465 if ($resql) {
466 $num = $db->num_rows($resql);
467
468 if ($num == 0) {
469 print '<tr><td colspan="4"><span class="opacitymedium">'.$langs->trans("NoMatchFound").'</span></td></tr>';
470 }
471
472 $MAX = 100;
473
474 while ($i < min($num, $MAX)) {
475 $objp = $db->fetch_object($resql);
476 if ($objp->rowid != $id) {
477 // check if a product is not already a parent product of this one
478 $prod_arbo = new Product($db);
479 $prod_arbo->id = $objp->rowid;
480 // This type is not supported (not required to have virtual products working).
481 if (getDolGlobalString('PRODUCT_USE_DEPRECATED_ASSEMBLY_AND_STOCK_KIT_TYPE')) {
482 if ($prod_arbo->type == 2 || $prod_arbo->type == 3) {
483 $is_pere = 0;
484 $prod_arbo->get_sousproduits_arbo();
485 // associations subproducts
486 $prods_arbo = $prod_arbo->get_arbo_each_prod();
487 if (count($prods_arbo) > 0) {
488 foreach ($prods_arbo as $key => $value) {
489 // @phan-suppress-next-line PhanTypeInvalidDimOffset
490 if ($value[1] == $id) {
491 $is_pere = 1;
492 }
493 }
494 }
495 if ($is_pere == 1) {
496 $i++;
497 continue;
498 }
499 }
500 }
501
502 print "\n";
503 print '<tr class="oddeven">';
504
505 $productstatic->id = $objp->rowid;
506 $productstatic->ref = $objp->ref;
507 $productstatic->label = $objp->label;
508 $productstatic->type = $objp->type;
509 $productstatic->entity = $objp->entity;
510 $productstatic->status = $objp->status;
511 $productstatic->status_buy = $objp->status_buy;
512 $productstatic->status_batch = $objp->tobatch;
513
514 print '<td>'.$productstatic->getNomUrl(1, '', 24).'</td>';
515 $labeltoshow = $objp->label;
516 if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->labelm)) {
517 $labeltoshow = $objp->labelm;
518 }
519
520 print '<td>'.$labeltoshow.'</td>';
521
522
523 if ($object->is_sousproduit($id, $objp->rowid)) {
524 //$addchecked = ' checked';
525 $qty = $object->is_sousproduit_qty;
526 $incdec = $object->is_sousproduit_incdec;
527 } else {
528 //$addchecked = '';
529 $qty = 0;
530 $incdec = 0;
531 }
532 // Contained into package
533 /*print '<td class="center"><input type="hidden" name="prod_id_'.$i.'" value="'.$objp->rowid.'">';
534 print '<input type="checkbox" '.$addchecked.'name="prod_id_chk'.$i.'" value="'.$objp->rowid.'"></td>';*/
535 // Qty
536 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>';
537
538 // Inc Dec
539 print '<td class="center">';
540 if ($qty) {
541 print '<input type="checkbox" name="prod_incdec_'.$i.'" value="1" '.($incdec ? 'checked' : '').'>';
542 } else {
543 // TODO Hide field and show it when setting a qty
544 print '<input type="checkbox" name="prod_incdec_'.$i.'" value="1" checked>';
545 //print '<input type="checkbox" disabled name="prod_incdec_'.$i.'" value="1" checked>';
546 }
547 print '</td>';
548
549 print '</tr>';
550 }
551 $i++;
552 }
553 if ($num > $MAX) {
554 print '<tr class="oddeven">';
555 print '<td><span class="opacitymedium">'.$langs->trans("More").'...</span></td>';
556 print '<td></td>';
557 print '<td></td>';
558 print '<td></td>';
559 print '</tr>';
560 }
561 } else {
563 }
564 print '</table>';
565 print '<input type="hidden" name="max_prod" value="'.$i.'">';
566
567 if ($num > 0) {
568 print '<div class="center">';
569 print '<input type="submit" class="button button-save" name="save" value="'.$langs->trans("Add").'/'.$langs->trans("Update").'">';
570 print '<input type="submit" class="button button-cancel" name="cancel" value="'.$langs->trans("Cancel").'">';
571 print '</div><br><br>';
572 }
573
574 print '</form>';
575 }
576
577
578 // Section of existing products in kit
579 print '<form name="formComposedProduct" action="'.$_SERVER['PHP_SELF'].'" method="post">';
580 print '<input type="hidden" name="token" value="'.newToken().'" />';
581 print '<input type="hidden" name="action" value="save_composed_product" />';
582 print '<input type="hidden" name="id" value="'.$id.'" />';
583
584 print '<div class="div-table-responsive-no-min">';
585 print '<table id="tablelines" class="ui-sortable liste noborder nobottom">';
586
587 print '<tr class="liste_titre nodrag nodrop">';
588 // Rank
589 print '<th>'.$langs->trans('Position').'</th>';
590 // Product ref
591 print '<th>'.$langs->trans('ComposedProduct').'</th>';
592 // Product label
593 print '<th>'.$langs->trans('Label').'</th>';
594 // Min supplier price
595 print '<th class="right" colspan="2">'.$langs->trans('MinSupplierPrice').'</th>';
596 // Min customer price
597 print '<th class="right" colspan="2">'.$langs->trans('MinCustomerPrice').'</th>';
598 // Stock
599 if (isModEnabled('stock')) {
600 print '<th class="right">'.$langs->trans('Stock').'</th>';
601 }
602 // Hook fields
603 $parameters = array();
604 $reshook = $hookmanager->executeHooks('printFieldListTitle', $parameters); // Note that $action and $object may have been modified by hook
605 print $hookmanager->resPrint;
606 // Qty in kit
607 print '<th class="right">'.$langs->trans('Qty').'</th>';
608 // Stoc inc/dev
609 print '<th class="center">'.$langs->trans('ComposedProductIncDecStock').'</th>';
610 // Move
611 print '<th class="linecolmove" style="width: 10px"></th>';
612 print '</tr>'."\n";
613
614 $totalsell = 0;
615 $total = 0;
616 if (count($prods_arbo)) {
617 foreach ($prods_arbo as $value) {
618 $productstatic->fetch($value['id']);
619
620 if ($value['level'] <= 1) {
621 print '<tr id="'.$object->sousprods[$parent_label][$value['id']][6].'" class="drag drop oddeven level1">';
622
623 // Rank
624 print '<td>'.$object->sousprods[$parent_label][$value['id']][7].'</td>';
625
626 $notdefined = 0;
627 $nb_of_subproduct = $value['nb'];
628
629 // Product ref
630 print '<td>'.$productstatic->getNomUrl(1, 'composition').'</td>';
631
632 // Product label
633 print '<td title="'.dol_escape_htmltag($productstatic->label).'" class="tdoverflowmax150">'.dol_escape_htmltag($productstatic->label).'</td>';
634
635 // Best buying price
636 print '<td class="right"><span class="small">';
637 if ($product_fourn->find_min_price_product_fournisseur($productstatic->id) > 0) {
638 print $langs->trans("BuyingPriceMinShort").': ';
639 if ($product_fourn->product_fourn_price_id > 0) {
640 print $product_fourn->display_price_product_fournisseur(0, 0);
641 } else {
642 print $langs->trans("NotDefined");
643 $notdefined++;
644 $atleastonenotdefined++;
645 }
646 }
647 print '</span>';
648 print '</td>';
649
650 // For avoid a non-numeric value
651 $fourn_unitprice = (!empty($product_fourn->fourn_unitprice) ? $product_fourn->fourn_unitprice : 0);
652 $fourn_remise_percent = (!empty($product_fourn->fourn_remise_percent) ? $product_fourn->fourn_remise_percent : 0);
653 $fourn_remise = (!empty($product_fourn->fourn_remise) ? $product_fourn->fourn_remise : 0);
654
655 $unitline = price2num(($fourn_unitprice * (1 - ($fourn_remise_percent / 100)) - $fourn_remise), 'MU');
656 $totalline = price2num($value['nb'] * ($fourn_unitprice * (1 - ($fourn_remise_percent / 100)) - $fourn_remise), 'MT');
657 $total += $totalline;
658
659 print '<td class="right nowraponall">';
660 print($notdefined ? '' : ($value['nb'] > 1 ? $value['nb'].'x ' : '').'<span class="amount">'.price($unitline, 0, '', 0, 0, -1, $conf->currency)).'</span>';
661 print '</td>';
662
663 // Best selling price
664 $pricesell = $productstatic->price;
665 if (getDolGlobalString('PRODUIT_MULTIPRICES')) {
666 $pricesell = 'Variable';
667 } else {
668 $totallinesell = price2num($value['nb'] * ($pricesell), 'MT');
669 $totalsell += $totallinesell;
670 }
671 print '<td class="right" colspan="2">';
672 print($notdefined ? '' : ($value['nb'] > 1 ? $value['nb'].'x ' : ''));
673 if (is_numeric($pricesell)) {
674 print '<span class="amount">'.price($pricesell, 0, '', 0, 0, -1, $conf->currency).'</span>';
675 } else {
676 print '<span class="opacitymedium">'.$langs->trans($pricesell).'</span>';
677 }
678 print '</td>';
679
680 // Stock
681 if (isModEnabled('stock')) {
682 print '<td class="right">'.$value['stock'].'</td>'; // Real stock
683 }
684
685 // Hook fields
686 $parameters = array();
687 $reshook = $hookmanager->executeHooks('printFieldListValue', $parameters, $productstatic); // Note that $action and $object may have been modified by hook
688 print $hookmanager->resPrint;
689
690 // Qty + IncDec
691 if ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer')) {
692 print '<td class="center"><input type="text" value="'.$nb_of_subproduct.'" name="TProduct['.$productstatic->id.'][qty]" class="right width40" /></td>';
693 print '<td class="center"><input type="checkbox" name="TProduct['.$productstatic->id.'][incdec]" value="1" '.($value['incdec'] == 1 ? 'checked' : '').' /></td>';
694 } else {
695 print '<td>'.$nb_of_subproduct.'</td>';
696 print '<td>'.($value['incdec'] == 1 ? 'x' : '').'</td>';
697 }
698
699 // Move action
700 print '<td class="linecolmove tdlineupdown center"></td>';
701
702 print '</tr>'."\n";
703 } else {
704 $hide = '';
705 if (!getDolGlobalString('PRODUCT_SHOW_SUB_SUB_PRODUCTS')) {
706 $hide = ' hideobject'; // By default, we do not show this. It makes screen very difficult to understand
707 }
708
709 print '<tr class="oddeven'.$hide.'" id="sub-'.$value['id_parent'].'" data-ignoreidfordnd=1>';
710
711 //$productstatic->ref=$value['label'];
712 $productstatic->ref = $value['ref'];
713
714 // Rankd
715 print '<td></td>';
716
717 // Product ref
718 print '<td>';
719 for ($i = 0; $i < $value['level']; $i++) {
720 print ' &nbsp; &nbsp; '; // Add indentation
721 }
722 print $productstatic->getNomUrl(1, 'composition');
723 print '</td>';
724
725 // Product label
726 print '<td>'.dol_escape_htmltag($productstatic->label).'</td>';
727
728 // Best buying price
729 print '<td>&nbsp;</td>';
730 print '<td>&nbsp;</td>';
731 // Best selling price
732 print '<td>&nbsp;</td>';
733 print '<td>&nbsp;</td>';
734
735 // Stock
736 if (isModEnabled('stock')) {
737 print '<td></td>'; // Real stock
738 }
739
740 // Hook fields
741 $parameters = array();
742 $reshook = $hookmanager->executeHooks('printFieldListValue', $parameters, $productstatic); // Note that $action and $object may have been modified by hook
743 print $hookmanager->resPrint;
744
745 // Qty in kit
746 print '<td class="right">'.dol_escape_htmltag((string) $value['nb']).'</td>';
747
748 // Inc/dec
749 print '<td>&nbsp;</td>';
750
751 // Action move
752 print '<td>&nbsp;</td>';
753
754 print '</tr>'."\n";
755 }
756 }
757
758
759 // Total
760
761 print '<tr class="liste_total">';
762
763 // Rank
764 print '<td></td>';
765
766 // Product ref
767 print '<td class="liste_total"></td>';
768
769 // Product label
770 print '<td class="liste_total"></td>';
771
772 // Minimum buying price
773 print '<td class="liste_total right">';
774 print $langs->trans("TotalBuyingPriceMinShort");
775 print '</td>';
776
777 print '<td class="liste_total right">';
778 if ($atleastonenotdefined) {
779 print $langs->trans("Unknown").' ('.$langs->trans("SomeSubProductHaveNoPrices").')';
780 }
781 print($atleastonenotdefined ? '' : price($total, 0, '', 0, 0, -1, $conf->currency));
782 print '</td>';
783
784 // Minimum selling price
785 print '<td class="liste_total right">';
786 print $langs->trans("TotalSellingPriceMinShort");
787 print '</td>';
788
789 print '<td class="liste_total right">';
790 if ($atleastonenotdefined) {
791 print $langs->trans("Unknown").' ('.$langs->trans("SomeSubProductHaveNoPrices").')';
792 }
793 print($atleastonenotdefined ? '' : price($totalsell, 0, '', 0, 0, -1, $conf->currency));
794 print '</td>';
795
796 // Stock
797 if (isModEnabled('stock')) {
798 print '<td class="liste_total right">&nbsp;</td>';
799 }
800
801 print '<td></td>';
802
803 print '<td class="center">';
804 if ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer')) {
805 print '<input type="submit" class="button button-save" value="'.$langs->trans("Save").'">';
806 }
807 print '</td>';
808
809 print '<td></td>';
810
811 print '</tr>'."\n";
812 } else {
813 $colspan = 10;
814 if (isModEnabled('stock')) {
815 $colspan++;
816 }
817
818 print '<tr class="oddeven">';
819 print '<td colspan="'.$colspan.'"><span class="opacitymedium">'.$langs->trans("None").'</span></td>';
820 print '</tr>';
821 }
822
823 print '</table>';
824 print '</div>';
825
826 /*if($user->rights->produit->creer || $user->hasRight('service', 'creer')) {
827 print '<input type="submit" class="button button-save" value="'.$langs->trans("Save").'">';
828 }*/
829
830 print '</form>';
831 print '</div>';
832 }
833}
834
835// End of page
836llxFooter();
837$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:476
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.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $db
API class for accounts.
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 '.
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.