dolibarr 22.0.5
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 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';
35require_once DOL_DOCUMENT_ROOT.'/product/class/html.formproduct.class.php';
36require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
37require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
38require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
39
48// Load translation files required by the page
49$langs->loadLangs(array('bills', 'products', 'stocks'));
50
51$id = GETPOSTINT('id');
52$ref = GETPOST('ref', 'alpha');
53$action = GETPOST('action', 'aZ09');
54$confirm = GETPOST('confirm', 'alpha');
55$cancel = GETPOST('cancel', 'alpha');
56$key = GETPOST('key');
57$parent = GETPOST('parent');
58
59// Security check
60if (!empty($user->socid)) {
61 $socid = $user->socid;
62}
63$fieldvalue = (!empty($id) ? $id : (!empty($ref) ? $ref : ''));
64$fieldtype = (!empty($ref) ? 'ref' : 'rowid');
65
66// Initialize a technical object to manage hooks of page. Note that conf->hooks_modules contains an array of hook context
67$hookmanager->initHooks(array('productcompositioncard', 'globalcard'));
68
69$object = new Product($db);
70$objectid = 0;
71if ($id > 0 || !empty($ref)) {
72 $result = $object->fetch($id, $ref);
73 $objectid = $object->id;
74 $id = $object->id;
75}
76
77$result = restrictedArea($user, 'produit|service', $fieldvalue, 'product&product', '', '', $fieldtype);
78
79if ($object->id > 0) {
80 if ($object->type == $object::TYPE_PRODUCT) {
81 restrictedArea($user, 'produit', $object->id, 'product&product', '', '');
82 }
83 if ($object->type == $object::TYPE_SERVICE) {
84 restrictedArea($user, 'service', $object->id, 'product&product', '', '');
85 }
86} else {
87 restrictedArea($user, 'produit|service', $fieldvalue, 'product&product', '', '', $fieldtype);
88}
89$usercanread = (($object->type == Product::TYPE_PRODUCT && $user->hasRight('produit', 'lire')) || ($object->type == Product::TYPE_SERVICE && $user->hasRight('service', 'lire')));
90$usercancreate = (($object->type == Product::TYPE_PRODUCT && $user->hasRight('produit', 'creer')) || ($object->type == Product::TYPE_SERVICE && $user->hasRight('service', 'creer')));
91$usercandelete = (($object->type == Product::TYPE_PRODUCT && $user->hasRight('produit', 'supprimer')) || ($object->type == Product::TYPE_SERVICE && $user->hasRight('service', 'supprimer')));
92
93
94/*
95 * Actions
96 */
97
98if ($cancel) {
99 $action = '';
100}
101
102$reshook = $hookmanager->executeHooks('doActions', [], $object, $action); // Note that $action and $object may have been modified by some hooks
103if ($reshook < 0) {
104 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
105}
106
107if (empty($reshook)) {
108 // Add subproduct to product
109 if ($action == 'add_prod' && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
110 $error = 0;
111 $maxprod = GETPOSTINT("max_prod");
112
113 for ($i = 0; $i < $maxprod; $i++) {
114 $qty = price2num(GETPOST("prod_qty_" . $i, 'alpha'), 'MS');
115 if ($qty > 0) {
116 if ($object->add_sousproduit($id, GETPOSTINT("prod_id_" . $i), (float) $qty, GETPOSTINT("prod_incdec_" . $i)) > 0) {
117 //var_dump($i.' '.GETPOST("prod_id_".$i, 'int'), $qty, GETPOST("prod_incdec_".$i, 'int'));
118 $action = 'edit';
119 } else {
120 $error++;
121 $action = 're-edit';
122 if ($object->error == "isFatherOfThis") {
123 setEventMessages($langs->trans("ErrorAssociationIsFatherOfThis"), null, 'errors');
124 } else {
125 setEventMessages($object->error, $object->errors, 'errors');
126 }
127 }
128 } else {
129 if ($object->del_sousproduit($id, GETPOSTINT("prod_id_" . $i)) > 0) {
130 $action = 'edit';
131 } else {
132 $error++;
133 $action = 're-edit';
134 setEventMessages($object->error, $object->errors, 'errors');
135 }
136 }
137 }
138
139 if (!$error) {
140 header("Location: " . $_SERVER["PHP_SELF"] . '?id=' . $object->id);
141 exit;
142 }
143 } elseif ($action === 'save_composed_product') {
144 $TProduct = GETPOST('TProduct', 'array');
145 if (!empty($TProduct)) {
146 foreach ($TProduct as $id_product => $row) {
147 if ($row['qty'] > 0) {
148 $object->update_sousproduit($id, $id_product, $row['qty'], isset($row['incdec']) ? 1 : 0);
149 } else {
150 $object->del_sousproduit($id, $id_product);
151 }
152 }
153 setEventMessages('RecordSaved', null);
154 }
155 $action = '';
156 header("Location: " . $_SERVER["PHP_SELF"] . '?id=' . $object->id);
157 exit;
158 }
159}
160
161/*
162 * View
163 */
164
165$form = new Form($db);
166$formproduct = new FormProduct($db);
167$product_fourn = new ProductFournisseur($db);
168$productstatic = new Product($db);
169$resql = false;
170// action recherche des produits par mot-cle et/ou par categorie
171if ($action == 'search') {
172 $current_lang = $langs->getDefaultLang();
173
174 $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,';
175 $sql .= ' p.fk_product_type, p.tms as datem, p.tobatch';
176 $sql .= ', p.tosell as status, p.tobuy as status_buy';
177 if (getDolGlobalInt('MAIN_MULTILANGS')) {
178 $sql .= ', pl.label as labelm, pl.description as descriptionm';
179 }
180
181 $parameters = array();
182 $reshook = $hookmanager->executeHooks('printFieldListSelect', $parameters, $object); // Note that $action and $object may have been modified by hook
183 $sql .= $hookmanager->resPrint;
184
185 $sql .= ' FROM '.MAIN_DB_PREFIX.'product as p';
186 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'categorie_product as cp ON p.rowid = cp.fk_product';
187 if (getDolGlobalInt('MAIN_MULTILANGS')) {
188 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product_lang as pl ON pl.fk_product = p.rowid AND lang='".($current_lang)."'";
189 }
190 $sql .= ' WHERE p.entity IN ('.getEntity('product').')';
191
192 $parameters = array();
193 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $object); // Note that $action and $object may have been modified by hook
194 $sql .= $hookmanager->resPrint;
195
196 if ($key != "") {
197 // For natural search
198 $params = array('p.ref', 'p.label', 'p.description', 'p.note');
199 // multilang
200 if (getDolGlobalInt('MAIN_MULTILANGS')) {
201 $params[] = 'pl.label';
202 $params[] = 'pl.description';
203 $params[] = 'pl.note';
204 }
205 if (isModEnabled('barcode')) {
206 $params[] = 'p.barcode';
207 }
208 $sql .= natural_search($params, $key);
209 }
210 if (isModEnabled('category') && !empty($parent) && $parent != -1) {
211 $sql .= " AND cp.fk_categorie ='".$db->escape($parent)."'";
212 }
213 $sql .= " ORDER BY p.ref ASC";
214
215 $resql = $db->query($sql);
216}
217
218$title = $langs->trans('ProductServiceCard');
219$help_url = '';
220$shortlabel = dol_trunc($object->label, 16);
221if (GETPOST("type") == '0' || ($object->type == Product::TYPE_PRODUCT)) {
222 $title = $langs->trans('Product')." ".$shortlabel." - ".$langs->trans('AssociatedProducts');
223 $help_url = 'EN:Module_Products|FR:Module_Produits|ES:M&oacute;dulo_Productos|DE:Modul_Produkte';
224}
225if (GETPOST("type") == '1' || ($object->type == Product::TYPE_SERVICE)) {
226 $title = $langs->trans('Service')." ".$shortlabel." - ".$langs->trans('AssociatedProducts');
227 $help_url = 'EN:Module_Services_En|FR:Module_Services|ES:M&oacute;dulo_Servicios|DE:Modul_Leistungen';
228}
229
230llxHeader('', $title, $help_url, '', 0, 0, '', '', '', 'mod-product page-composition_card');
231
233
234$titre = $langs->trans("CardProduct".$object->type);
235$picto = ($object->type == Product::TYPE_SERVICE ? 'service' : 'product');
236
237print dol_get_fiche_head($head, 'subproduct', $titre, -1, $picto);
238
239
240if ($id > 0 || !empty($ref)) {
241 /*
242 * Product card
243 */
244 if ($user->hasRight('produit', 'lire') || $user->hasRight('service', 'lire')) {
245 $linkback = '<a href="'.DOL_URL_ROOT.'/product/list.php?restore_lastsearch_values=1&type='.$object->type.'">'.$langs->trans("BackToList").'</a>';
246
247 $shownav = 1;
248 if ($user->socid && !in_array('product', explode(',', getDolGlobalString('MAIN_MODULES_FOR_EXTERNAL')))) {
249 $shownav = 0;
250 }
251
252 dol_banner_tab($object, 'ref', $linkback, $shownav, 'ref', '');
253
254 if ($object->type != Product::TYPE_SERVICE || getDolGlobalString('STOCK_SUPPORTS_SERVICES') || !getDolGlobalString('PRODUIT_MULTIPRICES')) {
255 print '<div class="fichecenter">';
256 print '<div class="fichehalfleft">';
257 print '<div class="underbanner clearboth"></div>';
258
259 print '<table class="border centpercent tableforfield">';
260
261 // Type
262 if (isModEnabled("product") && isModEnabled("service")) {
263 $typeformat = 'select;0:'.$langs->trans("Product").',1:'.$langs->trans("Service");
264 print '<tr><td class="titlefield">';
265 print (!getDolGlobalString('PRODUCT_DENY_CHANGE_PRODUCT_TYPE')) ? $form->editfieldkey("Type", 'fk_product_type', (string) $object->type, $object, (int) $usercancreate, $typeformat) : $langs->trans('Type');
266 print '</td><td>';
267 print $form->editfieldval("Type", 'fk_product_type', $object->type, $object, $usercancreate, $typeformat);
268 print '</td></tr>';
269 }
270
271 print '</table>';
272
273 print '</div><div class="fichehalfright">';
274 print '<div class="underbanner clearboth"></div>';
275
276 print '<table class="border centpercent tableforfield">';
277
278 // Nature
279 if ($object->type != Product::TYPE_SERVICE) {
280 if (!getDolGlobalString('PRODUCT_DISABLE_NATURE')) {
281 print '<tr><td>'.$form->textwithpicto($langs->trans("NatureOfProductShort"), $langs->trans("NatureOfProductDesc")).'</td><td>';
282 print $object->getLibFinished();
283 //print $formproduct->selectProductNature('finished', $object->finished);
284 print '</td></tr>';
285 }
286 }
287
288 if (!getDolGlobalString('PRODUIT_MULTIPRICES')) {
289 // Price
290 print '<tr><td class="titlefield">'.$langs->trans("SellingPrice").'</td><td>';
291 if ($object->price_base_type == 'TTC') {
292 print price($object->price_ttc).' '.$langs->trans($object->price_base_type);
293 } else {
294 print price($object->price).' '.$langs->trans($object->price_base_type ? $object->price_base_type : 'HT');
295 }
296 print '</td></tr>';
297
298 // Price minimum
299 print '<tr><td>'.$langs->trans("MinPrice").'</td><td>';
300 if ($object->price_base_type == 'TTC') {
301 print price($object->price_min_ttc).' '.$langs->trans($object->price_base_type);
302 } else {
303 print price($object->price_min).' '.$langs->trans($object->price_base_type ? $object->price_base_type : 'HT');
304 }
305 print '</td></tr>';
306 }
307
308 print '</table>';
309 print '</div>';
310 print '</div>';
311 }
312
313 print dol_get_fiche_end();
314
315
316 print '<br><br>';
317
318 $prodsfather = $object->getFather(); // Parent Products
319 $object->get_sousproduits_arbo(); // Load $object->sousprods
320 $parent_label = $object->label;
321 $prods_arbo = $object->get_arbo_each_prod();
322
323 $tmpid = $id;
324 if (!empty($conf->use_javascript_ajax)) {
325 $nboflines = $prods_arbo;
326 $table_element_line = 'product_association';
327
328 include DOL_DOCUMENT_ROOT . '/core/tpl/ajaxrow.tpl.php';
329 }
330 $id = $tmpid;
331
332 $nbofsubsubproducts = count($prods_arbo); // This include sub sub product into nb
333 $prodschild = $object->getChildsArbo($id, 1);
334 $nbofsubproducts = count($prodschild); // This include only first level of children
335
336
337 print '<div class="fichecenter">';
338
339 print load_fiche_titre($langs->trans("ProductParentList"), '', '');
340
341 print '<table class="liste noborder">';
342 print '<tr class="liste_titre">';
343 print '<th>'.$langs->trans('ParentProducts').'</th>';
344 print '<th>'.$langs->trans('Label').'</th>';
345 print '<th class="right">'.$langs->trans('Qty').'</th>';
346 print '</td>';
347 if (count($prodsfather) > 0) {
348 foreach ($prodsfather as $value) {
349 $idprod = $value["id"];
350 $productstatic->id = $idprod; // $value["id"];
351 $productstatic->type = $value["fk_product_type"];
352 $productstatic->ref = $value['ref'];
353 $productstatic->label = $value['label'];
354 $productstatic->entity = $value['entity'];
355 $productstatic->status = $value['status'];
356 $productstatic->status_buy = $value['status_buy'];
357
358 print '<tr class="oddeven">';
359 print '<td>'.$productstatic->getNomUrl(1, 'composition').'</td>';
360 print '<td>'.dol_escape_htmltag($productstatic->label).'</td>';
361 print '<td class="right">'.dol_escape_htmltag((string) $value['qty']).'</td>';
362 print '</tr>';
363 }
364 } else {
365 print '<tr class="oddeven">';
366 print '<td colspan="3"><span class="opacitymedium">'.$langs->trans("None").'</span></td>';
367 print '</tr>';
368 }
369 print '</table>';
370 print '</div>';
371
372 print '<br>'."\n";
373
374
375 print '<div class="fichecenter">';
376
377 $atleastonenotdefined = 0; // at least on buying price not defined
378
379 $tmpurlforbutton = 'javascript:void(0);';
380 $newButtonParams = [
381 'attr' => [
382 'onclick' => 'console.log("click to add a product in kit");jQuery(".formtoaddinkit").toggle();return false;',
383 ]
384 ];
385 $morehtmlright = dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', $tmpurlforbutton, '', $usercancreate ? 1 : 0, $newButtonParams);
386
387 print load_fiche_titre($langs->trans("ProductAssociationList"), $morehtmlright, '');
388
389
390 // Form with product to add
391 if ((empty($action) || $action == 'view' || $action == 'edit' || $action == 'search' || $action == 're-edit') && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
392 //print '<br>';
393
394 $rowspan = 1;
395 if (isModEnabled('category')) {
396 $rowspan++;
397 }
398
399 print '<form action="'.DOL_URL_ROOT.'/product/composition/card.php?id='.$id.'" method="POST" class="formtoaddinkit'.($action != 'search' ?' hideobject' : '').'" name="formtoaddinkit" id="formtoaddinkit">';
400 print '<input type="hidden" name="token" value="'.newToken().'">';
401 print '<input type="hidden" name="action" value="search">';
402 print '<input type="hidden" name="id" value="'.$id.'">';
403
404 print '<div class="inline-block">';
405 print $langs->trans("KeywordFilter").': ';
406 print '<input type="text" name="key" value="'.$key.'"> &nbsp; ';
407 print '</div>';
408 if (isModEnabled('category')) {
409 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
410 print '<div class="inline-block">'.$langs->trans("CategoryFilter").': ';
411 print $form->select_all_categories(Categorie::TYPE_PRODUCT, $parent, 'parent').' &nbsp; </div>';
412 print ajax_combobox('parent');
413 }
414 print '<div class="inline-block">';
415 print '<input type="submit" class="button small" value="'.$langs->trans("Search").'">';
416 print '</div><br><br>';
417
418 print '</form>';
419 }
420
421
422 // List of products found to add
423 if ($action == 'search') {
424 print '<form action="'.DOL_URL_ROOT.'/product/composition/card.php?id='.$id.'" method="post" class="formtoaddinkit">';
425 print '<input type="hidden" name="token" value="'.newToken().'">';
426 print '<input type="hidden" name="action" value="add_prod">';
427 print '<input type="hidden" name="id" value="'.$id.'">';
428
429 print '<table class="noborder centpercent">';
430 print '<tr class="liste_titre">';
431 print '<th class="liste_titre">'.$langs->trans("ComposedProduct").'</td>';
432 print '<th class="liste_titre">'.$langs->trans("Label").'</td>';
433 //print '<th class="liste_titre center">'.$langs->trans("IsInPackage").'</td>';
434 print '<th class="liste_titre right">'.$langs->trans("Qty").'</td>';
435 print '<th class="center">'.$langs->trans('ComposedProductIncDecStock').'</th>';
436 print '</tr>';
437 if ($resql) {
438 $num = $db->num_rows($resql);
439 $i = 0;
440
441 if ($num == 0) {
442 print '<tr><td colspan="4"><span class="opacitymedium">'.$langs->trans("NoMatchFound").'</span></td></tr>';
443 }
444
445 $MAX = 100;
446
447 while ($i < min($num, $MAX)) {
448 $objp = $db->fetch_object($resql);
449 if ($objp->rowid != $id) {
450 // check if a product is not already a parent product of this one
451 $prod_arbo = new Product($db);
452 $prod_arbo->id = $objp->rowid;
453 // This type is not supported (not required to have virtual products working).
454 if (getDolGlobalString('PRODUCT_USE_DEPRECATED_ASSEMBLY_AND_STOCK_KIT_TYPE')) {
455 if ($prod_arbo->type == 2 || $prod_arbo->type == 3) {
456 $is_pere = 0;
457 $prod_arbo->get_sousproduits_arbo();
458 // associations sousproduits
459 $prods_arbo = $prod_arbo->get_arbo_each_prod();
460 if (count($prods_arbo) > 0) {
461 foreach ($prods_arbo as $key => $value) {
462 // @phan-suppress-next-line PhanTypeInvalidDimOffset
463 if ($value[1] == $id) {
464 $is_pere = 1;
465 }
466 }
467 }
468 if ($is_pere == 1) {
469 $i++;
470 continue;
471 }
472 }
473 }
474
475 print "\n";
476 print '<tr class="oddeven">';
477
478 $productstatic->id = $objp->rowid;
479 $productstatic->ref = $objp->ref;
480 $productstatic->label = $objp->label;
481 $productstatic->type = $objp->type;
482 $productstatic->entity = $objp->entity;
483 $productstatic->status = $objp->status;
484 $productstatic->status_buy = $objp->status_buy;
485 $productstatic->status_batch = $objp->tobatch;
486
487 print '<td>'.$productstatic->getNomUrl(1, '', 24).'</td>';
488 $labeltoshow = $objp->label;
489 if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->labelm)) {
490 $labeltoshow = $objp->labelm;
491 }
492
493 print '<td>'.$labeltoshow.'</td>';
494
495
496 if ($object->is_sousproduit($id, $objp->rowid)) {
497 //$addchecked = ' checked';
498 $qty = $object->is_sousproduit_qty;
499 $incdec = $object->is_sousproduit_incdec;
500 } else {
501 //$addchecked = '';
502 $qty = 0;
503 $incdec = 0;
504 }
505 // Contained into package
506 /*print '<td class="center"><input type="hidden" name="prod_id_'.$i.'" value="'.$objp->rowid.'">';
507 print '<input type="checkbox" '.$addchecked.'name="prod_id_chk'.$i.'" value="'.$objp->rowid.'"></td>';*/
508 // Qty
509 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>';
510
511 // Inc Dec
512 print '<td class="center">';
513 if ($qty) {
514 print '<input type="checkbox" name="prod_incdec_'.$i.'" value="1" '.($incdec ? 'checked' : '').'>';
515 } else {
516 // TODO Hide field and show it when setting a qty
517 print '<input type="checkbox" name="prod_incdec_'.$i.'" value="1" checked>';
518 //print '<input type="checkbox" disabled name="prod_incdec_'.$i.'" value="1" checked>';
519 }
520 print '</td>';
521
522 print '</tr>';
523 }
524 $i++;
525 }
526 if ($num > $MAX) {
527 print '<tr class="oddeven">';
528 print '<td><span class="opacitymedium">'.$langs->trans("More").'...</span></td>';
529 print '<td></td>';
530 print '<td></td>';
531 print '<td></td>';
532 print '</tr>';
533 }
534 } else {
535 dol_print_error($db);
536 }
537 print '</table>';
538 print '<input type="hidden" name="max_prod" value="'.$i.'">';
539
540 if ($num > 0) {
541 print '<div class="center">';
542 print '<input type="submit" class="button button-save" name="save" value="'.$langs->trans("Add").'/'.$langs->trans("Update").'">';
543 print '<input type="submit" class="button button-cancel" name="cancel" value="'.$langs->trans("Cancel").'">';
544 print '</div><br><br>';
545 }
546
547 print '</form>';
548 }
549
550
551 // Section of existing products in kit
552 print '<form name="formComposedProduct" action="'.$_SERVER['PHP_SELF'].'" method="post">';
553 print '<input type="hidden" name="token" value="'.newToken().'" />';
554 print '<input type="hidden" name="action" value="save_composed_product" />';
555 print '<input type="hidden" name="id" value="'.$id.'" />';
556
557 print '<div class="div-table-responsive-no-min">';
558 print '<table id="tablelines" class="ui-sortable liste noborder nobottom">';
559
560 print '<tr class="liste_titre nodrag nodrop">';
561 // Rank
562 print '<th>'.$langs->trans('Position').'</th>';
563 // Product ref
564 print '<th>'.$langs->trans('ComposedProduct').'</th>';
565 // Product label
566 print '<th>'.$langs->trans('Label').'</th>';
567 // Min supplier price
568 print '<th class="right" colspan="2">'.$langs->trans('MinSupplierPrice').'</th>';
569 // Min customer price
570 print '<th class="right" colspan="2">'.$langs->trans('MinCustomerPrice').'</th>';
571 // Stock
572 if (isModEnabled('stock')) {
573 print '<th class="right">'.$langs->trans('Stock').'</th>';
574 }
575 // Hook fields
576 $parameters = array();
577 $reshook = $hookmanager->executeHooks('printFieldListTitle', $parameters); // Note that $action and $object may have been modified by hook
578 print $hookmanager->resPrint;
579 // Qty in kit
580 print '<th class="right">'.$langs->trans('Qty').'</th>';
581 // Stoc inc/dev
582 print '<th class="center">'.$langs->trans('ComposedProductIncDecStock').'</th>';
583 // Move
584 print '<th class="linecolmove" style="width: 10px"></th>';
585 print '</tr>'."\n";
586
587 $totalsell = 0;
588 $total = 0;
589 if (count($prods_arbo)) {
590 foreach ($prods_arbo as $value) {
591 $productstatic->fetch($value['id']);
592
593 if ($value['level'] <= 1) {
594 print '<tr id="'.$object->sousprods[$parent_label][$value['id']][6].'" class="drag drop oddeven level1">';
595
596 // Rank
597 print '<td>'.$object->sousprods[$parent_label][$value['id']][7].'</td>';
598
599 $notdefined = 0;
600 $nb_of_subproduct = $value['nb'];
601
602 // Product ref
603 print '<td>'.$productstatic->getNomUrl(1, 'composition').'</td>';
604
605 // Product label
606 print '<td title="'.dol_escape_htmltag($productstatic->label).'" class="tdoverflowmax150">'.dol_escape_htmltag($productstatic->label).'</td>';
607
608 // Best buying price
609 print '<td class="right"><span class="small">';
610 if ($product_fourn->find_min_price_product_fournisseur($productstatic->id) > 0) {
611 print $langs->trans("BuyingPriceMinShort").': ';
612 if ($product_fourn->product_fourn_price_id > 0) {
613 print $product_fourn->display_price_product_fournisseur(0, 0);
614 } else {
615 print $langs->trans("NotDefined");
616 $notdefined++;
617 $atleastonenotdefined++;
618 }
619 }
620 print '</span>';
621 print '</td>';
622
623 // For avoid a non-numeric value
624 $fourn_unitprice = (!empty($product_fourn->fourn_unitprice) ? $product_fourn->fourn_unitprice : 0);
625 $fourn_remise_percent = (!empty($product_fourn->fourn_remise_percent) ? $product_fourn->fourn_remise_percent : 0);
626 $fourn_remise = (!empty($product_fourn->fourn_remise) ? $product_fourn->fourn_remise : 0);
627
628 $unitline = price2num(($fourn_unitprice * (1 - ($fourn_remise_percent / 100)) - $fourn_remise), 'MU');
629 $totalline = price2num($value['nb'] * ($fourn_unitprice * (1 - ($fourn_remise_percent / 100)) - $fourn_remise), 'MT');
630 $total += $totalline;
631
632 print '<td class="right nowraponall">';
633 print($notdefined ? '' : ($value['nb'] > 1 ? $value['nb'].'x ' : '').'<span class="amount">'.price($unitline, 0, '', 0, 0, -1, $conf->currency)).'</span>';
634 print '</td>';
635
636 // Best selling price
637 $pricesell = $productstatic->price;
638 if (getDolGlobalString('PRODUIT_MULTIPRICES')) {
639 $pricesell = 'Variable';
640 } else {
641 $totallinesell = price2num($value['nb'] * ($pricesell), 'MT');
642 $totalsell += $totallinesell;
643 }
644 print '<td class="right" colspan="2">';
645 print($notdefined ? '' : ($value['nb'] > 1 ? $value['nb'].'x ' : ''));
646 if (is_numeric($pricesell)) {
647 print '<span class="amount">'.price($pricesell, 0, '', 0, 0, -1, $conf->currency).'</span>';
648 } else {
649 print '<span class="opacitymedium">'.$langs->trans($pricesell).'</span>';
650 }
651 print '</td>';
652
653 // Stock
654 if (isModEnabled('stock')) {
655 print '<td class="right">'.$value['stock'].'</td>'; // Real stock
656 }
657
658 // Hook fields
659 $parameters = array();
660 $reshook = $hookmanager->executeHooks('printFieldListValue', $parameters, $productstatic); // Note that $action and $object may have been modified by hook
661 print $hookmanager->resPrint;
662
663 // Qty + IncDec
664 if ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer')) {
665 print '<td class="center"><input type="text" value="'.$nb_of_subproduct.'" name="TProduct['.$productstatic->id.'][qty]" class="right width40" /></td>';
666 print '<td class="center"><input type="checkbox" name="TProduct['.$productstatic->id.'][incdec]" value="1" '.($value['incdec'] == 1 ? 'checked' : '').' /></td>';
667 } else {
668 print '<td>'.$nb_of_subproduct.'</td>';
669 print '<td>'.($value['incdec'] == 1 ? 'x' : '').'</td>';
670 }
671
672 // Move action
673 print '<td class="linecolmove tdlineupdown center"></td>';
674
675 print '</tr>'."\n";
676 } else {
677 $hide = '';
678 if (!getDolGlobalString('PRODUCT_SHOW_SUB_SUB_PRODUCTS')) {
679 $hide = ' hideobject'; // By default, we do not show this. It makes screen very difficult to understand
680 }
681
682 print '<tr class="oddeven'.$hide.'" id="sub-'.$value['id_parent'].'" data-ignoreidfordnd=1>';
683
684 //$productstatic->ref=$value['label'];
685 $productstatic->ref = $value['ref'];
686
687 // Rankd
688 print '<td></td>';
689
690 // Product ref
691 print '<td>';
692 for ($i = 0; $i < $value['level']; $i++) {
693 print ' &nbsp; &nbsp; '; // Add indentation
694 }
695 print $productstatic->getNomUrl(1, 'composition');
696 print '</td>';
697
698 // Product label
699 print '<td>'.dol_escape_htmltag($productstatic->label).'</td>';
700
701 // Best buying price
702 print '<td>&nbsp;</td>';
703 print '<td>&nbsp;</td>';
704 // Best selling price
705 print '<td>&nbsp;</td>';
706 print '<td>&nbsp;</td>';
707
708 // Stock
709 if (isModEnabled('stock')) {
710 print '<td></td>'; // Real stock
711 }
712
713 // Hook fields
714 $parameters = array();
715 $reshook = $hookmanager->executeHooks('printFieldListValue', $parameters, $productstatic); // Note that $action and $object may have been modified by hook
716 print $hookmanager->resPrint;
717
718 // Qty in kit
719 print '<td class="right">'.dol_escape_htmltag((string) $value['nb']).'</td>';
720
721 // Inc/dec
722 print '<td>&nbsp;</td>';
723
724 // Action move
725 print '<td>&nbsp;</td>';
726
727 print '</tr>'."\n";
728 }
729 }
730
731
732 // Total
733
734 print '<tr class="liste_total">';
735
736 // Rank
737 print '<td></td>';
738
739 // Product ref
740 print '<td class="liste_total"></td>';
741
742 // Product label
743 print '<td class="liste_total"></td>';
744
745 // Minimum buying price
746 print '<td class="liste_total right">';
747 print $langs->trans("TotalBuyingPriceMinShort");
748 print '</td>';
749
750 print '<td class="liste_total right">';
751 if ($atleastonenotdefined) {
752 print $langs->trans("Unknown").' ('.$langs->trans("SomeSubProductHaveNoPrices").')';
753 }
754 print($atleastonenotdefined ? '' : price($total, 0, '', 0, 0, -1, $conf->currency));
755 print '</td>';
756
757 // Minimum selling price
758 print '<td class="liste_total right">';
759 print $langs->trans("TotalSellingPriceMinShort");
760 print '</td>';
761
762 print '<td class="liste_total right">';
763 if ($atleastonenotdefined) {
764 print $langs->trans("Unknown").' ('.$langs->trans("SomeSubProductHaveNoPrices").')';
765 }
766 print($atleastonenotdefined ? '' : price($totalsell, 0, '', 0, 0, -1, $conf->currency));
767 print '</td>';
768
769 // Stock
770 if (isModEnabled('stock')) {
771 print '<td class="liste_total right">&nbsp;</td>';
772 }
773
774 print '<td></td>';
775
776 print '<td class="center">';
777 if ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer')) {
778 print '<input type="submit" class="button button-save" value="'.$langs->trans("Save").'">';
779 }
780 print '</td>';
781
782 print '<td></td>';
783
784 print '</tr>'."\n";
785 } else {
786 $colspan = 10;
787 if (isModEnabled('stock')) {
788 $colspan++;
789 }
790
791 print '<tr class="oddeven">';
792 print '<td colspan="'.$colspan.'"><span class="opacitymedium">'.$langs->trans("None").'</span></td>';
793 print '</tr>';
794 }
795
796 print '</table>';
797 print '</div>';
798
799 /*if($user->rights->produit->creer || $user->hasRight('service', 'creer')) {
800 print '<input type="submit" class="button button-save" value="'.$langs->trans("Save").'">';
801 }*/
802
803 print '</form>';
804 print '</div>';
805 }
806}
807
808// End of page
809llxFooter();
810$db->close();
$id
Support class for third parties, contacts, members, users or resources.
Definition account.php:48
if( $user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
Definition card.php:67
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 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.
load_fiche_titre($title, $morehtmlright='', $picto='generic', $pictoisfullpath=0, $id='', $morecssontable='', $morehtmlcenter='')
Load a title with picto.
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.
natural_search($fields, $value, $mode=0, $nofirstand=0)
Generate natural SQL search string for a criteria (this criteria can be tested on one or several fiel...
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.
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...
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.
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...
global $conf
The following vars must be defined: $type2label $form $conf, $lang, The following vars may also be de...
Definition member.php:79
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.