dolibarr 25.0.0-alpha
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 $action = 'edit';
117 } else {
118 $error++;
119 $action = 're-edit';
120 if ($object->error == "isFatherOfThis") {
121 setEventMessages($langs->trans("ErrorAssociationIsFatherOfThis"), null, 'errors');
122 } else {
123 setEventMessages($object->error, $object->errors, 'errors');
124 }
125 }
126 } else {
127 if ($object->del_sousproduit($id, GETPOSTINT("prod_id_" . $i)) > 0) {
128 $action = 'edit';
129 } else {
130 $error++;
131 $action = 're-edit';
132 setEventMessages($object->error, $object->errors, 'errors');
133 }
134 }
135 }
136
137 if (!$error) {
138 header("Location: " . $_SERVER["PHP_SELF"] . '?id=' . $object->id);
139 exit;
140 }
141 } elseif ($action === 'save_composed_product') {
142 $TProduct = GETPOST('TProduct', 'array');
143 if (!empty($TProduct)) {
144 foreach ($TProduct as $id_product => $row) {
145 if ($row['qty'] > 0) {
146 $object->update_sousproduit($id, $id_product, $row['qty'], isset($row['incdec']) ? 1 : 0);
147 } else {
148 $object->del_sousproduit($id, $id_product);
149 }
150 }
151 setEventMessages('RecordSaved', null);
152 }
153 $action = '';
154 header("Location: " . $_SERVER["PHP_SELF"] . '?id=' . $object->id);
155 exit;
156 }
157}
158
159/*
160 * View
161 */
162
163$form = new Form($db);
164$formproduct = new FormProduct($db);
165$product_fourn = new ProductFournisseur($db);
166$productstatic = new Product($db);
167$resql = false;
168// action searching products by keywords and/or by category
169if ($action == 'search') {
170 $current_lang = $langs->getDefaultLang();
171
172 $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,';
173 $sql .= ' p.fk_product_type, p.tms as datem, p.tobatch';
174 $sql .= ', p.tosell as status, p.tobuy as status_buy';
175 if (getDolGlobalInt('MAIN_MULTILANGS')) {
176 $sql .= ', pl.label as labelm, pl.description as descriptionm';
177 }
178
179 $parameters = array();
180 $reshook = $hookmanager->executeHooks('printFieldListSelect', $parameters, $object); // Note that $action and $object may have been modified by hook
181 $sql .= $hookmanager->resPrint;
182
183 $sql .= ' FROM '.MAIN_DB_PREFIX.'product as p';
184 $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'categorie_product as cp ON p.rowid = cp.fk_product';
185 if (getDolGlobalInt('MAIN_MULTILANGS')) {
186 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product_lang as pl ON pl.fk_product = p.rowid AND lang='".$db->escape($current_lang)."'";
187 }
188 $sql .= ' WHERE p.entity IN ('.getEntity('product').')';
189
190 $parameters = array();
191 $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters, $object); // Note that $action and $object may have been modified by hook
192 $sql .= $hookmanager->resPrint;
193
194 if ($key != "") {
195 // For natural search
196 $params = array('p.ref', 'p.label', 'p.description', 'p.note');
197 // multilang
198 if (getDolGlobalInt('MAIN_MULTILANGS')) {
199 $params[] = 'pl.label';
200 $params[] = 'pl.description';
201 $params[] = 'pl.note';
202 }
203 if (isModEnabled('barcode')) {
204 $params[] = 'p.barcode';
205 }
206 $sql .= natural_search($params, $key);
207 }
208 if (isModEnabled('category') && !empty($parent) && $parent != -1) {
209 $sql .= " AND cp.fk_categorie ='".$db->escape($parent)."'";
210 }
211 $sql .= " ORDER BY p.ref ASC";
212
213 $resql = $db->query($sql);
214}
215
216$title = $langs->trans('ProductServiceCard');
217$help_url = '';
218$shortlabel = dol_trunc($object->label, 16);
219if (GETPOST("type") == '0' || ($object->type == Product::TYPE_PRODUCT)) {
220 $title = $langs->trans('Product')." ".$shortlabel." - ".$langs->trans('AssociatedProducts');
221 $help_url = 'EN:Module_Products|FR:Module_Produits|ES:M&oacute;dulo_Productos|DE:Modul_Produkte';
222}
223if (GETPOST("type") == '1' || ($object->type == Product::TYPE_SERVICE)) {
224 $title = $langs->trans('Service')." ".$shortlabel." - ".$langs->trans('AssociatedProducts');
225 $help_url = 'EN:Module_Services_En|FR:Module_Services|ES:M&oacute;dulo_Servicios|DE:Modul_Leistungen';
226}
227
228llxHeader('', $title, $help_url, '', 0, 0, '', '', '', 'mod-product page-composition_card');
229
230$head = product_prepare_head($object);
231
232$titre = $langs->trans("CardProduct".$object->type);
233$picto = ($object->type == Product::TYPE_SERVICE ? 'service' : 'product');
234
235print dol_get_fiche_head($head, 'subproduct', $titre, -1, $picto);
236
237
238if ($id > 0 || !empty($ref)) {
239 /*
240 * Product card
241 */
242
243 $iskit = $object->hasFatherOrChild(1);
244
245 if ($user->hasRight('produit', 'lire') || $user->hasRight('service', 'lire')) {
246 $linkback = '<a href="'.DOL_URL_ROOT.'/product/list.php?restore_lastsearch_values=1&type='.$object->type.'">'.$langs->trans("BackToList").'</a>';
247
248 $shownav = 1;
249 if ($user->socid && !in_array('product', explode(',', getDolGlobalString('MAIN_MODULES_FOR_EXTERNAL')))) {
250 $shownav = 0;
251 }
252
253 dol_banner_tab($object, 'ref', $linkback, $shownav, 'ref', '');
254
255 if ($object->type != Product::TYPE_SERVICE || getDolGlobalString('STOCK_SUPPORTS_SERVICES') || (!getDolGlobalString('PRODUIT_MULTIPRICES') && !getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES'))) {
256 print '<div class="fichecenter">';
257 print '<div class="fichehalfleft">';
258 print '<div class="underbanner clearboth"></div>';
259
260 print '<table class="border centpercent tableforfield">';
261
262 // Type
263 if (isModEnabled("product") && isModEnabled("service")) {
264 $typeformat = 'select;0:'.$langs->trans("Product").',1:'.$langs->trans("Service");
265 print '<tr><td class="titlefieldmiddle">';
266 print (!getDolGlobalString('PRODUCT_DENY_CHANGE_PRODUCT_TYPE')) ? $form->editfieldkey("Type", 'fk_product_type', (string) $object->type, $object, 0, $typeformat) : $langs->trans('Type');
267 print '</td><td>';
268 print $form->editfieldval("Type", 'fk_product_type', $object->type, $object, 0, $typeformat);
269 print '</td></tr>';
270 }
271
272 // Stockable product / default warehouse
273 if (($object->isProduct() || getDolGlobalInt('STOCK_SUPPORTS_SERVICES')) && isModEnabled('stock')) { // Do not use isStockManaged here.We must sow info even if stock not managed
274 print '<tr><td>' . $form->textwithpicto($langs->trans("StockableProduct"), $langs->trans('StockableProductDescription')) . '</td>';
275 print '<td>';
276 if ($iskit) {
277 print '<input type="checkbox" readonly disabled> <span class="opacitymedium">' . $langs->trans("NotSupportedOnKits").'</span>';
278 } else {
279 print '<input type="checkbox" readonly disabled '.($object->stockable_product == 1 ? 'checked' : '').'>';
280 }
281 print '</td></tr>';
282
283 if ($object->isStockManaged() && !$iskit) {
284 $warehouse = new Entrepot($db);
285 $warehouse->fetch($object->fk_default_warehouse);
286
287 print '<tr><td>'.$langs->trans("DefaultWarehouse").'</td><td>';
288 print(!empty($warehouse->id) ? $warehouse->getNomUrl(1) : '');
289 print '</td>';
290 }
291 }
292
293 print '</table>';
294
295 print '</div><div class="fichehalfright">';
296 print '<div class="underbanner clearboth"></div>';
297
298 print '<table class="border centpercent tableforfield">';
299
300 // Nature
301 if ($object->type != Product::TYPE_SERVICE) {
302 if (!getDolGlobalString('PRODUCT_DISABLE_NATURE')) {
303 print '<tr><td>'.$form->textwithpicto($langs->trans("NatureOfProductShort"), $langs->trans("NatureOfProductDesc")).'</td><td>';
304 print $object->getLibFinished();
305 //print $formproduct->selectProductNature('finished', $object->finished);
306 print '</td></tr>';
307 }
308 }
309
310 if (!getDolGlobalString('PRODUIT_MULTIPRICES') && !getDolGlobalString('PRODUIT_CUSTOMER_PRICES_AND_MULTIPRICES')) {
311 // Price
312 print '<tr><td class="titlefield">'.$langs->trans("SellingPrice").'</td><td>';
313 if ($object->price_base_type == 'TTC') {
314 print price($object->price_ttc).' '.$langs->trans($object->price_base_type);
315 } else {
316 print price($object->price).' '.$langs->trans($object->price_base_type ? $object->price_base_type : 'HT');
317 }
318 print '</td></tr>';
319
320 // Price minimum
321 print '<tr><td>'.$langs->trans("MinPrice").'</td><td>';
322 if ($object->price_base_type == 'TTC') {
323 print price($object->price_min_ttc).' '.$langs->trans($object->price_base_type);
324 } else {
325 print price($object->price_min).' '.$langs->trans($object->price_base_type ? $object->price_base_type : 'HT');
326 }
327 print '</td></tr>';
328 }
329
330 print '</table>';
331 print '</div>';
332 print '</div>';
333 }
334
335 print dol_get_fiche_end();
336
337 print '<div class="clearboth"></div>';
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
397 print '<br>'."\n";
398
399
400 print '<div class="fichecenter">';
401
402 $atleastonenotdefined = 0; // at least on buying price not defined
403
404 $tmpurlforbutton = 'javascript:void(0);';
405 $newButtonParams = [
406 'attr' => [
407 'onclick' => 'console.log("click to add a product in kit");jQuery(".formtoaddinkit").toggle();return false;',
408 ]
409 ];
410 $morehtmlright = dolGetButtonTitle($langs->trans('New'), '', 'fa fa-plus-circle', $tmpurlforbutton, '', $usercancreate ? 1 : 0, $newButtonParams);
411
412 print load_fiche_titre($langs->trans("ProductAssociationList"), $morehtmlright, '');
413
414
415 // Form with product to add
416 if ((empty($action) || $action == 'view' || $action == 'edit' || $action == 'search' || $action == 're-edit') && ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer'))) {
417 //print '<br>';
418
419 $rowspan = 1;
420 if (isModEnabled('category')) {
421 $rowspan++;
422 }
423
424 print '<form action="'.DOL_URL_ROOT.'/product/composition/card.php?id='.$id.'" method="POST" class="formtoaddinkit'.($action != 'search' ? ' hideobject' : '').'" name="formtoaddinkit" id="formtoaddinkit">';
425 print '<input type="hidden" name="token" value="'.newToken().'">';
426 print '<input type="hidden" name="action" value="search">';
427 print '<input type="hidden" name="id" value="'.$id.'">';
428
429 print '<div class="inline-block">';
430 print $langs->trans("KeywordFilter").': ';
431 print '<input type="text" name="key" value="'.$key.'"> &nbsp; ';
432 print '</div>';
433 if (isModEnabled('category')) {
434 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
435 print '<div class="inline-block">'.$langs->trans("CategoryFilter").': ';
436 print $form->select_all_categories(Categorie::TYPE_PRODUCT, $parent, 'parent').' &nbsp; </div>';
437 print ajax_combobox('parent');
438 }
439 print '<div class="inline-block">';
440 print '<input type="submit" class="button small" value="'.$langs->trans("Search").'">';
441 print '</div><br><br>';
442
443 print '</form>';
444 }
445
446
447 // List of products found to add
448 if ($action == 'search') {
449 print '<form action="'.DOL_URL_ROOT.'/product/composition/card.php?id='.$id.'" method="post" class="formtoaddinkit">';
450 print '<input type="hidden" name="token" value="'.newToken().'">';
451 print '<input type="hidden" name="action" value="add_prod">';
452 print '<input type="hidden" name="id" value="'.$id.'">';
453
454 print '<table class="noborder centpercent">';
455 print '<tr class="liste_titre">';
456 print '<th class="liste_titre">'.$langs->trans("ComposedProduct").'</td>';
457 print '<th class="liste_titre">'.$langs->trans("Label").'</td>';
458 //print '<th class="liste_titre center">'.$langs->trans("IsInPackage").'</td>';
459 print '<th class="liste_titre right">'.$langs->trans("Qty").'</td>';
460 print '<th class="center">'.$langs->trans('ComposedProductIncDecStock').'</th>';
461 print '</tr>';
462 $i = 0;
463 $num = 0;
464 if ($resql) {
465 $num = $db->num_rows($resql);
466
467 if ($num == 0) {
468 print '<tr><td colspan="4"><span class="opacitymedium">'.$langs->trans("NoMatchFound").'</span></td></tr>';
469 }
470
471 $MAX = 100;
472
473 while ($i < min($num, $MAX)) {
474 $objp = $db->fetch_object($resql);
475 if ($objp->rowid != $id) {
476 // check if a product is not already a parent product of this one
477 $prod_arbo = new Product($db);
478 $prod_arbo->id = $objp->rowid;
479 // This type is not supported (not required to have virtual products working).
480 if (getDolGlobalString('PRODUCT_USE_DEPRECATED_ASSEMBLY_AND_STOCK_KIT_TYPE')) {
481 if ($prod_arbo->type == 2 || $prod_arbo->type == 3) {
482 $is_pere = 0;
483 $prod_arbo->get_sousproduits_arbo();
484 // associations subproducts
485 $prods_arbo = $prod_arbo->get_arbo_each_prod();
486 if (count($prods_arbo) > 0) {
487 foreach ($prods_arbo as $key => $value) {
488 // @phan-suppress-next-line PhanTypeInvalidDimOffset
489 if ($value[1] == $id) {
490 $is_pere = 1;
491 }
492 }
493 }
494 if ($is_pere == 1) {
495 $i++;
496 continue;
497 }
498 }
499 }
500
501 print "\n";
502 print '<tr class="oddeven">';
503
504 $productstatic->id = $objp->rowid;
505 $productstatic->ref = $objp->ref;
506 $productstatic->label = $objp->label;
507 $productstatic->type = $objp->type;
508 $productstatic->entity = $objp->entity;
509 $productstatic->status = $objp->status;
510 $productstatic->status_buy = $objp->status_buy;
511 $productstatic->status_batch = $objp->tobatch;
512
513 print '<td>'.$productstatic->getNomUrl(1, '', 24).'</td>';
514 $labeltoshow = $objp->label;
515 if (getDolGlobalInt('MAIN_MULTILANGS') && !empty($objp->labelm)) {
516 $labeltoshow = $objp->labelm;
517 }
518
519 print '<td>'.$labeltoshow.'</td>';
520
521
522 if ($object->is_sousproduit($id, $objp->rowid)) {
523 //$addchecked = ' checked';
524 $qty = $object->is_sousproduit_qty;
525 $incdec = $object->is_sousproduit_incdec;
526 } else {
527 //$addchecked = '';
528 $qty = 0;
529 $incdec = 0;
530 }
531 // Contained into package
532 /*print '<td class="center"><input type="hidden" name="prod_id_'.$i.'" value="'.$objp->rowid.'">';
533 print '<input type="checkbox" '.$addchecked.'name="prod_id_chk'.$i.'" value="'.$objp->rowid.'"></td>';*/
534 // Qty
535 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>';
536
537 // Inc Dec
538 print '<td class="center">';
539 if ($qty) {
540 print '<input type="checkbox" name="prod_incdec_'.$i.'" value="1" '.($incdec ? 'checked' : '').'>';
541 } else {
542 // TODO Hide field and show it when setting a qty
543 print '<input type="checkbox" name="prod_incdec_'.$i.'" value="1" checked>';
544 //print '<input type="checkbox" disabled name="prod_incdec_'.$i.'" value="1" checked>';
545 }
546 print '</td>';
547
548 print '</tr>';
549 }
550 $i++;
551 }
552 if ($num > $MAX) {
553 print '<tr class="oddeven">';
554 print '<td><span class="opacitymedium">'.$langs->trans("More").'...</span></td>';
555 print '<td></td>';
556 print '<td></td>';
557 print '<td></td>';
558 print '</tr>';
559 }
560 } else {
562 }
563 print '</table>';
564 print '<input type="hidden" name="max_prod" value="'.$i.'">';
565
566 if ($num > 0) {
567 print '<div class="center">';
568 print '<input type="submit" class="button button-save" name="save" value="'.$langs->trans("Add").'/'.$langs->trans("Update").'">';
569 print '<input type="submit" class="button button-cancel" name="cancel" value="'.$langs->trans("Cancel").'">';
570 print '</div><br><br>';
571 }
572
573 print '</form>';
574 }
575
576
577 // Section of existing products in kit
578 print '<form name="formComposedProduct" action="'.$_SERVER['PHP_SELF'].'" method="post">';
579 print '<input type="hidden" name="token" value="'.newToken().'" />';
580 print '<input type="hidden" name="action" value="save_composed_product" />';
581 print '<input type="hidden" name="id" value="'.$id.'" />';
582
583 print '<div class="div-table-responsive-no-min">';
584 print '<table id="tablelines" class="ui-sortable liste noborder nobottom">';
585
586 print '<tr class="liste_titre nodrag nodrop">';
587 // Rank
588 print '<th>'.$langs->trans('Position').'</th>';
589 // Product ref
590 print '<th>'.$langs->trans('ComposedProduct').'</th>';
591 // Product label
592 print '<th>'.$langs->trans('Label').'</th>';
593 // Min supplier price
594 print '<th class="right" colspan="2">'.$langs->trans('MinSupplierPrice').'</th>';
595 // Min customer price
596 print '<th class="right" colspan="2">'.$langs->trans('MinCustomerPrice').'</th>';
597 // Stock
598 if (isModEnabled('stock')) {
599 print '<th class="right">'.$langs->trans('Stock').'</th>';
600 }
601 // Hook fields
602 $parameters = array();
603 $reshook = $hookmanager->executeHooks('printFieldListTitle', $parameters); // Note that $action and $object may have been modified by hook
604 print $hookmanager->resPrint;
605 // Qty in kit
606 print '<th class="right">'.$langs->trans('Qty').'</th>';
607 // Stoc inc/dev
608 print '<th class="center">'.$langs->trans('ComposedProductIncDecStock').'</th>';
609 // Move
610 print '<th class="linecolmove" style="width: 10px"></th>';
611 print '</tr>'."\n";
612
613 $totalsell = 0;
614 $total = 0;
615 if (count($prods_arbo)) {
616 foreach ($prods_arbo as $value) {
617 $productstatic->fetch($value['id']);
618
619 if ($value['level'] <= 1) {
620 print '<tr id="'.$object->sousprods[$parent_label][$value['id']][6].'" class="drag drop oddeven level1">';
621
622 // Rank
623 print '<td>'.$object->sousprods[$parent_label][$value['id']][7].'</td>';
624
625 $notdefined = 0;
626 $nb_of_subproduct = $value['nb'];
627
628 // Product ref
629 print '<td>'.$productstatic->getNomUrl(1, 'composition').'</td>';
630
631 // Product label
632 print '<td title="'.dol_escape_htmltag($productstatic->label).'" class="tdoverflowmax150">'.dol_escape_htmltag($productstatic->label).'</td>';
633
634 // Best buying price
635 print '<td class="right"><span class="small">';
636 if ($product_fourn->find_min_price_product_fournisseur($productstatic->id) > 0) {
637 print $langs->trans("BuyingPriceMinShort").': ';
638 if ($product_fourn->product_fourn_price_id > 0) {
639 print $product_fourn->display_price_product_fournisseur(0, 0);
640 } else {
641 print $langs->trans("NotDefined");
642 $notdefined++;
643 $atleastonenotdefined++;
644 }
645 }
646 print '</span>';
647 print '</td>';
648
649 // For avoid a non-numeric value
650 $fourn_unitprice = (!empty($product_fourn->fourn_unitprice) ? $product_fourn->fourn_unitprice : 0);
651 $fourn_remise_percent = (!empty($product_fourn->fourn_remise_percent) ? $product_fourn->fourn_remise_percent : 0);
652 $fourn_remise = (!empty($product_fourn->fourn_remise) ? $product_fourn->fourn_remise : 0);
653
654 $unitline = price2num(($fourn_unitprice * (1 - ($fourn_remise_percent / 100)) - $fourn_remise), 'MU');
655 $totalline = price2num($value['nb'] * ($fourn_unitprice * (1 - ($fourn_remise_percent / 100)) - $fourn_remise), 'MT');
656 $total += $totalline;
657
658 print '<td class="right nowraponall">';
659 print($notdefined ? '' : ($value['nb'] > 1 ? $value['nb'].'x ' : '').'<span class="amount">'.price($unitline, 0, '', 0, 0, -1, $conf->currency)).'</span>';
660 print '</td>';
661
662 // Best selling price
663 $pricesell = $productstatic->price;
664 if (getDolGlobalString('PRODUIT_MULTIPRICES')) {
665 $pricesell = 'Variable';
666 } else {
667 $totallinesell = price2num($value['nb'] * ($pricesell), 'MT');
668 $totalsell += $totallinesell;
669 }
670 print '<td class="right" colspan="2">';
671 print($notdefined ? '' : ($value['nb'] > 1 ? $value['nb'].'x ' : ''));
672 if (is_numeric($pricesell)) {
673 print '<span class="amount">'.price($pricesell, 0, '', 0, 0, -1, $conf->currency).'</span>';
674 } else {
675 print '<span class="opacitymedium">'.$langs->trans($pricesell).'</span>';
676 }
677 print '</td>';
678
679 // Stock
680 if (isModEnabled('stock')) {
681 print '<td class="right">'.$value['stock'].'</td>'; // Real stock
682 }
683
684 // Hook fields
685 $parameters = array();
686 $reshook = $hookmanager->executeHooks('printFieldListValue', $parameters, $productstatic); // Note that $action and $object may have been modified by hook
687 print $hookmanager->resPrint;
688
689 // Qty + IncDec
690 if ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer')) {
691 print '<td class="center"><input type="text" value="'.$nb_of_subproduct.'" name="TProduct['.$productstatic->id.'][qty]" class="right width40" /></td>';
692 print '<td class="center"><input type="checkbox" name="TProduct['.$productstatic->id.'][incdec]" value="1" '.($value['incdec'] == 1 ? 'checked' : '').' /></td>';
693 } else {
694 print '<td>'.$nb_of_subproduct.'</td>';
695 print '<td>'.($value['incdec'] == 1 ? 'x' : '').'</td>';
696 }
697
698 // Move action
699 print '<td class="linecolmove tdlineupdown center"></td>';
700
701 print '</tr>'."\n";
702 } else {
703 $hide = '';
704 if (!getDolGlobalString('PRODUCT_SHOW_SUB_SUB_PRODUCTS')) {
705 $hide = ' hideobject'; // By default, we do not show this. It makes screen very difficult to understand
706 }
707
708 print '<tr class="oddeven'.$hide.'" id="sub-'.$value['id_parent'].'" data-ignoreidfordnd=1>';
709
710 //$productstatic->ref=$value['label'];
711 $productstatic->ref = $value['ref'];
712
713 // Rankd
714 print '<td></td>';
715
716 // Product ref
717 print '<td>';
718 for ($i = 0; $i < $value['level']; $i++) {
719 print ' &nbsp; &nbsp; '; // Add indentation
720 }
721 print $productstatic->getNomUrl(1, 'composition');
722 print '</td>';
723
724 // Product label
725 print '<td>'.dol_escape_htmltag($productstatic->label).'</td>';
726
727 // Best buying price
728 print '<td>&nbsp;</td>';
729 print '<td>&nbsp;</td>';
730 // Best selling price
731 print '<td>&nbsp;</td>';
732 print '<td>&nbsp;</td>';
733
734 // Stock
735 if (isModEnabled('stock')) {
736 print '<td></td>'; // Real stock
737 }
738
739 // Hook fields
740 $parameters = array();
741 $reshook = $hookmanager->executeHooks('printFieldListValue', $parameters, $productstatic); // Note that $action and $object may have been modified by hook
742 print $hookmanager->resPrint;
743
744 // Qty in kit
745 print '<td class="right">'.dol_escape_htmltag((string) $value['nb']).'</td>';
746
747 // Inc/dec
748 print '<td>&nbsp;</td>';
749
750 // Action move
751 print '<td>&nbsp;</td>';
752
753 print '</tr>'."\n";
754 }
755 }
756
757
758 // Total
759
760 print '<tr class="liste_total">';
761
762 // Rank
763 print '<td></td>';
764
765 // Product ref
766 print '<td class="liste_total"></td>';
767
768 // Product label
769 print '<td class="liste_total"></td>';
770
771 // Minimum buying price
772 print '<td class="liste_total right">';
773 print $langs->trans("TotalBuyingPriceMinShort");
774 print '</td>';
775
776 print '<td class="liste_total right">';
777 if ($atleastonenotdefined) {
778 print $langs->trans("Unknown").' ('.$langs->trans("SomeSubProductHaveNoPrices").')';
779 }
780 print($atleastonenotdefined ? '' : price($total, 0, '', 0, 0, -1, $conf->currency));
781 print '</td>';
782
783 // Minimum selling price
784 print '<td class="liste_total right">';
785 print $langs->trans("TotalSellingPriceMinShort");
786 print '</td>';
787
788 print '<td class="liste_total right">';
789 if ($atleastonenotdefined) {
790 print $langs->trans("Unknown").' ('.$langs->trans("SomeSubProductHaveNoPrices").')';
791 }
792 print($atleastonenotdefined ? '' : price($totalsell, 0, '', 0, 0, -1, $conf->currency));
793 print '</td>';
794
795 // Stock
796 if (isModEnabled('stock')) {
797 print '<td class="liste_total right">&nbsp;</td>';
798 }
799
800 print '<td></td>';
801
802 print '<td class="center">';
803 if ($user->hasRight('produit', 'creer') || $user->hasRight('service', 'creer')) {
804 print '<input type="submit" class="button button-save" value="'.$langs->trans("Save").'">';
805 }
806 print '</td>';
807
808 print '<td></td>';
809
810 print '</tr>'."\n";
811 } else {
812 $colspan = 10;
813 if (isModEnabled('stock')) {
814 $colspan++;
815 }
816
817 print '<tr class="oddeven">';
818 print '<td colspan="'.$colspan.'"><span class="opacitymedium">'.$langs->trans("None").'</span></td>';
819 print '</tr>';
820 }
821
822 print '</table>';
823 print '</div>';
824
825 /*if($user->rights->produit->creer || $user->hasRight('service', 'creer')) {
826 print '<input type="submit" class="button button-save" value="'.$langs->trans("Save").'">';
827 }*/
828
829 print '</form>';
830 print '</div>';
831 }
832}
833
834// End of page
835llxFooter();
836$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.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
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, $nodefault=0)
Return value of a param into GET or POST supervariable.
GETPOSTINT($paramname, $method=0, $nodefault=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
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.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
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.
Definition html.lib.php:519
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.
Definition html.lib.php:717
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_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...
Definition html.lib.php:172
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.