30 require
'../../main.inc.php';
31 require_once DOL_DOCUMENT_ROOT.
'/product/class/html.formproduct.class.php';
32 require_once DOL_DOCUMENT_ROOT.
'/core/lib/product.lib.php';
33 require_once DOL_DOCUMENT_ROOT.
'/product/class/product.class.php';
34 require_once DOL_DOCUMENT_ROOT.
'/categories/class/categorie.class.php';
37 $langs->loadLangs(array(
'bills',
'products',
'stocks'));
41 $action =
GETPOST(
'action',
'aZ09');
42 $confirm =
GETPOST(
'confirm',
'alpha');
43 $cancel =
GETPOST(
'cancel',
'alpha');
48 if (!empty($user->socid)) {
49 $socid = $user->socid;
51 $fieldvalue = (!empty($id) ? $id : (!empty($ref) ? $ref :
''));
52 $fieldtype = (!empty($ref) ?
'ref' :
'rowid');
55 $hookmanager->initHooks(array(
'productcompositioncard',
'globalcard'));
59 if ($id > 0 || !empty($ref)) {
60 $result = $object->fetch($id, $ref);
61 $objectid = $object->id;
65 $result =
restrictedArea($user,
'produit|service', $fieldvalue,
'product&product',
'',
'', $fieldtype);
67 if ($object->id > 0) {
68 if ($object->type == $object::TYPE_PRODUCT) {
69 restrictedArea($user,
'produit', $object->id,
'product&product',
'',
'');
71 if ($object->type == $object::TYPE_SERVICE) {
72 restrictedArea($user,
'service', $object->id,
'product&product',
'',
'');
75 restrictedArea($user,
'produit|service', $fieldvalue,
'product&product',
'',
'', $fieldtype);
91 if ($action ==
'add_prod' && ($user->rights->produit->creer || $user->rights->service->creer)) {
93 $maxprod =
GETPOST(
"max_prod",
'int');
95 for ($i = 0; $i < $maxprod; $i++) {
98 if ($object->add_sousproduit($id,
GETPOST(
"prod_id_".$i,
'int'), $qty,
GETPOST(
"prod_incdec_".$i,
'int')) > 0) {
104 if ($object->error ==
"isFatherOfThis") {
105 setEventMessages($langs->trans(
"ErrorAssociationIsFatherOfThis"),
null,
'errors');
111 if ($object->del_sousproduit($id,
GETPOST(
"prod_id_".$i,
'int')) > 0) {
122 header(
"Location: ".$_SERVER[
"PHP_SELF"].
'?id='.$object->id);
125 } elseif ($action ===
'save_composed_product') {
126 $TProduct =
GETPOST(
'TProduct',
'array');
127 if (!empty($TProduct)) {
128 foreach ($TProduct as $id_product => $row) {
129 if ($row[
'qty'] > 0) {
130 $object->update_sousproduit($id, $id_product, $row[
'qty'], isset($row[
'incdec']) ? 1 : 0);
132 $object->del_sousproduit($id, $id_product);
138 header(
"Location: ".$_SERVER[
"PHP_SELF"].
'?id='.$object->id);
150 $productstatic =
new Product($db);
153 if ($action ==
'search') {
154 $current_lang = $langs->getDefaultLang();
156 $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,';
157 $sql .=
' p.fk_product_type, p.tms as datem, p.tobatch';
158 $sql .=
', p.tosell as status, p.tobuy as status_buy';
159 if (!empty($conf->global->MAIN_MULTILANGS)) {
160 $sql .=
', pl.label as labelm, pl.description as descriptionm';
162 $sql .=
' FROM '.MAIN_DB_PREFIX.
'product as p';
163 $sql .=
' LEFT JOIN '.MAIN_DB_PREFIX.
'categorie_product as cp ON p.rowid = cp.fk_product';
164 if (!empty($conf->global->MAIN_MULTILANGS)) {
165 $sql .=
" LEFT JOIN ".MAIN_DB_PREFIX.
"product_lang as pl ON pl.fk_product = p.rowid AND lang='".($current_lang).
"'";
167 $sql .=
' WHERE p.entity IN ('.getEntity(
'product').
')';
170 $params = array(
'p.ref',
'p.label',
'p.description',
'p.note');
172 if (!empty($conf->global->MAIN_MULTILANGS)) {
173 $params[] =
'pl.label';
174 $params[] =
'pl.description';
175 $params[] =
'pl.note';
177 if (!empty($conf->barcode->enabled)) {
178 $params[] =
'p.barcode';
182 if (!empty($conf->categorie->enabled) && !empty($parent) && $parent != -1) {
183 $sql .=
" AND cp.fk_categorie ='".$db->escape($parent).
"'";
185 $sql .=
" ORDER BY p.ref ASC";
187 $resql = $db->query($sql);
190 $title = $langs->trans(
'ProductServiceCard');
192 $shortlabel =
dol_trunc($object->label, 16);
194 $title = $langs->trans(
'Product').
" ".$shortlabel.
" - ".$langs->trans(
'AssociatedProducts');
195 $help_url =
'EN:Module_Products|FR:Module_Produits|ES:Módulo_Productos|DE:Modul_Produkte';
198 $title = $langs->trans(
'Service').
" ".$shortlabel.
" - ".$langs->trans(
'AssociatedProducts');
199 $help_url =
'EN:Module_Services_En|FR:Module_Services|ES:Módulo_Servicios|DE:Modul_Leistungen';
206 $titre = $langs->trans(
"CardProduct".$object->type);
212 if ($id > 0 || !empty($ref)) {
216 if ($user->rights->produit->lire || $user->rights->service->lire) {
217 $linkback =
'<a href="'.DOL_URL_ROOT.
'/product/list.php?restore_lastsearch_values=1">'.$langs->trans(
"BackToList").
'</a>';
220 if ($user->socid && !in_array(
'product', explode(
',', $conf->global->MAIN_MODULES_FOR_EXTERNAL))) {
226 if ($object->type !=
Product::TYPE_SERVICE || !empty($conf->global->STOCK_SUPPORTS_SERVICES) || empty($conf->global->PRODUIT_MULTIPRICES)) {
227 print
'<div class="fichecenter">';
228 print
'<div class="fichehalfleft">';
229 print
'<div class="underbanner clearboth"></div>';
231 print
'<table class="border centpercent tableforfield">';
234 if (!empty($conf->product->enabled) && !empty($conf->service->enabled)) {
235 $typeformat =
'select;0:'.$langs->trans(
"Product").
',1:'.$langs->trans(
"Service");
236 print
'<tr><td class="titlefield">';
237 print (empty($conf->global->PRODUCT_DENY_CHANGE_PRODUCT_TYPE)) ?
$form->editfieldkey(
"Type",
'fk_product_type', $object->type, $object, $usercancreate, $typeformat) : $langs->trans(
'Type');
239 print
$form->editfieldval(
"Type",
'fk_product_type', $object->type, $object, $usercancreate, $typeformat);
245 print
'</div><div class="fichehalfright">';
246 print
'<div class="underbanner clearboth"></div>';
248 print
'<table class="border centpercent tableforfield">';
252 if (empty($conf->global->PRODUCT_DISABLE_NATURE)) {
253 print
'<tr><td>'.$form->textwithpicto($langs->trans(
"NatureOfProductShort"), $langs->trans(
"NatureOfProductDesc")).
'</td><td>';
254 print $object->getLibFinished();
260 if (empty($conf->global->PRODUIT_MULTIPRICES)) {
262 print
'<tr><td class="titlefield">'.$langs->trans(
"SellingPrice").
'</td><td>';
263 if ($object->price_base_type ==
'TTC') {
264 print
price($object->price_ttc).
' '.$langs->trans($object->price_base_type);
266 print
price($object->price).
' '.$langs->trans($object->price_base_type ? $object->price_base_type :
'HT');
271 print
'<tr><td>'.$langs->trans(
"MinPrice").
'</td><td>';
272 if ($object->price_base_type ==
'TTC') {
273 print
price($object->price_min_ttc).
' '.$langs->trans($object->price_base_type);
275 print
price($object->price_min).
' '.$langs->trans($object->price_base_type ? $object->price_base_type :
'HT');
290 $prodsfather = $object->getFather();
291 $object->get_sousproduits_arbo();
292 $parent_label = $object->label;
293 $prods_arbo = $object->get_arbo_each_prod();
296 if (! empty($conf->use_javascript_ajax)) {
297 $nboflines = $prods_arbo;
298 $table_element_line=
'product_association';
300 include DOL_DOCUMENT_ROOT .
'/core/tpl/ajaxrow.tpl.php';
304 $nbofsubsubproducts = count($prods_arbo);
305 $prodschild = $object->getChildsArbo($id, 1);
306 $nbofsubproducts = count($prodschild);
309 print
'<div class="fichecenter">';
313 print
'<table class="liste">';
314 print
'<tr class="liste_titre">';
315 print
'<td>'.$langs->trans(
'ParentProducts').
'</td>';
316 print
'<td>'.$langs->trans(
'Label').
'</td>';
317 print
'<td>'.$langs->trans(
'Qty').
'</td>';
319 if (count($prodsfather) > 0) {
320 foreach ($prodsfather as $value) {
321 $idprod = $value[
"id"];
322 $productstatic->id = $idprod;
323 $productstatic->type = $value[
"fk_product_type"];
324 $productstatic->ref = $value[
'ref'];
325 $productstatic->label = $value[
'label'];
326 $productstatic->entity = $value[
'entity'];
327 $productstatic->status = $value[
'status'];
328 $productstatic->status_buy = $value[
'status_buy'];
330 print
'<tr class="oddeven">';
331 print
'<td>'.$productstatic->getNomUrl(1,
'composition').
'</td>';
332 print
'<td>'.$productstatic->label.
'</td>';
333 print
'<td>'.$value[
'qty'].
'</td>';
337 print
'<tr class="oddeven">';
338 print
'<td colspan="3" class="opacitymedium">'.$langs->trans(
"None").
'</td>';
347 print
'<div class="fichecenter">';
349 $atleastonenotdefined = 0;
352 print
'<form name="formComposedProduct" action="'.$_SERVER[
'PHP_SELF'].
'" method="post">';
353 print
'<input type="hidden" name="token" value="'.newToken().
'" />';
354 print
'<input type="hidden" name="action" value="save_composed_product" />';
355 print
'<input type="hidden" name="id" value="'.$id.
'" />';
357 print
'<table id="tablelines" class="ui-sortable liste nobottom">';
359 print
'<tr class="liste_titre nodrag nodrop">';
361 print
'<td>'.$langs->trans(
'Position').
'</td>';
363 print
'<td>'.$langs->trans(
'ComposedProduct').
'</td>';
365 print
'<td>'.$langs->trans(
'Label').
'</td>';
367 print
'<td class="right" colspan="2">'.$langs->trans(
'MinSupplierPrice').
'</td>';
369 print
'<td class="right" colspan="2">'.$langs->trans(
'MinCustomerPrice').
'</td>';
371 if (!empty($conf->stock->enabled)) {
372 print
'<td class="right">'.$langs->trans(
'Stock').
'</td>';
375 print
'<td class="center">'.$langs->trans(
'Qty').
'</td>';
377 print
'<td class="center">'.$langs->trans(
'ComposedProductIncDecStock').
'</td>';
379 print
'<td class="linecolmove" style="width: 10px"></td>';
384 if (count($prods_arbo)) {
385 foreach ($prods_arbo as $value) {
386 $productstatic->fetch($value[
'id']);
388 if ($value[
'level'] <= 1) {
389 print
'<tr id="'.$object->sousprods[$parent_label][$value[
'id']][6].
'" class="drag drop oddeven level1">';
392 print
'<td>'.$object->sousprods[$parent_label][$value[
'id']][7].
'</td>';
395 $nb_of_subproduct = $value[
'nb'];
398 print
'<td>'.$productstatic->getNomUrl(1,
'composition').
'</td>';
401 print
'<td>'.$productstatic->label.
'</td>';
404 print
'<td class="right">';
405 if ($product_fourn->find_min_price_product_fournisseur($productstatic->id) > 0) {
406 print $langs->trans(
"BuyingPriceMinShort").
': ';
407 if ($product_fourn->product_fourn_price_id > 0) {
408 print $product_fourn->display_price_product_fournisseur(0, 0);
410 print $langs->trans(
"NotDefined"); $notdefined++; $atleastonenotdefined++;
416 $fourn_unitprice = (!empty($product_fourn->fourn_unitprice) ? $product_fourn->fourn_unitprice : 0);
417 $fourn_remise_percent = (!empty($product_fourn->fourn_remise_percent) ? $product_fourn->fourn_remise_percent : 0);
418 $fourn_remise = (!empty($product_fourn->fourn_remise) ? $product_fourn->fourn_remise : 0);
420 $unitline =
price2num(($fourn_unitprice * (1 - ($fourn_remise_percent / 100)) - $fourn_remise),
'MU');
421 $totalline =
price2num($value[
'nb'] * ($fourn_unitprice * (1 - ($fourn_remise_percent / 100)) - $fourn_remise),
'MT');
422 $total += $totalline;
424 print
'<td class="right nowraponall">';
425 print ($notdefined ?
'' : ($value[
'nb'] > 1 ? $value[
'nb'].
'x ' :
'').
'<span class="amount">'.
price($unitline,
'',
'', 0, 0, -1, $conf->currency)).
'</span>';
429 $pricesell = $productstatic->price;
430 if (!empty($conf->global->PRODUIT_MULTIPRICES)) {
431 $pricesell =
'Variable';
433 $totallinesell =
price2num($value[
'nb'] * ($pricesell),
'MT');
434 $totalsell += $totallinesell;
436 print
'<td class="right" colspan="2">';
437 print ($notdefined ?
'' : ($value[
'nb'] > 1 ? $value[
'nb'].
'x ' :
''));
438 if (is_numeric($pricesell)) {
439 print
'<span class="amount">'.price($pricesell,
'',
'', 0, 0, -1, $conf->currency).
'</span>';
441 print
'<span class="opacitymedium">'.$langs->trans($pricesell).
'</span>';
446 if (!empty($conf->stock->enabled)) {
447 print
'<td class="right">'.$value[
'stock'].
'</td>';
451 if ($user->rights->produit->creer || $user->rights->service->creer) {
452 print
'<td class="center"><input type="text" value="'.$nb_of_subproduct.
'" name="TProduct['.$productstatic->id.
'][qty]" size="4" class="right" /></td>';
453 print
'<td class="center"><input type="checkbox" name="TProduct['.$productstatic->id.
'][incdec]" value="1" '.($value[
'incdec'] == 1 ?
'checked' :
'').
' /></td>';
455 print
'<td>'.$nb_of_subproduct.
'</td>';
456 print
'<td>'.($value[
'incdec'] == 1 ?
'x' :
'').
'</td>';
460 print
'<td class="linecolmove tdlineupdown center"></td>';
465 if (empty($conf->global->PRODUCT_SHOW_SUB_SUB_PRODUCTS)) {
466 $hide =
' hideobject';
469 print
'<tr class="oddeven'.$hide.
'" id="sub-'.$value[
'id_parent'].
'" data-ignoreidfordnd=1>';
472 $productstatic->ref = $value[
'ref'];
479 for ($i = 0; $i < $value[
'level']; $i++) {
480 print
' ';
482 print $productstatic->getNomUrl(1,
'composition').
'</td>';
485 print
'<td>'.$productstatic->label.
'</td>';
488 print
'<td> </td>';
489 print
'<td> </td>';
491 print
'<td> </td>';
492 print
'<td> </td>';
495 if (!empty($conf->stock->enabled)) {
500 print
'<td class="center">'.$value[
'nb'].
'</td>';
503 print
'<td> </td>';
506 print
'<td> </td>';
515 print
'<tr class="liste_total">';
521 print
'<td class="liste_total"></td>';
524 print
'<td class="liste_total"></td>';
527 print
'<td class="liste_total right">';
528 print $langs->trans(
"TotalBuyingPriceMinShort");
531 print
'<td class="liste_total right">';
532 if ($atleastonenotdefined) {
533 print $langs->trans(
"Unknown").
' ('.$langs->trans(
"SomeSubProductHaveNoPrices").
')';
535 print ($atleastonenotdefined ?
'' :
price($total,
'',
'', 0, 0, -1, $conf->currency));
539 print
'<td class="liste_total right">';
540 print $langs->trans(
"TotalSellingPriceMinShort");
543 print
'<td class="liste_total right">';
544 if ($atleastonenotdefined) {
545 print $langs->trans(
"Unknown").
' ('.$langs->trans(
"SomeSubProductHaveNoPrices").
')';
547 print ($atleastonenotdefined ?
'' :
price($totalsell,
'',
'', 0, 0, -1, $conf->currency));
551 if (!empty($conf->stock->enabled)) {
552 print
'<td class="liste_total right"> </td>';
557 print
'<td class="center">';
558 if ($user->rights->produit->creer || $user->rights->service->creer) {
559 print
'<input type="submit" class="button button-save" value="'.$langs->trans(
"Save").
'">';
568 if (!empty($conf->stock->enabled)) {
572 print
'<tr class="oddeven">';
573 print
'<td colspan="'.$colspan.
'" class="opacitymedium">'.$langs->trans(
"None").
'</td>';
589 if ((empty($action) || $action ==
'view' || $action ==
'edit' || $action ==
'search' || $action ==
're-edit') && ($user->rights->produit->creer || $user->rights->service->creer)) {
593 if (!empty($conf->categorie->enabled)) {
598 print
'<form action="'.DOL_URL_ROOT.
'/product/composition/card.php?id='.$id.
'" method="POST">';
599 print
'<input type="hidden" name="action" value="search">';
600 print
'<input type="hidden" name="id" value="'.$id.
'">';
601 print
'<div class="inline-block">';
602 print
'<input type="hidden" name="token" value="'.newToken().
'">';
603 print $langs->trans(
"KeywordFilter").
': ';
604 print
'<input type="text" name="key" value="'.$key.
'"> ';
606 if (!empty($conf->categorie->enabled)) {
607 require_once DOL_DOCUMENT_ROOT.
'/categories/class/categorie.class.php';
608 print
'<div class="inline-block">'.$langs->trans(
"CategoryFilter").
': ';
609 print
$form->select_all_categories(Categorie::TYPE_PRODUCT, $parent,
'parent').
' </div>';
612 print
'<div class="inline-block">';
613 print
'<input type="submit" class="button" value="'.$langs->trans(
"Search").
'">';
620 if ($action ==
'search') {
622 print
'<form action="'.DOL_URL_ROOT.
'/product/composition/card.php?id='.$id.
'" method="post">';
623 print
'<input type="hidden" name="token" value="'.newToken().
'">';
624 print
'<input type="hidden" name="action" value="add_prod">';
625 print
'<input type="hidden" name="id" value="'.$id.
'">';
627 print
'<table class="noborder centpercent">';
628 print
'<tr class="liste_titre">';
629 print
'<th class="liste_titre">'.$langs->trans(
"ComposedProduct").
'</td>';
630 print
'<th class="liste_titre">'.$langs->trans(
"Label").
'</td>';
632 print
'<th class="liste_titre right">'.$langs->trans(
"Qty").
'</td>';
633 print
'<th class="center">'.$langs->trans(
'ComposedProductIncDecStock').
'</th>';
636 $num = $db->num_rows(
$resql);
640 print
'<tr><td colspan="4">'.$langs->trans(
"NoMatchFound").
'</td></tr>';
645 while ($i < min($num, $MAX)) {
646 $objp = $db->fetch_object(
$resql);
647 if ($objp->rowid != $id) {
650 $prod_arbo->id = $objp->rowid;
654 $prod_arbo->get_sousproduits_arbo();
656 $prods_arbo = $prod_arbo->get_arbo_each_prod();
657 if (count($prods_arbo) > 0) {
658 foreach ($prods_arbo as $key => $value) {
659 if ($value[1] == $id) {
671 print
'<tr class="oddeven">';
673 $productstatic->id = $objp->rowid;
674 $productstatic->ref = $objp->ref;
675 $productstatic->label = $objp->label;
676 $productstatic->type = $objp->type;
677 $productstatic->entity = $objp->entity;
678 $productstatic->status = $objp->status;
679 $productstatic->status_buy = $objp->status_buy;
680 $productstatic->status_batch = $objp->tobatch;
682 print
'<td>'.$productstatic->getNomUrl(1,
'', 24).
'</td>';
683 $labeltoshow = $objp->label;
684 if (!empty($conf->global->MAIN_MULTILANGS) && !empty($objp->labelm)) {
685 $labeltoshow = $objp->labelm;
688 print
'<td>'.$labeltoshow.
'</td>';
691 if ($object->is_sousproduit($id, $objp->rowid)) {
693 $qty = $object->is_sousproduit_qty;
694 $incdec = $object->is_sousproduit_incdec;
704 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>';
707 print
'<td class="center">';
709 print
'<input type="checkbox" name="prod_incdec_'.$i.
'" value="1" '.($incdec ?
'checked' :
'').
'>';
712 print
'<input type="checkbox" name="prod_incdec_'.$i.
'" value="1" checked>';
722 print
'<tr class="oddeven">';
723 print
'<td><span class="opacitymedium">'.$langs->trans(
"More").
'...</span></td>';
733 print
'<input type="hidden" name="max_prod" value="'.$i.
'">';
736 print
'<div class="center">';
737 print
'<input type="submit" class="button button-save" name="save" value="'.$langs->trans(
"Add").
'/'.$langs->trans(
"Update").
'">';
738 print
'<input type="submit" class="button button-cancel" name="cancel" value="'.$langs->trans(
"Cancel").
'">';