Преглед на файлове

Merge branch 'develop' into new_branch_17_08_2022

Laurent Destailleur преди 2 години
родител
ревизия
b846072e9d
променени са 79 файла, в които са добавени 958 реда и са изтрити 295 реда
  1. 1 3
      ChangeLog
  2. 1 1
      dev/initdemo/initdemopassword.sh
  3. 6 6
      htdocs/accountancy/admin/defaultaccounts.php
  4. 1 1
      htdocs/adherents/document.php
  5. 1 1
      htdocs/adherents/htpasswd.php
  6. 1 1
      htdocs/adherents/list.php
  7. 1 1
      htdocs/adherents/partnership.php
  8. 4 4
      htdocs/admin/barcode.php
  9. 1 1
      htdocs/admin/company.php
  10. 2 2
      htdocs/admin/menus/edit.php
  11. 11 0
      htdocs/admin/system/security.php
  12. 1 1
      htdocs/barcode/codeinit.php
  13. 19 37
      htdocs/bom/bom_card.php
  14. 172 0
      htdocs/bom/class/api_boms.class.php
  15. 236 1
      htdocs/bom/class/bom.class.php
  16. 2 1
      htdocs/comm/action/index.php
  17. 18 31
      htdocs/compta/bank/card.php
  18. 2 1
      htdocs/compta/bank/class/account.class.php
  19. 1 1
      htdocs/compta/cashcontrol/cashcontrol_card.php
  20. 6 6
      htdocs/core/class/html.form.class.php
  21. 18 0
      htdocs/core/lib/bank.lib.php
  22. 2 2
      htdocs/core/lib/functions.lib.php
  23. 3 3
      htdocs/core/modules/expedition/doc/pdf_espadon.modules.php
  24. 3 3
      htdocs/core/modules/expedition/doc/pdf_rouget.modules.php
  25. 10 10
      htdocs/core/modules/modProduct.class.php
  26. 10 10
      htdocs/core/modules/modService.class.php
  27. 3 3
      htdocs/core/modules/reception/doc/pdf_squille.modules.php
  28. 3 3
      htdocs/core/modules/stocktransfer/doc/pdf_eagle.modules.php
  29. 23 1
      htdocs/core/website.inc.php
  30. 17 0
      htdocs/cron/class/cronjob.class.php
  31. 3 3
      htdocs/fourn/class/fournisseur.product.class.php
  32. 3 3
      htdocs/index.php
  33. BIN
      htdocs/install/doctemplates/websites/website_template-style01.jpg
  34. BIN
      htdocs/install/doctemplates/websites/website_template-style01.png
  35. BIN
      htdocs/install/doctemplates/websites/website_template-style01.zip
  36. BIN
      htdocs/install/doctemplates/websites/website_template-style03.zip
  37. BIN
      htdocs/install/doctemplates/websites/website_template-style04.png
  38. BIN
      htdocs/install/doctemplates/websites/website_template-style04.zip
  39. 10 0
      htdocs/install/mysql/data/llx_c_hrm_public_holiday.sql
  40. 11 0
      htdocs/install/mysql/migration/16.0.0-17.0.0.sql
  41. 1 0
      htdocs/install/mysql/tables/llx_cronjob.sql
  42. 73 12
      htdocs/mrp/js/lib_dispatch.js.php
  43. 1 1
      htdocs/mrp/mo_agenda.php
  44. 42 3
      htdocs/mrp/mo_production.php
  45. 1 1
      htdocs/product/card.php
  46. 3 3
      htdocs/product/class/product.class.php
  47. 1 1
      htdocs/product/composition/card.php
  48. 5 5
      htdocs/product/fournisseurs.php
  49. 1 1
      htdocs/product/inventory/inventory.php
  50. 2 2
      htdocs/product/list.php
  51. 10 5
      htdocs/product/stock/movement_list.php
  52. 1 1
      htdocs/product/stock/replenish.php
  53. 1 1
      htdocs/societe/canvas/company/tpl/card_create.tpl.php
  54. 1 1
      htdocs/societe/canvas/company/tpl/card_edit.tpl.php
  55. 1 1
      htdocs/societe/canvas/company/tpl/card_view.tpl.php
  56. 1 1
      htdocs/societe/canvas/individual/tpl/card_create.tpl.php
  57. 1 1
      htdocs/societe/canvas/individual/tpl/card_edit.tpl.php
  58. 1 1
      htdocs/societe/canvas/individual/tpl/card_view.tpl.php
  59. 3 3
      htdocs/societe/card.php
  60. 18 0
      htdocs/societe/class/api_contacts.class.php
  61. 1 0
      htdocs/societe/class/societe.class.php
  62. 3 3
      htdocs/societe/list.php
  63. 2 2
      htdocs/takepos/admin/setup.php
  64. 14 7
      htdocs/takepos/ajax/ajax.php
  65. 11 4
      htdocs/takepos/floors.php
  66. 4 2
      htdocs/takepos/freezone.php
  67. 7 1
      htdocs/takepos/genimg/index.php
  68. 9 0
      htdocs/takepos/genimg/qr.php
  69. 22 20
      htdocs/takepos/index.php
  70. 31 21
      htdocs/takepos/invoice.php
  71. 29 24
      htdocs/takepos/pay.php
  72. 17 11
      htdocs/takepos/phone.php
  73. 3 2
      htdocs/takepos/printbox.php
  74. 1 2
      htdocs/takepos/receipt.php
  75. 1 0
      htdocs/takepos/reduction.php
  76. 13 2
      htdocs/takepos/send.php
  77. 7 0
      htdocs/takepos/smpcb.php
  78. 8 8
      htdocs/takepos/split.php
  79. 1 1
      htdocs/website/index.php

+ 1 - 3
ChangeLog

@@ -233,10 +233,8 @@ FIX: #18704
 FIX: #20444
 FIX: #20448 missing preg_replace for vat rate when adding a free line
 FIX: #20476 migration postgresql 13.0.x to 14.0.x packaging type
-FIX: #20487 missing letter D in constant THIRDPARTIES_DISABLE_RELATED_…
 FIX: #20487 missing letter D in constant THIRDPARTIES_DISABLE_RELATED_OBJECT_TAB
 FIX: #20527 Accountancy - Unbalanced entry proposed when an employee are declared on a social contribution
-FIX: #20527 Accountancy Unbalanced entry proposed when an employee are declared on social contribution
 FIX: #20621 signature online with proposal with n page.
 FIX: #20696
 FIX: #20828
@@ -272,7 +270,7 @@ FIX: fourn/commande/card.php Added "$object" parameter to $formfile->showdocumen
 FIX: french traductions for payment methods
 FIX: hook for dol_format_address
 FIX: Index page for "Sales" give wrong URL link to Intervention
-FIX: issue Dolibarr#21495 for v15
+FIX: issue Dolibarr #21495 for v15
 FIX: label and get_substitutionarray_each_var_object on ODT generation
 FIX: load product stock in inventory lines
 FIX: missing morecss for multiselectarray

+ 1 - 1
dev/initdemo/initdemopassword.sh

@@ -174,7 +174,7 @@ if [ -s "$mydir/initdemopostsql.sql" ]; then
 	echo A file initdemopostsql.sql was found, we execute it.
 	mysql -P$port $base < "$mydir/initdemopostsql.sql"
 else
-	echo No file initdemopostsql.sql found, we extra sql action done.
+	echo No file initdemopostsql.sql found, so no extra sql action done.
 fi 
 
 

+ 6 - 6
htdocs/accountancy/admin/defaultaccounts.php

@@ -81,22 +81,22 @@ $list_account[] = '---Others---';
 $list_account[] = 'ACCOUNTING_VAT_BUY_ACCOUNT';
 $list_account[] = 'ACCOUNTING_VAT_SOLD_ACCOUNT';
 $list_account[] = 'ACCOUNTING_VAT_PAY_ACCOUNT';
-if (!empty($conf->banque->enabled)) {
+if (isModEnabled('banque')) {
 	$list_account[] = 'ACCOUNTING_ACCOUNT_TRANSFER_CASH';
 }
-if (!empty($conf->don->enabled)) {
+if (isModEnabled('don')) {
 	$list_account[] = 'DONATION_ACCOUNTINGACCOUNT';
 }
-if (!empty($conf->adherent->enabled)) {
+if (isModEnabled('adherent')) {
 	$list_account[] = 'ADHERENT_SUBSCRIPTION_ACCOUNTINGACCOUNT';
 }
-if (!empty($conf->loan->enabled)) {
+if (isModEnabled('loan')) {
 	$list_account[] = 'LOAN_ACCOUNTING_ACCOUNT_CAPITAL';
 	$list_account[] = 'LOAN_ACCOUNTING_ACCOUNT_INTEREST';
 	$list_account[] = 'LOAN_ACCOUNTING_ACCOUNT_INSURANCE';
 }
 $list_account[] = 'ACCOUNTING_ACCOUNT_SUSPENSE';
-if (!empty($conf->societe->enabled)) {
+if (isModEnabled('societe')) {
 	$list_account[] = '---Deposits---';
 }
 
@@ -266,7 +266,7 @@ print $formaccounting->select_account(getDolGlobalString('ACCOUNTING_ACCOUNT_CUS
 print '</td>';
 print '</tr>';
 
-if (!empty($conf->societe->enabled) && getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER_DEPOSIT') && getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER_DEPOSIT') != '-1') {
+if (isModEnabled('societe') && getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER_DEPOSIT') && getDolGlobalString('ACCOUNTING_ACCOUNT_CUSTOMER_DEPOSIT') != '-1') {
 	print '<tr class="oddeven">';
 	print '<td>' . img_picto('', 'bill', 'class="pictofixedwidth"') . $langs->trans("UseAuxiliaryAccountOnCustomerDeposit") . '</td>';
 	if (getDolGlobalInt('ACCOUNTING_ACCOUNT_CUSTOMER_USE_AUXILIARY_ON_DEPOSIT')) {

+ 1 - 1
htdocs/adherents/document.php

@@ -128,7 +128,7 @@ if ($id > 0) {
 			$totalsize += $file['size'];
 		}
 
-		if (!empty($conf->notification->enabled)) {
+		if (isModEnabled('notification')) {
 			$langs->load("mails");
 		}
 

+ 1 - 1
htdocs/adherents/htpasswd.php

@@ -33,7 +33,7 @@ $sortfield = GETPOST('sortfield', 'alphanohtml');
 $sortorder = GETPOST('sortorder', 'aZ09');
 
 // Security check
-if (empty($conf->adherent->enabled)) {
+if (!isModEnabled('adherent')) {
 	accessforbidden();
 }
 if (empty($user->rights->adherent->export)) {

+ 1 - 1
htdocs/adherents/list.php

@@ -659,7 +659,7 @@ if ($sall) {
 
 // Filter on categories
 $moreforfilter = '';
-if (!empty($conf->categorie->enabled) && $user->rights->categorie->lire) {
+if (isModEnabled('categorie') && $user->rights->categorie->lire) {
 	require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
 	$moreforfilter .= '<div class="divsearchfield">';
 	$moreforfilter .= img_picto($langs->trans('Categories'), 'category', 'class="pictofixedlength"').$formother->select_categories(Categorie::TYPE_MEMBER, $search_categ, 'search_categ', 1);

+ 1 - 1
htdocs/adherents/partnership.php

@@ -88,7 +88,7 @@ $upload_dir 			= $conf->partnership->multidir_output[isset($object->entity) ? $o
 if (getDolGlobalString('PARTNERSHIP_IS_MANAGED_FOR') != 'member') {
 	accessforbidden('Partnership module is not activated for members');
 }
-if (empty($conf->partnership->enabled)) {
+if (!isModEnabled('partnership')) {
 	accessforbidden();
 }
 if (empty($permissiontoread)) {

+ 4 - 4
htdocs/admin/barcode.php

@@ -190,7 +190,7 @@ foreach ($dirbarcode as $reldir) {
 
 
 // Select barcode numbering module
-if ($conf->product->enabled) {
+if (isModEnabled('product')) {
 	print load_fiche_titre($langs->trans("BarCodeNumberManager")." (".$langs->trans("Product").")", '', '');
 
 	print '<div class="div-table-responsive-no-min">';
@@ -252,7 +252,7 @@ if ($conf->product->enabled) {
 }
 
 // Select barcode numbering module
-if ($conf->societe->enabled) {
+if (isModEnabled('societe')) {
 	print load_fiche_titre($langs->trans("BarCodeNumberManager")." (".$langs->trans("ThirdParty").")", '', '');
 
 	print '<div class="div-table-responsive-no-min">';
@@ -450,7 +450,7 @@ if (!isset($_SERVER['WINDIR'])) {
 }
 
 // Module products
-if (!empty($conf->product->enabled)) {
+if (isModEnabled('product')) {
 	print '<tr class="oddeven">';
 	print '<td>'.$langs->trans("SetDefaultBarcodeTypeProducts").'</td>';
 	print '<td width="60" class="right">';
@@ -461,7 +461,7 @@ if (!empty($conf->product->enabled)) {
 }
 
 // Module thirdparty
-if (!empty($conf->societe->enabled)) {
+if (isModEnabled('societe')) {
 	print '<tr class="oddeven">';
 	print '<td>'.$langs->trans("SetDefaultBarcodeTypeThirdParties").'</td>';
 	print '<td width="60" class="right">';

+ 1 - 1
htdocs/admin/company.php

@@ -466,7 +466,7 @@ print '<input class="maxwidth300 widthcentpercentminusx" name="web" id="web" val
 print '</td></tr>'."\n";
 
 // Barcode
-if (!empty($conf->barcode->enabled)) {
+if (isModEnabled('barcode')) {
 	print '<tr class="oddeven"><td>';
 	print '<label for="barcode">'.$langs->trans("Gencod").'</label></td><td>';
 	print '<span class="fa fa-barcode pictofixedwidth"></span>';

+ 2 - 2
htdocs/admin/menus/edit.php

@@ -172,12 +172,12 @@ if ($action == 'add') {
 		$action = 'create';
 		$error++;
 	}
-	if (!$error && GETPOST('menuId') && GETPOST('type') == 'top') {
+	if (!$error && GETPOST('menuId', 'alphanohtml', 3) && GETPOST('type') == 'top') {
 		setEventMessages($langs->trans("ErrorTopMenuMustHaveAParentWithId0"), null, 'errors');
 		$action = 'create';
 		$error++;
 	}
-	if (!$error && !GETPOST('menuId') && GETPOST('type') == 'left') {
+	if (!$error && !GETPOST('menuId', 'alphanohtml', 3) && GETPOST('type') == 'left') {
 		setEventMessages($langs->trans("ErrorLeftMenuMustHaveAParentId"), null, 'errors');
 		$action = 'create';
 		$error++;

+ 11 - 0
htdocs/admin/system/security.php

@@ -491,6 +491,17 @@ print '<br>';
 print '<strong>WEBSITE_MAIN_SECURITY_FORCECSP</strong> = '.(empty($conf->global->WEBSITE_MAIN_SECURITY_FORCECSP) ? '<span class="opacitymedium">'.$langs->trans("Undefined").'</span>' : $conf->global->WEBSITE_MAIN_SECURITY_FORCECSP).' &nbsp; <span class="opacitymedium">('.$langs->trans("Example").": \"default-src 'self'; style-src: https://cdnjs.cloudflare.com https://fonts.googleapis.com; script-src: https://cdn.transifex.com https://www.googletagmanager.com; object-src https://youtube.com; frame-src https://youtube.com; img-src: *;\")</span><br>";
 print '<br>';
 
+print '<strong>MAIN_SECURITY_FORCERP</strong> = '.(empty($conf->global->MAIN_SECURITY_FORCERP) ? '<span class="opacitymedium">'.$langs->trans("Undefined").'</span>' : $conf->global->MAIN_SECURITY_FORCERP).' &nbsp; <span class="opacitymedium">('.$langs->trans("Recommended").': '.$langs->trans("Undefined").' '.$langs->trans("or")." \"same-origin\")</span><br>";
+print '<br>';
+
+print '<strong>WEBSITE_MAIN_SECURITY_FORCERP</strong> = '.(empty($conf->global->WEBSITE_MAIN_SECURITY_FORCERP) ? '<span class="opacitymedium">'.$langs->trans("Undefined").'</span>' : $conf->global->WEBSITE_MAIN_SECURITY_FORCERP).' &nbsp; <span class="opacitymedium">('.$langs->trans("Recommended").': '.$langs->trans("Undefined").' '.$langs->trans("or")." \"strict-origin-when-cross-origin\")</span><br>";
+print '<br>';
+
+print '<strong>WEBSITE_MAIN_SECURITY_FORCESTS</strong> = '.(empty($conf->global->WEBSITE_MAIN_SECURITY_FORCESTS) ? '<span class="opacitymedium">'.$langs->trans("Undefined").'</span>' : $conf->global->WEBSITE_MAIN_SECURITY_FORCESTS).' &nbsp; <span class="opacitymedium">('.$langs->trans("Example").": \"max-age=31536000; includeSubDomains\")</span><br>";
+print '<br>';
+
+print '<strong>WEBSITE_MAIN_SECURITY_FORCEPP</strong> = '.(empty($conf->global->WEBSITE_MAIN_SECURITY_FORCEPP) ? '<span class="opacitymedium">'.$langs->trans("Undefined").'</span>' : $conf->global->WEBSITE_MAIN_SECURITY_FORCEPP).' &nbsp; <span class="opacitymedium">('.$langs->trans("Example").": \"camera: 'none'; microphone: 'none';\")</span><br>";
+print '<br>';
 
 print '<br>';
 

+ 1 - 1
htdocs/barcode/codeinit.php

@@ -355,7 +355,7 @@ if (isModEnabled('societe')) {
 
 
 // For products
-if ($conf->product->enabled || $conf->product->service) {
+if (isModEnabled('product') || isModEnabled('service')) {
 	print '<form action="'.$_SERVER["PHP_SELF"].'" method="POST">';
 	print '<input type="hidden" name="mode" value="label">';
 	print '<input type="hidden" name="action" value="initbarcodeproducts">';

+ 19 - 37
htdocs/bom/bom_card.php

@@ -181,24 +181,10 @@ if (empty($reshook)) {
 		}
 
 		if (!$error) {
-			$bomline = new BOMLine($db);
-			$bomline->fk_bom = $id;
-			$bomline->fk_product = $idprod;
-			$bomline->fk_bom_child = $bom_child_id;
-			$bomline->qty = $qty;
-			$bomline->qty_frozen = (int) $qty_frozen;
-			$bomline->disable_stock_change = (int) $disable_stock_change;
-			$bomline->efficiency = $efficiency;
-
-			// Rang to use
-			$rangmax = $object->line_max(0);
-			$ranktouse = $rangmax + 1;
-
-			$bomline->position = ($ranktouse + 1);
+			$result = $object->addLine($idprod, $qty, $qty_frozen, $disable_stock_change, $efficiency, -1, $bom_child_id, null);
 
-			$result = $bomline->create($user);
 			if ($result <= 0) {
-				setEventMessages($bomline->error, $bomline->errors, 'errors');
+				setEventMessages($object->error, $object->errors, 'errors');
 				$action = '';
 			} else {
 				unset($_POST['idprod']);
@@ -207,13 +193,11 @@ if (empty($reshook)) {
 				unset($_POST['disable_stock_change']);
 
 				$object->fetchLines();
-
-				$object->calculateCosts();
 			}
 		}
 	}
 
-	// Add line
+	// Update line
 	if ($action == 'updateline' && $user->rights->bom->write) {
 		$langs->load('errors');
 		$error = 0;
@@ -229,26 +213,24 @@ if (empty($reshook)) {
 			$error++;
 		}
 
-		$bomline = new BOMLine($db);
-		$bomline->fetch($lineid);
-		$bomline->qty = $qty;
-		$bomline->qty_frozen = (int) $qty_frozen;
-		$bomline->disable_stock_change = (int) $disable_stock_change;
-		$bomline->efficiency = $efficiency;
-
-		$result = $bomline->update($user);
-		if ($result <= 0) {
-			setEventMessages($bomline->error, $bomline->errors, 'errors');
-			$action = '';
-		} else {
-			unset($_POST['idprod']);
-			unset($_POST['qty']);
-			unset($_POST['qty_frozen']);
-			unset($_POST['disable_stock_change']);
+		if (!$error) {
 
-			$object->fetchLines();
+			$bomline = new BOMLine($db);
+			$bomline->fetch($lineid);
 
-			$object->calculateCosts();
+			$result = $object->updateLine($lineid, $qty, (int)$qty_frozen, (int)$disable_stock_change, $efficiency, $bomline->position, $bomline->import_key);
+
+			if ($result <= 0) {
+				setEventMessages($object->error, $object->errors, 'errors');
+				$action = '';
+			} else {
+				unset($_POST['idprod']);
+				unset($_POST['qty']);
+				unset($_POST['qty_frozen']);
+				unset($_POST['disable_stock_change']);
+
+				$object->fetchLines();
+			}
 		}
 	}
 }

+ 172 - 0
htdocs/bom/class/api_boms.class.php

@@ -2,6 +2,7 @@
 /* Copyright (C) 2015   Jean-François Ferry     <jfefe@aternatik.fr>
  * Copyright (C) 2019 Maxime Kohlhaas <maxime@atm-consulting.fr>
  * Copyright (C) 2020     	Frédéric France		<frederic.france@netlogic.fr>
+ * Copyright (C) 2022     	Christian Humpel		<christian.humpel@live.com>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -279,6 +280,177 @@ class Boms extends DolibarrApi
 		);
 	}
 
+	/**
+	 * Get lines of an BOM
+	 *
+	 * @param int   $id             Id of BOM
+	 *
+	 * @url	GET {id}/lines
+	 *
+	 * @return array
+	 */
+	public function getLines($id)
+	{
+		if (!DolibarrApiAccess::$user->rights->bom->read) {
+			throw new RestException(401);
+		}
+
+		$result = $this->bom->fetch($id);
+		if (!$result) {
+			throw new RestException(404, 'BOM not found');
+		}
+
+		if (!DolibarrApi::_checkAccessToResource('bom_bom', $this->bom->id)) {
+			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+		}
+		$this->bom->getLinesArray();
+		$result = array();
+		foreach ($this->bom->lines as $line) {
+			array_push($result, $this->_cleanObjectDatas($line));
+		}
+		return $result;
+	}
+
+	/**
+	 * Add a line to given BOM
+	 *
+	 * @param int   $id             Id of BOM to update
+	 * @param array $request_data   BOMLine data
+	 *
+	 * @url	POST {id}/lines
+	 *
+	 * @return int
+	 */
+	public function postLine($id, $request_data = null)
+	{
+		if (!DolibarrApiAccess::$user->rights->bom->write) {
+			throw new RestException(401);
+		}
+
+		$result = $this->bom->fetch($id);
+		if (!$result) {
+			throw new RestException(404, 'BOM not found');
+		}
+
+		if (!DolibarrApi::_checkAccessToResource('bom_bom', $this->bom->id)) {
+			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+		}
+
+		$request_data = (object) $request_data;
+
+		$updateRes = $this->bom->addLine(
+			$request_data->fk_product,
+			$request_data->qty,
+			$request_data->qty_frozen,
+			$request_data->disable_stock_change,
+			$request_data->efficiency,
+			$request_data->position,
+			$request_data->fk_bom_child,
+			$request_data->import_key
+		);
+
+		if ($updateRes > 0) {
+			return $updateRes;
+		} else {
+			throw new RestException(400, $this->bom->error);
+		}
+	}
+
+	/**
+	 * Update a line to given BOM
+	 *
+	 * @param int   $id             Id of BOM to update
+	 * @param int   $lineid         Id of line to update
+	 * @param array $request_data   BOMLine data
+	 *
+	 * @url	PUT {id}/lines/{lineid}
+	 *
+	 * @return array|bool
+	 */
+	public function putLine($id, $lineid, $request_data = null)
+	{
+		if (!DolibarrApiAccess::$user->rights->bom->write) {
+			throw new RestException(401);
+		}
+
+		$result = $this->bom->fetch($id);
+		if (!$result) {
+			throw new RestException(404, 'BOM not found');
+		}
+
+		if (!DolibarrApi::_checkAccessToResource('bom_bom', $this->bom->id)) {
+			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+		}
+
+		$request_data = (object) $request_data;
+
+		$updateRes = $this->bom->updateLine(
+			$lineid,
+			$request_data->qty,
+			$request_data->qty_frozen,
+			$request_data->disable_stock_change,
+			$request_data->efficiency,
+			$request_data->position,
+			$request_data->import_key
+		);
+
+		if ($updateRes > 0) {
+			$result = $this->get($id);
+			unset($result->line);
+			return $this->_cleanObjectDatas($result);
+		}
+		return false;
+	}
+
+	/**
+	 * Delete a line to given BOM
+	 *
+	 *
+	 * @param int   $id             Id of BOM to update
+	 * @param int   $lineid         Id of line to delete
+	 *
+	 * @url	DELETE {id}/lines/{lineid}
+	 *
+	 * @return int
+	 *
+	 * @throws RestException 401
+	 * @throws RestException 404
+	 * @throws RestException 500
+	 */
+	public function deleteLine($id, $lineid)
+	{
+		if (!DolibarrApiAccess::$user->rights->bom->write) {
+			throw new RestException(401);
+		}
+
+		$result = $this->bom->fetch($id);
+		if (!$result) {
+			throw new RestException(404, 'BOM not found');
+		}
+
+		if (!DolibarrApi::_checkAccessToResource('bom_bom', $this->bom->id)) {
+			throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
+		}
+
+		//Check the rowid is a line of current bom object
+		$lineIdIsFromObject = false;
+		foreach ($this->bom->lines as $bl) {
+			if ($bl->id == $lineid) {
+				$lineIdIsFromObject = true;
+				break;
+			}
+		}
+		if (!$lineIdIsFromObject) {
+			throw new RestException(500, 'Line to delete (rowid: '.$lineid.') is not a line of BOM (id: '.$this->bom->id.')');
+		}
+
+		$updateRes = $this->bom->deleteline(DolibarrApiAccess::$user, $lineid);
+		if ($updateRes > 0) {
+			return $this->get($id);
+		} else {
+			throw new RestException(405, $this->bom->error);
+		}
+	}
 
 	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
 	/**

+ 236 - 1
htdocs/bom/class/bom.class.php

@@ -502,6 +502,210 @@ class BOM extends CommonObject
 		//return $this->deleteCommon($user, $notrigger, 1);
 	}
 
+	/**
+	 * Add an BOM line into database (linked to BOM)
+	 *
+	 * @param	int		$fk_product				Id of product
+	 * @param	float	$qty					Quantity
+	 * @param	int		$qty_frozen				Frozen quantity
+	 * @param 	int		$disable_stock_change	Disable stock change on using in MO
+	 * @param	float	$efficiency				Efficiency in MO
+	 * @param	int		$position				Position of BOM-Line in BOM-Lines
+	 * @param	int		$fk_bom_child			Id of BOM Child
+	 * @param	string	$import_key				Import Key
+	 * @return	int								<0 if KO, Id of created object if OK
+	 */
+	public function addLine($fk_product, $qty, $qty_frozen = 0, $disable_stock_change = 0, $efficiency = 1.0, $position = -1, $fk_bom_child = null, $import_key = null)
+	{
+
+		global $mysoc, $conf, $langs, $user;
+
+		$logtext = "::addLine bomid=$this->id, qty=$qty, fk_product=$fk_product, qty_frozen=$qty_frozen, disable_stock_change=$disable_stock_change, efficiency=$efficiency";
+		$logtext .= ", fk_bom_child=$fk_bom_child, import_key=$import_key";
+		dol_syslog(get_class($this).$logtext, LOG_DEBUG);
+
+		if ($this->statut == self::STATUS_DRAFT) {
+			include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
+
+			// Clean parameters
+			if (empty($qty)) {
+				$qty = 0;
+			}
+			if (empty($qty_frozen)) {
+				$qty_frozen = 0;
+			}
+			if (empty($disable_stock_change)) {
+				$disable_stock_change = 0;
+			}
+			if (empty($efficiency)) {
+				$efficiency = 1.0;
+			}
+			if (empty($fk_bom_child)) {
+				$fk_bom_child = null;
+			}
+			if (empty($import_key)) {
+				$import_key = null;
+			}
+			if (empty($position)) {
+				$position = -1;
+			}
+
+			$qty = price2num($qty);
+			$efficiency = price2num($efficiency);
+			$position = price2num($position);
+
+			$this->db->begin();
+
+			// Rank to use
+			$rangMax = $this->line_max();
+			$rankToUse = $position;
+			if ($rankToUse <= 0 or $rankToUse > $rangMax) { // New line after existing lines
+				$rankToUse = $rangMax + 1;
+			} else { // New line between the existing lines
+				foreach ($this->lines as $bl) {
+					if ($bl->position >= $rankToUse) {
+						$bl->position++;
+						$bl->update($user);
+					}
+				}
+			}
+
+			// Insert line
+			$this->line = new BOMLine($this->db);
+
+			$this->line->context = $this->context;
+
+			$this->line->fk_bom = $this->id;
+			$this->line->fk_product = $fk_product;
+			$this->line->qty = $qty;
+			$this->line->qty_frozen = $qty_frozen;
+			$this->line->disable_stock_change = $disable_stock_change;
+			$this->line->efficiency = $efficiency;
+			$this->line->fk_bom_child = $fk_bom_child;
+			$this->line->import_key = $import_key;
+			$this->line->position = $rankToUse;
+
+			$result = $this->line->create($user);
+
+			if ($result > 0) {
+				$this->calculateCosts();
+				$this->db->commit();
+				return $result;
+			} else {
+				$this->error = $this->line->error;
+				dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
+				$this->db->rollback();
+				return -2;
+			}
+		} else {
+			dol_syslog(get_class($this)."::addLine status of BOM must be Draft to allow use of ->addLine()", LOG_ERR);
+			return -3;
+		}
+	}
+
+	/**
+	 * Update an BOM line into database
+	 *
+	 * @param 	int		$rowid					Id of line to update
+	 * @param	float	$qty					Quantity
+	 * @param	int		$qty_frozen				Frozen quantity
+	 * @param 	int		$disable_stock_change	Disable stock change on using in MO
+	 * @param	float	$efficiency				Efficiency in MO
+	 * @param	int		$position				Position of BOM-Line in BOM-Lines
+	 * @param	int		$fk_bom_child			Id of BOM Child
+	 * @param	string	$import_key				Import Key
+	 * @return	int								<0 if KO, Id of updated BOM-Line if OK
+	 */
+	public function updateLine($rowid, $qty, $qty_frozen = 0, $disable_stock_change = 0, $efficiency = 1.0, $position = -1, $import_key = null)
+	{
+
+		global $mysoc, $conf, $langs, $user;
+
+		$logtext = "::updateLine bomid=$this->id, qty=$qty, qty_frozen=$qty_frozen, disable_stock_change=$disable_stock_change, efficiency=$efficiency";
+		$logtext .= ", import_key=$import_key";
+		dol_syslog(get_class($this).$logtext, LOG_DEBUG);
+
+		if ($this->statut == self::STATUS_DRAFT) {
+			include_once DOL_DOCUMENT_ROOT.'/core/lib/price.lib.php';
+
+			// Clean parameters
+			if (empty($qty)) {
+				$qty = 0;
+			}
+			if (empty($qty_frozen)) {
+				$qty_frozen = 0;
+			}
+			if (empty($disable_stock_change)) {
+				$disable_stock_change = 0;
+			}
+			if (empty($efficiency)) {
+				$efficiency = 1.0;
+			}
+			if (empty($import_key)) {
+				$import_key = null;
+			}
+			if (empty($position)) {
+				$position = -1;
+			}
+
+			$qty = price2num($qty);
+			$efficiency = price2num($efficiency);
+			$position = price2num($position);
+
+			$this->db->begin();
+
+			//Fetch current line from the database and then clone the object and set it in $oldline property
+			$line = new BOMLine($this->db);
+			$line->fetch($rowid);
+			$line->fetch_optionals();
+
+			$staticLine = clone $line;
+			$line->oldcopy = $staticLine;
+			$this->line = $line;
+			$this->line->context = $this->context;
+
+			// Rank to use
+			$rankToUse = (int) $position;
+			if ($rankToUse != $line->oldcopy->position) { // check if position have a new value
+				foreach ($this->lines as $bl) {
+					if ($bl->position >= $rankToUse AND $bl->position < ($line->oldcopy->position + 1)) { // move rank up
+						$bl->position++;
+						$bl->update($user);
+					}
+					if ($bl->position <= $rankToUse AND $bl->position > ($line->oldcopy->position)) { // move rank down
+						$bl->position--;
+						$bl->update($user);
+					}
+				}
+			}
+
+
+			$this->line->fk_bom = $this->id;
+			$this->line->qty = $qty;
+			$this->line->qty_frozen = $qty_frozen;
+			$this->line->disable_stock_change = $disable_stock_change;
+			$this->line->efficiency = $efficiency;
+			$this->line->import_key = $import_key;
+			$this->line->position = $rankToUse;
+
+			$result = $this->line->update($user);
+
+			if ($result > 0) {
+				$this->calculateCosts();
+				$this->db->commit();
+				return $result;
+			} else {
+				$this->error = $this->line->error;
+				dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
+				$this->db->rollback();
+				return -2;
+			}
+		} else {
+			dol_syslog(get_class($this)."::addLine status of BOM must be Draft to allow use of ->addLine()", LOG_ERR);
+			return -3;
+		}
+	}
+
 	/**
 	 *  Delete a line of object in database
 	 *
@@ -517,7 +721,38 @@ class BOM extends CommonObject
 			return -2;
 		}
 
-		return $this->deleteLineCommon($user, $idline, $notrigger);
+		$this->db->begin();
+
+		//Fetch current line from the database and then clone the object and set it in $oldline property
+		$line = new BOMLine($this->db);
+		$line->fetch($idline);
+		$line->fetch_optionals();
+
+		$staticLine = clone $line;
+		$line->oldcopy = $staticLine;
+		$this->line = $line;
+		$this->line->context = $this->context;
+
+		$result = $this->line->delete($user, $notrigger);
+
+		//Positions (rank) reordering
+		foreach ($this->lines as $bl) {
+			if ($bl->position > ($line->oldcopy->position)) { // move rank down
+				$bl->position--;
+				$bl->update($user);
+			}
+		}
+
+		if ($result > 0) {
+			$this->calculateCosts();
+			$this->db->commit();
+			return $result;
+		} else {
+			$this->error = $this->line->error;
+			dol_syslog(get_class($this)."::addLine error=".$this->error, LOG_ERR);
+			$this->db->rollback();
+			return -2;
+		}
 	}
 
 	/**

+ 2 - 1
htdocs/comm/action/index.php

@@ -757,9 +757,10 @@ if ($type) {
 	$sql .= " AND ca.id = ".((int) $type);
 }
 if ($status == '0') {
+	// To do (not started)
 	$sql .= " AND a.percent = 0";
 }
-if ($status == '-1' || $status == 'na') {
+if ($status == 'na') {
 	// Not applicable
 	$sql .= " AND a.percent = -1";
 }

+ 18 - 31
htdocs/compta/bank/card.php

@@ -68,6 +68,15 @@ $hookmanager->initHooks(array('bankcard', 'globalcard'));
 $id = GETPOST("id", 'int') ? GETPOST("id", 'int') : GETPOST('ref', 'alpha');
 $fieldid = GETPOST("id", 'int') ? 'rowid' : 'ref';
 
+if (GETPOST("id", 'int') || GETPOST("ref")) {
+	if (GETPOST("id", 'int')) {
+		$object->fetch(GETPOST("id", 'int'));
+	}
+	if (GETPOST("ref")) {
+		$object->fetch(0, GETPOST("ref"));
+	}
+}
+
 $result = restrictedArea($user, 'banque', $id, 'bank_account&bank_account', '', '', $fieldid);
 
 
@@ -319,6 +328,7 @@ if (empty($reshook)) {
 	}
 }
 
+
 /*
  * View
  */
@@ -333,15 +343,15 @@ if (!empty($conf->accounting->enabled)) {
 $countrynotdefined = $langs->trans("ErrorSetACountryFirst").' ('.$langs->trans("SeeAbove").')';
 
 $help_url = 'EN:Module_Banks_and_Cash|FR:Module_Banques_et_Caisses|ES:Módulo_Bancos_y_Cajas|DE:Modul_Banken_und_Barbestände';
-
-// Creation
-
 if ($action == 'create') {
-	$object = new Account($db);
-
 	$title = $langs->trans("NewFinancialAccount");
-	llxHeader("", $title, $help_url);
+} elseif (!empty($object->ref)) {
+	$title = $object->ref." - ".$langs->trans("Card");
+}
+llxHeader("", $title, $help_url);
 
+// Creation
+if ($action == 'create') {
 	print load_fiche_titre($langs->trans("NewFinancialAccount"), '', 'bank_account');
 
 	if ($conf->use_javascript_ajax) {
@@ -597,25 +607,8 @@ if ($action == 'create') {
 
 	print '</form>';
 } else {
-	/* ************************************************************************** */
-	/*                                                                            */
-	/* Visu et edition                                                            */
-	/*                                                                            */
-	/* ************************************************************************** */
-
+	// View and edit mode
 	if ((GETPOST("id", 'int') || GETPOST("ref")) && $action != 'edit') {
-		$object = new Account($db);
-		if (GETPOST("id", 'int')) {
-			$object->fetch(GETPOST("id", 'int'));
-		}
-		if (GETPOST("ref")) {
-			$object->fetch(0, GETPOST("ref"));
-			$_GET["id"] = $object->id;
-		}
-
-		$title = $object->ref." - ".$langs->trans("Card");
-		llxHeader("", $title, $help_url);
-
 		// Show tabs
 		$head = bank_prepare_head($object);
 		print dol_get_fiche_head($head, 'bankname', $langs->trans("FinancialAccount"), -1, 'account');
@@ -758,7 +751,7 @@ if ($action == 'create') {
 			}
 
 			print '<tr><td>'.$langs->trans($ibankey).'</td>';
-			print '<td>'.$object->iban.'&nbsp;';
+			print '<td>'.getIbanHumanReadable($object).'&nbsp;';
 			if (!empty($object->iban)) {
 				if (!checkIbanForAccount($object)) {
 					print img_picto($langs->trans("IbanNotValid"), 'warning');
@@ -843,12 +836,6 @@ if ($action == 'create') {
 	/* ************************************************************************** */
 
 	if (GETPOST('id', 'int') && $action == 'edit' && $user->rights->banque->configurer) {
-		$object = new Account($db);
-		$object->fetch(GETPOST('id', 'int'));
-
-		$title = $object->ref." - ".$langs->trans("Card");
-		llxHeader("", $title, $help_url);
-
 		print load_fiche_titre($langs->trans("EditFinancialAccount"), '', 'bank_account');
 
 		if ($conf->use_javascript_ajax) {

+ 2 - 1
htdocs/compta/bank/class/account.class.php

@@ -1381,6 +1381,7 @@ class Account extends CommonObject
 	public function getNomUrl($withpicto = 0, $mode = '', $option = '', $save_lastsearch_value = -1, $notooltip = 0)
 	{
 		global $conf, $langs, $user;
+		include_once DOL_DOCUMENT_ROOT.'/core/lib/bank.lib.php';
 
 		$result = '';
 		$label = img_picto('', $this->picto).' <u class="paddingrightnow">'.$langs->trans("BankAccount").'</u>';
@@ -1389,7 +1390,7 @@ class Account extends CommonObject
 		}
 		$label .= '<br><b>'.$langs->trans('Label').':</b> '.$this->label;
 		$label .= '<br><b>'.$langs->trans('AccountNumber').':</b> '.$this->number;
-		$label .= '<br><b>'.$langs->trans('IBAN').':</b> '.$this->iban;
+		$label .= '<br><b>'.$langs->trans('IBAN').':</b> '.getIbanHumanReadable($this);
 		$label .= '<br><b>'.$langs->trans('BIC').':</b> '.$this->bic;
 		$label .= '<br><b>'.$langs->trans("AccountCurrency").':</b> '.$this->currency_code;
 

+ 1 - 1
htdocs/compta/cashcontrol/cashcontrol_card.php

@@ -128,7 +128,7 @@ if (GETPOST('cancel', 'alpha')) {
 if ($action == "reopen") {
 	$result = $object->setStatut($object::STATUS_DRAFT, null, '', 'CASHFENCE_REOPEN');
 	if ($result < 0) {
-		setEventMessages($object->error, $object->error, 'errors');
+		setEventMessages($object->error, $object->errors, 'errors');
 	}
 
 	$action = 'view';

+ 6 - 6
htdocs/core/class/html.form.class.php

@@ -1408,7 +1408,7 @@ class Form
 			if (count($scrit) > 1) {
 				$sql .= ")";
 			}
-			if (!empty($conf->barcode->enabled)) {
+			if (isModEnabled('barcode')) {
 				$sql .= " OR s.barcode LIKE '".$this->db->escape($prefix.$filterkey)."%'";
 			}
 			$sql .= " OR s.code_client LIKE '".$this->db->escape($prefix.$filterkey)."%' OR s.code_fournisseur LIKE '".$this->db->escape($prefix.$filterkey)."%'";
@@ -2691,7 +2691,7 @@ class Form
 			if (count($scrit) > 1) {
 				$sql .= ")";
 			}
-			if (!empty($conf->barcode->enabled)) {
+			if (isModEnabled('barcode')) {
 				$sql .= " OR p.barcode LIKE '".$this->db->escape($prefix.$filterkey)."%'";
 			}
 			$sql .= ')';
@@ -3254,7 +3254,7 @@ class Form
 		if (!empty($conf->global->PRODUCT_USE_UNITS)) {
 			$sql .= ", u.label as unit_long, u.short_label as unit_short, p.weight, p.weight_units, p.length, p.length_units, p.width, p.width_units, p.height, p.height_units, p.surface, p.surface_units, p.volume, p.volume_units";
 		}
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			$sql .= ", pfp.barcode";
 		}
 		$sql .= " FROM ".$this->db->prefix()."product as p";
@@ -3305,7 +3305,7 @@ class Form
 			if (count($scrit) > 1) {
 				$sql .= ")";
 			}
-			if (!empty($conf->barcode->enabled)) {
+			if (isModEnabled('barcode')) {
 				$sql .= " OR p.barcode LIKE '".$this->db->escape($prefix.$filterkey)."%'";
 				$sql .= " OR pfp.barcode LIKE '".$this->db->escape($prefix.$filterkey)."%'";
 			}
@@ -3402,7 +3402,7 @@ class Form
 				if (!empty($objp->idprodfournprice) && ($objp->ref != $objp->ref_fourn)) {
 					$optlabel .= ' <span class="opacitymedium">('.$objp->ref_fourn.')</span>';
 				}
-				if (!empty($conf->barcode->enabled) && !empty($objp->barcode)) {
+				if (isModEnabled('barcode') && !empty($objp->barcode)) {
 					$optlabel .= ' ('.$outbarcode.')';
 				}
 				$optlabel .= ' - '.dol_trunc($label, $maxlengtharticle);
@@ -3411,7 +3411,7 @@ class Form
 				if (!empty($objp->idprodfournprice) && ($objp->ref != $objp->ref_fourn)) {
 					$outvallabel .= ' ('.$objRefFourn.')';
 				}
-				if (!empty($conf->barcode->enabled) && !empty($objp->barcode)) {
+				if (isModEnabled('barcode') && !empty($objp->barcode)) {
 					$outvallabel .= ' ('.$outbarcode.')';
 				}
 				$outvallabel .= ' - '.dol_trunc($label, $maxlengtharticle);

+ 18 - 0
htdocs/core/lib/bank.lib.php

@@ -290,6 +290,24 @@ function checkIbanForAccount(Account $account)
 	}
 }
 
+/**
+ * Returns the iban human readable
+ *
+ * @param Account $account Account object
+ * @return string
+ */
+function getIbanHumanReadable(Account $account)
+{
+	if ($account->getCountryCode() == 'FR') {
+		require_once DOL_DOCUMENT_ROOT.'/includes/php-iban/oophp-iban.php';
+		$ibantoprint = preg_replace('/[^a-zA-Z0-9]/', '', $account->iban);
+		$iban = new PHP_IBAN\IBAN($ibantoprint);
+		return $iban->HumanFormat();
+	}
+
+	return $account->iban;
+}
+
 /**
  * 		Check account number informations for a bank account
  *

+ 2 - 2
htdocs/core/lib/functions.lib.php

@@ -3310,8 +3310,8 @@ function dol_print_phone($phone, $countrycode = '', $cid = 0, $socid = 0, $addli
 			$newphone = substr($newphone, 0, 5).$separ.substr($newphone, 5, 3).$separ.substr($newphone, 8, 4);
 		}
 	} elseif (strtoupper($countrycode) == "MG") {//Madagascar
-		if (dol_strlen($phone) == 13) {//ex: +261_AB_CD_EF_GHI
-			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 2).$separ.substr($newphone, 6, 2).$separ.substr($newphone, 8, 2).$separ.substr($newphone, 10, 3);
+		if (dol_strlen($phone) == 13) {//ex: +261_AB_CD_EFG_HI
+			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 2).$separ.substr($newphone, 6, 2).$separ.substr($newphone, 8, 3).$separ.substr($newphone, 11, 2);
 		}
 	} elseif (strtoupper($countrycode) == "GB") {//Royaume uni
 		if (dol_strlen($phone) == 13) {//ex: +44_ABCD_EFG_HIJ

+ 3 - 3
htdocs/core/modules/expedition/doc/pdf_espadon.modules.php

@@ -986,20 +986,20 @@ class pdf_espadon extends ModelePdfExpedition
 		}
 
 		// Show barcode
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			$posx = 105;
 		} else {
 			$posx = $this->marge_gauche + 3;
 		}
 		//$pdf->Rect($this->marge_gauche, $this->marge_haute, $this->page_largeur-$this->marge_gauche-$this->marge_droite, 30);
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			// TODO Build code bar with function writeBarCode of barcode module for sending ref $object->ref
 			//$pdf->SetXY($this->marge_gauche+3, $this->marge_haute+3);
 			//$pdf->Image($logo,10, 5, 0, 24);
 		}
 
 		$pdf->SetDrawColor(128, 128, 128);
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			// TODO Build code bar with function writeBarCode of barcode module for sending ref $object->ref
 			//$pdf->SetXY($this->marge_gauche+3, $this->marge_haute+3);
 			//$pdf->Image($logo,10, 5, 0, 24);

+ 3 - 3
htdocs/core/modules/expedition/doc/pdf_rouget.modules.php

@@ -931,20 +931,20 @@ class pdf_rouget extends ModelePdfExpedition
 		}
 
 		// Show barcode
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			$posx = 105;
 		} else {
 			$posx = $this->marge_gauche + 3;
 		}
 		//$pdf->Rect($this->marge_gauche, $this->marge_haute, $this->page_largeur-$this->marge_gauche-$this->marge_droite, 30);
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			// TODO Build code bar with function writeBarCode of barcode module for sending ref $object->ref
 			//$pdf->SetXY($this->marge_gauche+3, $this->marge_haute+3);
 			//$pdf->Image($logo,10, 5, 0, 24);
 		}
 
 		$pdf->SetDrawColor(128, 128, 128);
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			// TODO Build code bar with function writeBarCode of barcode module for sending ref $object->ref
 			//$pdf->SetXY($this->marge_gauche+3, $this->marge_haute+3);
 			//$pdf->Image($logo,10, 5, 0, 24);

+ 10 - 10
htdocs/core/modules/modProduct.class.php

@@ -209,7 +209,7 @@ class modProduct extends DolibarrModules
 		if (!empty($conf->stock->enabled)) {
 			$this->export_fields_array[$r] = array_merge($this->export_fields_array[$r], array('e.ref'=>'DefaultWarehouse', 'p.tobatch'=>'ManageLotSerial', 'p.stock'=>'Stock', 'p.seuil_stock_alerte'=>'StockLimit', 'p.desiredstock'=>'DesiredStock', 'p.pmp'=>'PMPValue'));
 		}
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			$this->export_fields_array[$r] = array_merge($this->export_fields_array[$r], array('p.barcode'=>'BarCode'));
 		}
 		$keyforselect = 'product';
@@ -247,7 +247,7 @@ class modProduct extends DolibarrModules
 		if (!empty($conf->stock->enabled)) {
 			$this->export_TypeFields_array[$r] = array_merge($this->export_TypeFields_array[$r], array('e.ref'=>'Text', 'p.tobatch'=>'Numeric', 'p.stock'=>'Numeric', 'p.seuil_stock_alerte'=>'Numeric', 'p.desiredstock'=>'Numeric', 'p.pmp'=>'Numeric', 'p.cost_price'=>'Numeric'));
 		}
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			$this->export_TypeFields_array[$r] = array_merge($this->export_TypeFields_array[$r], array('p.barcode'=>'Text'));
 		}
 		if ((!empty($conf->fournisseur->enabled) && empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD)) || !empty($conf->supplier_order->enabled) || !empty($conf->supplier_invoice->enabled)) {
@@ -266,7 +266,7 @@ class modProduct extends DolibarrModules
 		if (!empty($conf->stock->enabled)) {
 			$this->export_entities_array[$r] = array_merge($this->export_entities_array[$r], array('p.stock'=>'product', 'p.pmp'=>'product'));
 		}
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			$this->export_entities_array[$r] = array_merge($this->export_entities_array[$r], array('p.barcode'=>'product'));
 		}
 		if ((!empty($conf->fournisseur->enabled) && empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD)) || !empty($conf->supplier_order->enabled) || !empty($conf->supplier_invoice->enabled)) {
@@ -281,7 +281,7 @@ class modProduct extends DolibarrModules
 		if (!empty($conf->stock->enabled)) {
 			$this->export_entities_array[$r] = array_merge($this->export_entities_array[$r], array('p.stock'=>'product', 'p.pmp'=>'product'));
 		}
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			$this->export_entities_array[$r] = array_merge($this->export_entities_array[$r], array('p.barcode'=>'product'));
 		}
 		if ((!empty($conf->fournisseur->enabled) && empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD)) || !empty($conf->supplier_order->enabled) || !empty($conf->supplier_invoice->enabled)) {
@@ -404,7 +404,7 @@ class modProduct extends DolibarrModules
 			if (!empty($conf->stock->enabled)) {
 				$this->export_fields_array[$r] = array_merge($this->export_fields_array[$r], array('p.stock'=>'Stock', 'p.seuil_stock_alerte'=>'StockLimit', 'p.desiredstock'=>'DesiredStock', 'p.pmp'=>'PMPValue'));
 			}
-			if (!empty($conf->barcode->enabled)) {
+			if (isModEnabled('barcode')) {
 				$this->export_fields_array[$r] = array_merge($this->export_fields_array[$r], array('p.barcode'=>'BarCode'));
 			}
 			$this->export_fields_array[$r] = array_merge($this->export_fields_array[$r], array('pa.qty'=>'Qty', 'pa.incdec'=>'ComposedProductIncDecStock'));
@@ -420,7 +420,7 @@ class modProduct extends DolibarrModules
 			if (!empty($conf->stock->enabled)) {
 				$this->export_TypeFields_array[$r] = array_merge($this->export_TypeFields_array[$r], array('p.stock'=>'Numeric', 'p.seuil_stock_alerte'=>'Numeric', 'p.desiredstock'=>'Numeric', 'p.pmp'=>'Numeric', 'p.cost_price'=>'Numeric'));
 			}
-			if (!empty($conf->barcode->enabled)) {
+			if (isModEnabled('barcode')) {
 				$this->export_TypeFields_array[$r] = array_merge($this->export_TypeFields_array[$r], array('p.barcode'=>'Text'));
 			}
 			$this->export_TypeFields_array[$r] = array_merge($this->export_TypeFields_array[$r], array('pa.qty'=>'Numeric'));
@@ -436,7 +436,7 @@ class modProduct extends DolibarrModules
 			if (!empty($conf->stock->enabled)) {
 				$this->export_entities_array[$r] = array_merge($this->export_entities_array[$r], array('p.stock'=>'virtualproduct', 'p.seuil_stock_alerte'=>'virtualproduct', 'p.desiredstock'=>'virtualproduct', 'p.pmp'=>'virtualproduct'));
 			}
-			if (!empty($conf->barcode->enabled)) {
+			if (isModEnabled('barcode')) {
 				$this->export_entities_array[$r] = array_merge($this->export_entities_array[$r], array('p.barcode'=>'virtualproduct'));
 			}
 			$this->export_entities_array[$r] = array_merge($this->export_entities_array[$r], array('pa.qty'=>"subproduct", 'pa.incdec'=>'subproduct'));
@@ -629,7 +629,7 @@ class modProduct extends DolibarrModules
 		if (is_object($mysoc) && $mysoc->useLocalTax(2)) {
 			$this->import_fields_array[$r] = array_merge($this->import_fields_array[$r], array('p.localtax2_tx'=>'LT2', 'p.localtax2_type'=>'LT2Type'));
 		}
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			$this->import_fields_array[$r] = array_merge($this->import_fields_array[$r], array('p.barcode'=>'BarCode'));
 		}
 		if (!empty($conf->global->PRODUCT_USE_UNITS)) {
@@ -722,7 +722,7 @@ class modProduct extends DolibarrModules
 		if (is_object($mysoc) && $mysoc->useLocalTax(2)) {
 			$import_sample = array_merge($import_sample, array('p.localtax2_tx'=>'', 'p.localtax2_type'=>''));
 		}
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			$import_sample = array_merge($import_sample, array('p.barcode'=>''));
 		}
 		if (!empty($conf->global->PRODUCT_USE_UNITS)) {
@@ -745,7 +745,7 @@ class modProduct extends DolibarrModules
 		}
 		$this->import_examplevalues_array[$r] = array_merge($import_sample, $import_extrafield_sample);
 		$this->import_updatekeys_array[$r] = array('p.ref'=>'Ref');
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			$this->import_updatekeys_array[$r] = array_merge($this->import_updatekeys_array[$r], array('p.barcode'=>'BarCode')); //only show/allow barcode as update key if Barcode module enabled
 		}
 

+ 10 - 10
htdocs/core/modules/modService.class.php

@@ -174,7 +174,7 @@ class modService extends DolibarrModules
 		if (!empty($conf->stock->enabled)) {
 			$this->export_fields_array[$r] = array_merge($this->export_fields_array[$r], array('p.stock'=>'Stock', 'p.seuil_stock_alerte'=>'StockLimit', 'p.desiredstock'=>'DesiredStock', 'p.pmp'=>'PMPValue'));
 		}
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			$this->export_fields_array[$r] = array_merge($this->export_fields_array[$r], array('p.barcode'=>'BarCode'));
 		}
 		$keyforselect = 'product';
@@ -210,7 +210,7 @@ class modService extends DolibarrModules
 		if (!empty($conf->stock->enabled)) {
 			$this->export_TypeFields_array[$r] = array_merge($this->export_TypeFields_array[$r], array('p.stock'=>'Numeric', 'p.seuil_stock_alerte'=>'Numeric', 'p.desiredstock'=>'Numeric', 'p.pmp'=>'Numeric', 'p.cost_price'=>'Numeric'));
 		}
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			$this->export_TypeFields_array[$r] = array_merge($this->export_TypeFields_array[$r], array('p.barcode'=>'Text'));
 		}
 		if ((!empty($conf->fournisseur->enabled) && empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD)) || !empty($conf->supplier_order->enabled) || !empty($conf->supplier_invoice->enabled)) {
@@ -229,7 +229,7 @@ class modService extends DolibarrModules
 		if (!empty($conf->stock->enabled)) {
 			$this->export_entities_array[$r] = array_merge($this->export_entities_array[$r], array('p.stock'=>'product', 'p.pmp'=>'product'));
 		}
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			$this->export_entities_array[$r] = array_merge($this->export_entities_array[$r], array('p.barcode'=>'product'));
 		}
 		if ((!empty($conf->fournisseur->enabled) && empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD)) || !empty($conf->supplier_order->enabled) || !empty($conf->supplier_invoice->enabled)) {
@@ -244,7 +244,7 @@ class modService extends DolibarrModules
 		if (!empty($conf->stock->enabled)) {
 			$this->export_entities_array[$r] = array_merge($this->export_entities_array[$r], array('p.stock'=>'product', 'p.pmp'=>'product'));
 		}
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			$this->export_entities_array[$r] = array_merge($this->export_entities_array[$r], array('p.barcode'=>'product'));
 		}
 		if ((!empty($conf->fournisseur->enabled) && empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD)) || !empty($conf->supplier_order->enabled) || !empty($conf->supplier_invoice->enabled)) {
@@ -363,7 +363,7 @@ class modService extends DolibarrModules
 				if (!empty($conf->stock->enabled)) {
 					$this->export_fields_array[$r] = array_merge($this->export_fields_array[$r], array('p.stock'=>'Stock', 'p.seuil_stock_alerte'=>'StockLimit', 'p.desiredstock'=>'DesiredStock', 'p.pmp'=>'PMPValue'));
 				}
-				if (!empty($conf->barcode->enabled)) {
+				if (isModEnabled('barcode')) {
 					$this->export_fields_array[$r] = array_merge($this->export_fields_array[$r], array('p.barcode'=>'BarCode'));
 				}
 				$this->export_fields_array[$r] = array_merge($this->export_fields_array[$r], array('pa.qty'=>'Qty', 'pa.incdec'=>'ComposedProductIncDecStock'));
@@ -379,7 +379,7 @@ class modService extends DolibarrModules
 				if (!empty($conf->stock->enabled)) {
 					$this->export_TypeFields_array[$r] = array_merge($this->export_TypeFields_array[$r], array('p.stock'=>'Numeric', 'p.seuil_stock_alerte'=>'Numeric', 'p.desiredstock'=>'Numeric', 'p.pmp'=>'Numeric', 'p.cost_price'=>'Numeric'));
 				}
-				if (!empty($conf->barcode->enabled)) {
+				if (isModEnabled('barcode')) {
 					$this->export_TypeFields_array[$r] = array_merge($this->export_TypeFields_array[$r], array('p.barcode'=>'Text'));
 				}
 				$this->export_TypeFields_array[$r] = array_merge($this->export_TypeFields_array[$r], array('pa.qty'=>'Numeric'));
@@ -395,7 +395,7 @@ class modService extends DolibarrModules
 				if (!empty($conf->stock->enabled)) {
 					$this->export_entities_array[$r] = array_merge($this->export_entities_array[$r], array('p.stock'=>'virtualproduct', 'p.seuil_stock_alerte'=>'virtualproduct', 'p.desiredstock'=>'virtualproduct', 'p.pmp'=>'virtualproduct'));
 				}
-				if (!empty($conf->barcode->enabled)) {
+				if (isModEnabled('barcode')) {
 					$this->export_entities_array[$r] = array_merge($this->export_entities_array[$r], array('p.barcode'=>'virtualproduct'));
 				}
 				$this->export_entities_array[$r] = array_merge($this->export_entities_array[$r], array('pa.qty'=>"subproduct", 'pa.incdec'=>'subproduct'));
@@ -576,7 +576,7 @@ class modService extends DolibarrModules
 		if (is_object($mysoc) && $mysoc->useLocalTax(2)) {
 			$this->import_fields_array[$r] = array_merge($this->import_fields_array[$r], array('p.localtax2_tx'=>'LT2', 'p.localtax2_type'=>'LT2Type'));
 		}
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			$this->import_fields_array[$r] = array_merge($this->import_fields_array[$r], array('p.barcode'=>'BarCode'));
 		}
 		if (!empty($conf->global->PRODUCT_USE_UNITS)) {
@@ -667,7 +667,7 @@ class modService extends DolibarrModules
 		if (is_object($mysoc) && $mysoc->useLocalTax(2)) {
 			$import_sample = array_merge($import_sample, array('p.localtax2_tx'=>'', 'p.localtax2_type'=>''));
 		}
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			$import_sample = array_merge($import_sample, array('p.barcode'=>''));
 		}
 		if (!empty($conf->global->PRODUCT_USE_UNITS)) {
@@ -693,7 +693,7 @@ class modService extends DolibarrModules
 		}
 		$this->import_examplevalues_array[$r] = array_merge($import_sample, $import_extrafield_sample);
 		$this->import_updatekeys_array[$r] = array('p.ref'=>'Ref');
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			$this->import_updatekeys_array[$r] = array_merge($this->import_updatekeys_array[$r], array('p.barcode'=>'BarCode')); //only show/allow barcode as update key if Barcode module enabled
 		}
 

+ 3 - 3
htdocs/core/modules/reception/doc/pdf_squille.modules.php

@@ -856,20 +856,20 @@ class pdf_squille extends ModelePdfReception
 		}
 
 		// Show barcode
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			$posx = 105;
 		} else {
 			$posx = $this->marge_gauche + 3;
 		}
 		//$pdf->Rect($this->marge_gauche, $this->marge_haute, $this->page_largeur-$this->marge_gauche-$this->marge_droite, 30);
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			// TODO Build code bar with function writeBarCode of barcode module for reception ref $object->ref
 			//$pdf->SetXY($this->marge_gauche+3, $this->marge_haute+3);
 			//$pdf->Image($logo,10, 5, 0, 24);
 		}
 
 		$pdf->SetDrawColor(128, 128, 128);
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			// TODO Build code bar with function writeBarCode of barcode module for reception ref $object->ref
 			//$pdf->SetXY($this->marge_gauche+3, $this->marge_haute+3);
 			//$pdf->Image($logo,10, 5, 0, 24);

+ 3 - 3
htdocs/core/modules/stocktransfer/doc/pdf_eagle.modules.php

@@ -944,20 +944,20 @@ class pdf_eagle extends ModelePdfStockTransfer
 		}
 
 		// Show barcode
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			$posx = 105;
 		} else {
 			$posx = $this->marge_gauche + 3;
 		}
 		//$pdf->Rect($this->marge_gauche, $this->marge_haute, $this->page_largeur-$this->marge_gauche-$this->marge_droite, 30);
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			// TODO Build code bar with function writeBarCode of barcode module for sending ref $object->ref
 			//$pdf->SetXY($this->marge_gauche+3, $this->marge_haute+3);
 			//$pdf->Image($logo,10, 5, 0, 24);
 		}
 
 		$pdf->SetDrawColor(128, 128, 128);
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			// TODO Build code bar with function writeBarCode of barcode module for sending ref $object->ref
 			//$pdf->SetXY($this->marge_gauche+3, $this->marge_haute+3);
 			//$pdf->Image($logo,10, 5, 0, 24);

+ 23 - 1
htdocs/core/website.inc.php

@@ -129,13 +129,35 @@ if (!defined('USEDOLIBARRSERVER') && !defined('USEDOLIBARREDITOR')) {
 
 	// Referrer-Policy
 	if (!defined('WEBSITE_MAIN_SECURITY_FORCERP')) {
-		// The constant WEBSITE_MAIN_SECURITY_FORCECSP should never be defined by page, but the variable used just after may be
+		// The constant WEBSITE_MAIN_SECURITY_FORCERP should never be defined by page, but the variable used just after may be
 
 		// For public web sites, we use the same default value than "strict-origin-when-cross-origin"
 		$referrerpolicy = getDolGlobalString('WEBSITE_MAIN_SECURITY_FORCERP', "strict-origin-when-cross-origin");
 
 		header("Referrer-Policy: ".$referrerpolicy);
 	}
+
+	// Strict-Transport-Security
+	if (!defined('WEBSITE_MAIN_SECURITY_FORCESTS')) {
+		// The constant WEBSITE_MAIN_SECURITY_FORCESTS should never be defined by page, but the variable used just after may be
+
+		// Example: "max-age=31536000; includeSubDomains"
+		$sts = getDolGlobalString('WEBSITE_MAIN_SECURITY_FORCESTS');
+		if (!empty($sts)) {
+			header("Strict-Transport-Security: ".$sts);
+		}
+	}
+
+	// Permissions-Policy (old name was Feature-Policy)
+	if (!defined('WEBSITE_MAIN_SECURITY_FORCEPP')) {
+		// The constant WEBSITE_MAIN_SECURITY_FORCEPP should never be defined by page, but the variable used just after may be
+
+		// Example: "camera: 'none'; microphone: 'none';"
+		$pp = getDolGlobalString('WEBSITE_MAIN_SECURITY_FORCEPP');
+		if (!empty($pp)) {
+			header("Permissions-Policy: ".$pp);
+		}
+	}
 }
 
 // A lang was forced, so we change weblangs init

+ 17 - 0
htdocs/cron/class/cronjob.class.php

@@ -137,6 +137,11 @@ class Cronjob extends CommonObject
 	 */
 	public $processing;
 
+	/**
+	 * @var int The job current PID
+	 */
+	public $pid;
+
 	/**
 	 * @var int ID
 	 */
@@ -422,6 +427,7 @@ class Cronjob extends CommonObject
 		$sql .= " t.frequency,";
 		$sql .= " t.status,";
 		$sql .= " t.processing,";
+		$sql .= " t.pid,";
 		$sql .= " t.fk_user_author,";
 		$sql .= " t.fk_user_mod,";
 		$sql .= " t.note as note_private,";
@@ -470,6 +476,7 @@ class Cronjob extends CommonObject
 				$this->frequency = $obj->frequency;
 				$this->status = $obj->status;
 				$this->processing = $obj->processing;
+				$this->pid = $obj->pid;
 				$this->fk_user_author = $obj->fk_user_author;
 				$this->fk_user_mod = $obj->fk_user_mod;
 				$this->note_private = $obj->note_private;
@@ -530,6 +537,7 @@ class Cronjob extends CommonObject
 		$sql .= " t.frequency,";
 		$sql .= " t.status,";
 		$sql .= " t.processing,";
+		$sql .= " t.pid,";
 		$sql .= " t.fk_user_author,";
 		$sql .= " t.fk_user_mod,";
 		$sql .= " t.note as note_private,";
@@ -606,6 +614,7 @@ class Cronjob extends CommonObject
 					$line->frequency = $obj->frequency;
 					$line->status = $obj->status;
 					$line->processing = $obj->processing;
+					$line->pid = $obj->pid;
 					$line->fk_user_author = $obj->fk_user_author;
 					$line->fk_user_mod = $obj->fk_user_mod;
 					$line->note_private = $obj->note_private;
@@ -708,6 +717,10 @@ class Cronjob extends CommonObject
 			$this->processing = 0;
 		}
 
+		if (empty($this->pid)) {
+			$this->pid = null;
+		}
+
 		// Check parameters
 		// Put here code to add a control on parameters values
 		if (dol_strlen($this->datenextrun) == 0) {
@@ -773,6 +786,7 @@ class Cronjob extends CommonObject
 		$sql .= " frequency=".(isset($this->frequency) ? $this->frequency : "null").",";
 		$sql .= " status=".(isset($this->status) ? $this->status : "null").",";
 		$sql .= " processing=".((isset($this->processing) && $this->processing > 0) ? $this->processing : "0").",";
+		$sql .= " pid=".(isset($this->pid) ? $this->pid : "null").",";
 		$sql .= " fk_user_mod=".$user->id.",";
 		$sql .= " note=".(isset($this->note_private) ? "'".$this->db->escape($this->note_private)."'" : "null").",";
 		$sql .= " nbrun=".((isset($this->nbrun) && $this->nbrun > 0) ? $this->nbrun : "null").",";
@@ -925,6 +939,7 @@ class Cronjob extends CommonObject
 		$this->frequency = '';
 		$this->status = 0;
 		$this->processing = 0;
+		$this->pid = null;
 		$this->fk_user_author = 0;
 		$this->fk_user_mod = 0;
 		$this->note_private = '';
@@ -1125,6 +1140,7 @@ class Cronjob extends CommonObject
 		$this->lastoutput = '';
 		$this->lastresult = '';
 		$this->processing = 1; // To know job was started
+		$this->pid = dol_getmypid();
 		$this->nbrun = $this->nbrun + 1;
 		$result = $this->update($user); // This include begin/commit
 		if ($result < 0) {
@@ -1316,6 +1332,7 @@ class Cronjob extends CommonObject
 
 		$this->datelastresult = dol_now();
 		$this->processing = 0;
+		$this->pid = null;
 		$result = $this->update($user); // This include begin/commit
 		if ($result < 0) {
 			dol_syslog(get_class($this)."::run_jobs ".$this->error, LOG_ERR);

+ 3 - 3
htdocs/fourn/class/fournisseur.product.class.php

@@ -620,7 +620,7 @@ class ProductFournisseur extends Product
 				$this->fourn_multicurrency_tx          = $obj->multicurrency_tx;
 				$this->fourn_multicurrency_id          = $obj->fk_multicurrency;
 				$this->fourn_multicurrency_code        = $obj->multicurrency_code;
-				if (!empty($conf->barcode->enabled)) {
+				if (isModEnabled('barcode')) {
 					$this->fourn_barcode = $obj->barcode; // deprecated
 					$this->fourn_fk_barcode_type = $obj->fk_barcode_type; // deprecated
 					$this->supplier_barcode = $obj->barcode;
@@ -730,7 +730,7 @@ class ProductFournisseur extends Product
 
 				$prodfourn->packaging = $record["packaging"];
 
-				if (!empty($conf->barcode->enabled)) {
+				if (isModEnabled('barcode')) {
 					$prodfourn->supplier_barcode = $record["barcode"];
 					$prodfourn->supplier_fk_barcode_type = $record["fk_barcode_type"];
 				}
@@ -1178,7 +1178,7 @@ class ProductFournisseur extends Product
 				$label .= "<br><b>".$langs->trans("ManageLotSerial").'</b>: '.$this->getLibStatut(0, 2);
 			}
 		}
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			$label .= '<br><b>'.$langs->trans('BarCode').':</b> '.$this->barcode;
 		}
 

+ 3 - 3
htdocs/index.php

@@ -255,19 +255,19 @@ if (empty($conf->global->MAIN_DISABLE_GLOBAL_WORKBOARD)) {
 	// Number of cheque to send
 	if (isModEnabled('banque')  && empty($conf->global->MAIN_DISABLE_BLOCK_BANK) && $user->hasRight('banque', 'lire') && !$user->socid) {
 		if (empty($conf->global->BANK_DISABLE_CHECK_DEPOSIT)) {
-			include_once DOL_DOCUMENT_ROOT.'/compta/paiement/cheque/class/remisecheque.class.php';
+			include_once DOL_DOCUMENT_ROOT . '/compta/paiement/cheque/class/remisecheque.class.php';
 			$board = new RemiseCheque($db);
 			$dashboardlines[$board->element] = $board->load_board($user);
 		}
 		if (isModEnabled('prelevement')) {
 			include_once DOL_DOCUMENT_ROOT.'/compta/prelevement/class/bonprelevement.class.php';
 			$board = new BonPrelevement($db);
-			$dashboardlines[$board->element.'_direct_debit'] = $board->load_board($user, 'direct_debit');
+			$dashboardlines[$board->element . '_direct_debit'] = $board->load_board($user, 'direct_debit');
 		}
 		if (isModEnabled('paymentbybanktransfer')) {
 			include_once DOL_DOCUMENT_ROOT.'/compta/prelevement/class/bonprelevement.class.php';
 			$board = new BonPrelevement($db);
-			$dashboardlines[$board->element.'_credit_transfer'] = $board->load_board($user, 'credit_transfer');
+			$dashboardlines[$board->element . '_credit_transfer'] = $board->load_board($user, 'credit_transfer');
 		}
 	}
 

BIN
htdocs/install/doctemplates/websites/website_template-style01.jpg


BIN
htdocs/install/doctemplates/websites/website_template-style01.png


BIN
htdocs/install/doctemplates/websites/website_template-style01.zip


BIN
htdocs/install/doctemplates/websites/website_template-style03.zip


BIN
htdocs/install/doctemplates/websites/website_template-style04.png


BIN
htdocs/install/doctemplates/websites/website_template-style04.zip


+ 10 - 0
htdocs/install/mysql/data/llx_c_hrm_public_holiday.sql

@@ -47,6 +47,16 @@ INSERT INTO llx_c_hrm_public_holiday (code, entity, fk_country, dayrule, year, m
 INSERT INTO llx_c_hrm_public_holiday (code, entity, fk_country, dayrule, year, month, day, active) VALUES('FR-ASCENSION',   0, 1, 'ascension', 0, 0, 0, 1);
 INSERT INTO llx_c_hrm_public_holiday (code, entity, fk_country, dayrule, year, month, day, active) VALUES('FR-PENTECOST',   0, 1, 'pentecost', 0, 0, 0, 1);
 
+-- Belgium only (2)
+INSERT INTO llx_c_hrm_public_holiday (code, entity, fk_country, dayrule, year, month, day, active) VALUES('BE-VICTORYDAY',  0, 2, '', 0,  5,  8, 1);
+INSERT INTO llx_c_hrm_public_holiday (code, entity, fk_country, dayrule, year, month, day, active) VALUES('BE-NATIONALDAY', 0, 2, '', 0,  7, 21, 1);
+INSERT INTO llx_c_hrm_public_holiday (code, entity, fk_country, dayrule, year, month, day, active) VALUES('BE-ASSOMPTION',  0, 2, '', 0,  8, 15, 1);
+INSERT INTO llx_c_hrm_public_holiday (code, entity, fk_country, dayrule, year, month, day, active) VALUES('BE-TOUSSAINT',   0, 2, '', 0, 11,  1, 1);
+INSERT INTO llx_c_hrm_public_holiday (code, entity, fk_country, dayrule, year, month, day, active) VALUES('BE-ARMISTICE',   0, 2, '', 0, 11, 11, 1);
+INSERT INTO llx_c_hrm_public_holiday (code, entity, fk_country, dayrule, year, month, day, active) VALUES('BE-EASTER',      0, 2, 'eastermonday', 0, 0, 0, 1);
+INSERT INTO llx_c_hrm_public_holiday (code, entity, fk_country, dayrule, year, month, day, active) VALUES('BE-ASCENSION',   0, 2, 'ascension', 0, 0, 0, 1);
+INSERT INTO llx_c_hrm_public_holiday (code, entity, fk_country, dayrule, year, month, day, active) VALUES('BE-PENTECOST',   0, 2, 'pentecost', 0, 0, 0, 1);
+
 -- Italy (3)
 INSERT INTO llx_c_hrm_public_holiday (code, entity, fk_country, year, month, day, active) VALUES('IT-LIBEAZIONE',     0, 3, 0,  4, 25, 1);
 INSERT INTO llx_c_hrm_public_holiday (code, entity, fk_country, year, month, day, active) VALUES('IT-EPIPHANY',       0, 3, 0,  6,  1, 1);

+ 11 - 0
htdocs/install/mysql/migration/16.0.0-17.0.0.sql

@@ -84,3 +84,14 @@ ALTER TABLE llx_societe ADD last_main_doc VARCHAR(255) NULL AFTER model_pdf;
 ALTER TABLE llx_ticket ADD COLUMN ip varchar(250);
 
 ALTER TABLE llx_ticket ADD email_date datetime after email_msgid;
+
+ALTER TABLE llx_cronjob ADD COLUMN pid integer;
+
+INSERT INTO llx_c_hrm_public_holiday (code, entity, fk_country, dayrule, year, month, day, active) VALUES('BE-VICTORYDAY',  0, 2, '', 0,  5,  8, 1);
+INSERT INTO llx_c_hrm_public_holiday (code, entity, fk_country, dayrule, year, month, day, active) VALUES('BE-NATIONALDAY', 0, 2, '', 0,  7, 21, 1);
+INSERT INTO llx_c_hrm_public_holiday (code, entity, fk_country, dayrule, year, month, day, active) VALUES('BE-ASSOMPTION',  0, 2, '', 0,  8, 15, 1);
+INSERT INTO llx_c_hrm_public_holiday (code, entity, fk_country, dayrule, year, month, day, active) VALUES('BE-TOUSSAINT',   0, 2, '', 0, 11,  1, 1);
+INSERT INTO llx_c_hrm_public_holiday (code, entity, fk_country, dayrule, year, month, day, active) VALUES('BE-ARMISTICE',   0, 2, '', 0, 11, 11, 1);
+INSERT INTO llx_c_hrm_public_holiday (code, entity, fk_country, dayrule, year, month, day, active) VALUES('BE-EASTER',      0, 2, 'eastermonday', 0, 0, 0, 1);
+INSERT INTO llx_c_hrm_public_holiday (code, entity, fk_country, dayrule, year, month, day, active) VALUES('BE-ASCENSION',   0, 2, 'ascension', 0, 0, 0, 1);
+INSERT INTO llx_c_hrm_public_holiday (code, entity, fk_country, dayrule, year, month, day, active) VALUES('BE-PENTECOST',   0, 2, 'pentecost', 0, 0, 0, 1);

+ 1 - 0
htdocs/install/mysql/tables/llx_cronjob.sql

@@ -47,6 +47,7 @@ CREATE TABLE llx_cronjob
     autodelete      integer DEFAULT 0,				-- 0=Job is kept unchanged once nbrun > maxrun or date > dateend, 2=Job must be archived (archive = status 2) once nbrun > maxrun or date > dateend 
   	status 			integer NOT NULL DEFAULT 1,		-- 0=disabled, 1=enabled, 2=archived
   	processing 		integer NOT NULL DEFAULT 0,		-- 1=process currently running
+  	pid             integer,                        -- The cronjob PID, NULL if not in processing
   	test		    varchar(255) DEFAULT '1',
   	fk_user_author 	integer DEFAULT NULL,
   	fk_user_mod 	integer DEFAULT NULL,

+ 73 - 12
htdocs/mrp/js/lib_dispatch.js.php

@@ -70,7 +70,7 @@ function addDispatchLine(index, type, mode)
 	mode = mode || 'qtymissing'
 
 	console.log("fourn/js/lib_dispatch.js.php Split line type="+type+" index="+index+" mode="+mode);
-	if(mode == 'qtymissingconsume') {
+	if(mode == 'qtymissingconsume' || mode == 'allmissingconsume') {
 		var inputId = 'qtytoconsume';
 		var warehouseId = 'idwarehouse';
 	}
@@ -94,16 +94,70 @@ function addDispatchLine(index, type, mode)
 		console.log($("#qty_dispatched_"+index).val());
 		// If user did not reduced the qty to dispatch on old line, we keep only 1 on old line and the rest on new line
 		if (qtyDispatched == qtyOrdered && qtyDispatched > 1) {
-			qtyDispatched = parseFloat($("#qty_dispatched_"+index).val()) + 1;
-			mode = 'lessone';
+			qtyDispatched = parseFloat($("#qty_dispatched_" + index).val()) + 1;
+
+		}
+		if(mode == 'allmissingconsume' || mode == 'alltoproduce') {
+				var qtymax = parseFloat($($row).data('max-qty'));
+				if(qtymax === 'undefined') qtymax = 1;
+		}
+	}
+
+
+	if(mode == 'allmissingconsume' || mode == 'alltoproduce') {
+		var count = 0;
+		var qtyalreadyused = 0;
+		var error = 0;
+
+		while (qtyalreadyused < qty) {
+			//If remaining qty needed is inferior to qty asked, qtymax = qty asked - qty already used
+			if ((qtyalreadyused + qtymax) > qty) qtymax = qty - qtyalreadyused;
+			//If first line, we replace value, not add line
+			if(count === 0){
+				$("#"+inputId+"-"+index+"-"+nbrTrs).val(qtymax);
+			} else {
+				var res = addDispatchTR(qtyOrdered, qtyDispatched, index, nbrTrs, warehouseId, inputId, type, qtymax, mode, $row);
+				if(res === -1){
+					error = 1;
+					break;
+				}
+				nbrTrs++;
+			}
+			qtyalreadyused = qtyalreadyused + qtymax;
+			count++;
+			$row = $("tr[name='" + type + '_' + index + "_1']").clone(true);
+		}
+
+		if(error === 0) {
+			addDispatchTR(qtyOrdered, qtyDispatched, index, nbrTrs, warehouseId, inputId, type, '', mode, $row);
 		}
 	}
-	console.log("qtyDispatched="+qtyDispatched+" qtyOrdered="+qtyOrdered);
+	else addDispatchTR(qtyOrdered, qtyDispatched, index, nbrTrs, warehouseId, inputId, type, qty, mode, $row)
 
+}
+
+/**
+ * addDispatchTR
+ * Adds new table row for dispatching to multiple stock locations or multiple lot/serial
+ *
+ * @param qtyOrdered    double
+ * @param qtyDispatched double
+ * @param index         int
+ * @param nbrTrs        int
+ * @param warehouseId   int
+ * @param inputId       int
+ * @param type          string
+ * @param qty           double
+ * @param mode          string
+ * @param $row          object
+ */
+function addDispatchTR(qtyOrdered, qtyDispatched, index, nbrTrs, warehouseId, inputId, type, qty, mode, $row) {
 	if (qtyOrdered <= 1) {
 		window.alert("Quantity can't be split");
+		return -1;
 	} else if (qtyDispatched >= qtyOrdered) {
 		window.alert("No remain qty to dispatch");
+		return -1;
 	} else if (qtyDispatched < qtyOrdered) {
 		//replace tr suffix nbr
 		var re1 = new RegExp('_'+index+'_1', 'g');
@@ -128,24 +182,27 @@ function addDispatchLine(index, type, mode)
 		/*  Suffix of lines are:  index _ trs.length */
 		$("#"+inputId+"-"+index+"-"+(nbrTrs+1)).focus();
 		if ($("#"+inputId+"-"+index+"-"+(nbrTrs)).val() == 0) {
-			$("#"+inputId+"-"+index+"-"+(nbrTrs)).val(1);
+			if(mode == 'allmissingconsume' || mode == 'alltoproduce') $("#"+inputId+"-"+index+"-"+(nbrTrs)).val(qty);
+			else $("#"+inputId+"-"+index+"-"+(nbrTrs)).val(1);
 		}
 		var totalonallines = 0;
 		for (let i = 1; i <= nbrTrs; i++) {
 			console.log(i+" = "+parseFloat($("#"+inputId+"-"+index+"-"+i).val()));
 			totalonallines = totalonallines + parseFloat($("#"+inputId+"-"+index+"-"+i).val());
 		}
-		console.log("totalonallines="+totalonallines);
-		if (totalonallines == qtyOrdered && qtyOrdered > 1) {
-			var prevouslineqty = $("#"+inputId+"-"+index+"-"+nbrTrs).val();
-			$("#"+inputId+"-"+index+"-"+(nbrTrs)).val(1);
-			$("#"+inputId+"-"+index+"-"+(nbrTrs+1)).val(prevouslineqty - 1);
+
+		if(mode != 'allmissingconsume' && mode != 'alltoproduce') {
+			if (totalonallines == qtyOrdered && qtyOrdered > 1) {
+				var prevouslineqty = $("#" + inputId + "-" + index + "-" + nbrTrs).val();
+				$("#" + inputId + "-" + index + "-" + (nbrTrs)).val(1);
+				$("#" + inputId + "-" + index + "-" + (nbrTrs + 1)).val(prevouslineqty - 1);
+			}
 		}
 		$("#qty_dispatched_"+index).val(qtyDispatched);
 
 		//hide all buttons then show only the last one
-		$("tr[name^='"+type+"_'][name$='_"+index+"'] .splitbutton").hide();
-		$("tr[name^='"+type+"_'][name$='_"+index+"']:last .splitbutton").show();
+		$("tr[name^='"+type+"_"+index+"_'] .splitbutton").hide();
+		$("tr[name^='"+type+"_"+index+"_']:last .splitbutton").show();
 
 		if (mode === 'lessone')
 		{
@@ -156,6 +213,10 @@ function addDispatchLine(index, type, mode)
 		$("#"+inputId+"-"+index+(nbrTrs)).data('qty', qty);
 		$("#"+inputId+"-"+index+(nbrTrs)).data('type', type);
 		$("#"+inputId+"-"+index+(nbrTrs)).data('index', index);
+		if(mode == 'allmissingconsume' || mode == 'alltoproduce') {
+			let currentQtyDispatched = qtyDispatched+qty;
+			$row.find("input[id^='"+inputId+"']").val(qty);
+		}
 	}
 }
 

+ 1 - 1
htdocs/mrp/mo_agenda.php

@@ -209,7 +209,7 @@ if ($object->id > 0) {
 		if (get_class($objthirdparty) == 'Societe') {
 			$out .= '&amp;socid='.$objthirdparty->id;
 		}
-		$out .= (!empty($objcon->id) ? '&amp;contactid='.$objcon->id : '').'&amp;backtopage=1&amp;percentage=-1';
+		$out .= (!empty($objcon->id) ? '&amp;contactid='.$objcon->id : '').'&amp;backtopage='.urlencode($_SERVER["PHP_SELF"].'?id='.$object->id).'&amp;percentage=-1';
 		//$out.=$langs->trans("AddAnAction").' ';
 		//$out.=img_picto($langs->trans("AddAnAction"),'filenew');
 		//$out.="</a>";

+ 42 - 3
htdocs/mrp/mo_production.php

@@ -772,6 +772,13 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea
 		if ($permissiontodelete) {
 			print '<td></td>';
 		}
+
+		// Split
+		print '<td></td>';
+
+		// SplitAll
+		print '<td></td>';
+
 		print '</tr>';
 
 		if ($action == 'addconsumeline') {
@@ -855,7 +862,7 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea
 					print '<input id="qty_ordered'.$suffix.'" type="hidden" value="'.$line->qty.'">';
 					print '<input id="qty_dispatched'.$suffix.'" type="hidden" value="'.$alreadyconsumed.'">';
 
-					print '<tr>';
+					print '<tr data-line-id="'.$line->id.'">';
 					// Product
 					print '<td>'.$tmpproduct->getNomUrl(1);
 					print '<br><span class="opacitymedium small">'.$tmpproduct->label.'</span>';
@@ -935,6 +942,13 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea
 						print '</a>';
 						print '</td>';
 					}
+
+					// Split
+					print '<td></td>';
+
+					// Split All
+					print '<td></td>';
+
 					print '</tr>';
 
 					// Show detailed of already consumed with js code to collapse
@@ -1000,7 +1014,8 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea
 					if (in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
 						$i = 1;
 						print '<!-- Enter line to consume -->'."\n";
-						print '<tr name="batch_'.$line->id.'_'.$i.'">';
+						$maxQty = 1;
+						print '<tr data-max-qty="'.$maxQty.'" name="batch_'.$line->id.'_'.$i.'">';
 						// Ref
 						print '<td><span class="opacitymedium">'.$langs->trans("ToConsume").'</span></td>';
 						$preselected = (GETPOSTISSET('qty-'.$line->id.'-'.$i) ? GETPOST('qty-'.$line->id.'-'.$i) : max(0, $line->qty - $alreadyconsumed));
@@ -1052,7 +1067,12 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea
 								print $formproduct->selectLotDataList('batch-'.$line->id.'-'.$i, 0, $line->fk_product, '', '');
 
 								$type = 'batch';
+								print '<td align="right" class="split">';
 								print ' '.img_picto($langs->trans('AddStockLocationLine'), 'split.png', 'class="splitbutton" onClick="addDispatchLine('.((int) $line->id).', \''.dol_escape_js($type).'\', \'qtymissingconsume\')"');
+								print '</td>';
+								print '<td align="right" class="splitall">';
+								if (($action == 'consumeorproduce' || $action == 'consumeandproduceall')) print img_picto($langs->trans('SplitAllQuantity'), 'split.png', 'class="splitbutton splitallbutton field-error-icon" data-max-qty="1" onClick="addDispatchLine('.$line->id.', \'batch\', \'allmissingconsume\')"');
+								print '</td>';
 							}
 							print '</td>';
 						}
@@ -1130,6 +1150,12 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea
 			print '<td>';
 			if ($collapse || in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
 				print $langs->trans("Batch");
+
+				// Split
+				print '<td></td>';
+
+				// Split All
+				print '<td></td>';
 			}
 			print '</td>';
 			print '<td></td>';
@@ -1293,13 +1319,20 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea
 							}
 							print '</td>';
 							print '<td></td>';
+
+							// Split
+							print '<td></td>';
+
+							// Split All
+							print '<td></td>';
 						}
 						print '</tr>';
 					}
 
 					if (in_array($action, array('consumeorproduce', 'consumeandproduceall'))) {
 						print '<!-- Enter line to produce -->'."\n";
-						print '<tr name="batch_'.$line->id.'_'.$i.'">';
+						$maxQty = 1;
+						print '<tr data-max-qty="'.$maxQty.'" name="batch_'.$line->id.'_'.$i.'">';
 						print '<td><span class="opacitymedium">'.$langs->trans("ToProduce").'</span></td>';
 						$preselected = (GETPOSTISSET('qtytoproduce-'.$line->id.'-'.$i) ? GETPOST('qtytoproduce-'.$line->id.'-'.$i) : max(0, $line->qty - $alreadyproduced));
 						if ($action == 'consumeorproduce' && !GETPOSTISSET('qtytoproduce-'.$line->id.'-'.$i)) {
@@ -1346,7 +1379,13 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea
 							print '<td>';
 							if ($tmpproduct->status_batch) {
 								$type = 'batch';
+								print '<td align="right" class="split">';
 								print img_picto($langs->trans('AddStockLocationLine'), 'split.png', 'class="splitbutton" onClick="addDispatchLine('.$line->id.', \''.$type.'\', \'qtymissing\')"');
+								print '</td>';
+
+								print '<td align="right"  class="splitall">';
+								if (($action == 'consumeorproduce' || $action == 'consumeandproduceall') && $tmpproduct->status_batch == 2) print img_picto($langs->trans('SplitAllQuantity'), 'split.png', 'class="splitbutton splitallbutton field-error-icon" onClick="addDispatchLine('.$line->id.', \'batch\', \'alltoproduce\')"'); //
+								print '</td>';
 							}
 							print '</td>';
 						}

+ 1 - 1
htdocs/product/card.php

@@ -1228,7 +1228,7 @@ llxHeader('', $title, $help_url);
 
 // Load object modBarCodeProduct
 $res = 0;
-if (!empty($conf->barcode->enabled) && !empty($conf->global->BARCODE_PRODUCT_ADDON_NUM)) {
+if (isModEnabled('barcode') && !empty($conf->global->BARCODE_PRODUCT_ADDON_NUM)) {
 	$module = strtolower($conf->global->BARCODE_PRODUCT_ADDON_NUM);
 	$dirbarcode = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
 	foreach ($dirbarcode as $dirroot) {

+ 3 - 3
htdocs/product/class/product.class.php

@@ -474,7 +474,7 @@ class Product extends CommonObject
 		'ref'           =>array('type'=>'varchar(128)', 'label'=>'Ref', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'showoncombobox'=>1, 'index'=>1, 'position'=>10, 'searchall'=>1, 'comment'=>'Reference of object'),
 		'entity'        =>array('type'=>'integer', 'label'=>'Entity', 'enabled'=>1, 'visible'=>0, 'default'=>1, 'notnull'=>1, 'index'=>1, 'position'=>5),
 		'label'         =>array('type'=>'varchar(255)', 'label'=>'Label', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'showoncombobox'=>2, 'position'=>15),
-		'barcode'       =>array('type'=>'varchar(255)', 'label'=>'Barcode', 'enabled'=>'!empty($conf->barcode->enabled)', 'position'=>20, 'visible'=>-1, 'showoncombobox'=>3),
+		'barcode'       =>array('type'=>'varchar(255)', 'label'=>'Barcode', 'enabled'=>'isModEnabled("barcode")', 'position'=>20, 'visible'=>-1, 'showoncombobox'=>3),
 		'fk_barcode_type' => array('type'=>'integer', 'label'=>'BarcodeType', 'enabled'=>'1', 'position'=>21, 'notnull'=>0, 'visible'=>-1,),
 		'note_public'   =>array('type'=>'html', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>61),
 		'note'          =>array('type'=>'html', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>62),
@@ -913,7 +913,7 @@ class Product extends CommonObject
 	{
 		// phpcs:enable
 		global $conf;
-		if (!empty($conf->barcode->enabled) && !empty($conf->global->BARCODE_PRODUCT_ADDON_NUM)) {
+		if (isModEnabled('barcode') && !empty($conf->global->BARCODE_PRODUCT_ADDON_NUM)) {
 			$module = strtolower($conf->global->BARCODE_PRODUCT_ADDON_NUM);
 
 			$dirsociete = array_merge(array('/core/modules/barcode/'), $conf->modules_parts['barcode']);
@@ -4944,7 +4944,7 @@ class Product extends CommonObject
 				$label .= "<br><b>".$langs->trans("ManageLotSerial").'</b>: '.$this->getLibStatut(0, 2);
 			}
 		}
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			$label .= '<br><b>'.$langs->trans('BarCode').':</b> '.$this->barcode;
 		}
 

+ 1 - 1
htdocs/product/composition/card.php

@@ -174,7 +174,7 @@ if ($action == 'search') {
 			$params[] = 'pl.description';
 			$params[] = 'pl.note';
 		}
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			$params[] = 'p.barcode';
 		}
 		$sql .= natural_search($params, $key);

+ 5 - 5
htdocs/product/fournisseurs.php

@@ -40,7 +40,7 @@ require_once DOL_DOCUMENT_ROOT.'/comm/propal/class/propal.class.php';
 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
 require_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_expression.class.php';
 require_once DOL_DOCUMENT_ROOT.'/product/dynamic_price/class/price_parser.class.php';
-if (!empty($conf->barcode->enabled)) {
+if (isModEnabled('barcode')) {
 	dol_include_once('/core/class/html.formbarcode.class.php');
 }
 // Load translation files required by the page
@@ -745,7 +745,7 @@ END;
 				print '</td></tr>';
 
 				// Barcode
-				if (!empty($conf->barcode->enabled)) {
+				if (isModEnabled('barcode')) {
 					$formbarcode = new FormBarCode($db);
 
 					// Barcode type
@@ -913,10 +913,10 @@ END;
 					'pfp.multicurrency_unitprice'=>array('label'=>$langs->trans("UnitPriceHTCurrency"), 'enabled' => (!empty($conf->multicurrency->enabled)), 'checked'=>0, 'position'=>10),
 					'pfp.delivery_time_days'=>array('label'=>$langs->trans("NbDaysToDelivery"), 'checked'=>1, 'position'=>13),
 					'pfp.supplier_reputation'=>array('label'=>$langs->trans("ReputationForThisProduct"), 'checked'=>1, 'position'=>14),
-					'pfp.fk_barcode_type'=>array('label'=>$langs->trans("BarcodeType"), 'enabled' => !empty($conf->barcode->enabled), 'checked'=>0, 'position'=>15),
-					'pfp.barcode'=>array('label'=>$langs->trans("BarcodeValue"), 'enabled' => !empty($conf->barcode->enabled), 'checked'=>0, 'position'=>16),
+					'pfp.fk_barcode_type'=>array('label'=>$langs->trans("BarcodeType"), 'enabled' => isModEnabled('barcode'), 'checked'=>0, 'position'=>15),
+					'pfp.barcode'=>array('label'=>$langs->trans("BarcodeValue"), 'enabled' => isModEnabled('barcode'), 'checked'=>0, 'position'=>16),
 					'pfp.packaging'=>array('label'=>$langs->trans("PackagingForThisProduct"), 'enabled' => !empty($conf->global->PRODUCT_USE_SUPPLIER_PACKAGING), 'checked'=>0, 'position'=>17),
-					'pfp.tms'=>array('label'=>$langs->trans("DateModification"), 'enabled' => !empty($conf->barcode->enabled), 'checked'=>1, 'position'=>18),
+					'pfp.tms'=>array('label'=>$langs->trans("DateModification"), 'enabled' => isModEnabled('barcode'), 'checked'=>1, 'position'=>18),
 				);
 
 				// fetch optionals attributes and labels

+ 1 - 1
htdocs/product/inventory/inventory.php

@@ -608,7 +608,7 @@ if ($object->id > 0) {
 		if (!empty($conf->use_javascript_ajax)) {
 			if ($permissiontoadd) {
 				// Link to launch scan tool
-				if (!empty($conf->barcode->enabled) || !empty($conf->productbatch->enabled)) {
+				if (isModEnabled('barcode') || !empty($conf->productbatch->enabled)) {
 					print '<a href="'.$_SERVER["PHP_SELF"].'?id='.$object->id.'&action=updatebyscaning" class="marginrightonly paddingright marginleftonly paddingleft">'.img_picto('', 'barcode', 'class="paddingrightonly"').$langs->trans("UpdateByScaning").'</a>';
 				}
 

+ 2 - 2
htdocs/product/list.php

@@ -179,7 +179,7 @@ if (!empty($conf->global->MAIN_MULTILANGS)) {
 	$fieldstosearchall['pl.description'] = 'ProductDescriptionTranslated';
 	$fieldstosearchall['pl.note'] = 'ProductNoteTranslated';
 }
-if (!empty($conf->barcode->enabled)) {
+if (isModEnabled('barcode')) {
 	$fieldstosearchall['p.barcode'] = 'Gencod';
 	$fieldstosearchall['pfp.barcode'] = 'GencodBuyPrice';
 }
@@ -207,7 +207,7 @@ $arrayfields = array(
 	'thumbnail'=>array('label'=>'Photo', 'checked'=>0, 'position'=>10),
 	'p.label'=>array('label'=>"Label", 'checked'=>1, 'position'=>10),
 	'p.fk_product_type'=>array('label'=>"Type", 'checked'=>0, 'enabled'=>(!empty($conf->product->enabled) && !empty($conf->service->enabled)), 'position'=>11),
-	'p.barcode'=>array('label'=>"Gencod", 'checked'=>1, 'enabled'=>(!empty($conf->barcode->enabled)), 'position'=>12),
+	'p.barcode'=>array('label'=>"Gencod", 'checked'=>1, 'enabled'=>(isModEnabled('barcode')), 'position'=>12),
 	'p.duration'=>array('label'=>"Duration", 'checked'=>($contextpage != 'productlist'), 'enabled'=>(!empty($conf->service->enabled) && (string) $type == '1'), 'position'=>13),
 	'p.finished'=>array('label'=>"Nature", 'checked'=>0, 'enabled'=>(!empty($conf->product->enabled) && $type != '1'), 'position'=>19),
 	'p.weight'=>array('label'=>'Weight', 'checked'=>0, 'enabled'=>(!empty($conf->product->enabled) && $type != '1'), 'position'=>20),

+ 10 - 5
htdocs/product/stock/movement_list.php

@@ -942,12 +942,17 @@ if ($action == "transfert") {
 if ((empty($action) || $action == 'list') && $id > 0) {
 	print "<div class=\"tabsAction\">\n";
 
-	if ($user->rights->stock->mouvement->creer) {
-		print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$id.'&action=correction">'.$langs->trans("CorrectStock").'</a>';
-	}
+	$parameters = array();
+	$reshook = $hookmanager->executeHooks('addMoreActionsButtons', $parameters, $object, $action); // Note that $action and $object may have been
+																								   // modified by hook
+	if (empty($reshook)) {
+		if ($user->rights->stock->mouvement->creer) {
+			print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$id.'&action=correction">'.$langs->trans("CorrectStock").'</a>';
+		}
 
-	if ($user->rights->stock->mouvement->creer) {
-		print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$id.'&action=transfert">'.$langs->trans("TransferStock").'</a>';
+		if ($user->rights->stock->mouvement->creer) {
+			print '<a class="butAction" href="'.$_SERVER["PHP_SELF"].'?id='.$id.'&action=transfert">'.$langs->trans("TransferStock").'</a>';
+		}
 	}
 
 	print '</div><br>';

+ 1 - 1
htdocs/product/stock/replenish.php

@@ -467,7 +467,7 @@ if ($usevirtualstock) {
 		$sqlProductionToConsume .= " AND mp5.role IN ('toconsume', 'consummed')";
 		$sqlProductionToConsume .= " AND mm5.status IN (1,2))";
 
-		$sqlProductionToProduce = "(SELECT GREATEST(0, ".$db->ifsql("SUM(".$db->ifsql("mp5.role = 'toproduce'", 'mp5.qty', '- mp5.qty').") IS NULL", "0", "SUM(".$db->ifsql("mp5.role = 'toconsume'", 'mp5.qty', '- mp5.qty').")").") as qty"; // We need the ifsql because if result is 0 for product p.rowid, we must return 0 and not NULL
+		$sqlProductionToProduce = "(SELECT GREATEST(0, ".$db->ifsql("SUM(".$db->ifsql("mp5.role = 'toproduce'", 'mp5.qty', '- mp5.qty').") IS NULL", "0", "SUM(".$db->ifsql("mp5.role = 'toproduce'", 'mp5.qty', '- mp5.qty').")").") as qty"; // We need the ifsql because if result is 0 for product p.rowid, we must return 0 and not NULL
 		$sqlProductionToProduce .= " FROM ".MAIN_DB_PREFIX."mrp_mo as mm5,";
 		$sqlProductionToProduce .= " ".MAIN_DB_PREFIX."mrp_production as mp5";
 		$sqlProductionToProduce .= " WHERE mm5.rowid = mp5.fk_mo AND mm5.entity IN (".getEntity(!empty($conf->global->STOCK_CALCULATE_VIRTUAL_STOCK_TRANSVERSE_MODE) ? 'stock' : 'mo').")";

+ 1 - 1
htdocs/societe/canvas/company/tpl/card_create.tpl.php

@@ -105,7 +105,7 @@ if (empty($conf) || !is_object($conf)) {
 	<?php }
 }
 
-if (!empty($conf->barcode->enabled)) { ?>
+if (isModEnabled('barcode')) { ?>
 <tr>
 	<td><?php echo $langs->trans('Gencod'); ?></td>
 	<td colspan="3"><input type="text" name="barcode" value="<?php echo $this->control->tpl['barcode']; ?>"></td>

+ 1 - 1
htdocs/societe/canvas/company/tpl/card_edit.tpl.php

@@ -120,7 +120,7 @@ if ($this->control->tpl['fournisseur']) {
 	}
 }
 
-if (!empty($conf->barcode->enabled)) { ?>
+if (isModEnabled('barcode')) { ?>
 <tr>
 	<td><?php echo $langs->trans('Gencod'); ?></td>
 	<td colspan="3"><input type="text" name="barcode" value="<?php echo $this->control->tpl['barcode']; ?>"></td>

+ 1 - 1
htdocs/societe/canvas/company/tpl/card_view.tpl.php

@@ -79,7 +79,7 @@ print dol_get_fiche_head($head, 'card', $langs->trans("ThirdParty"), 0, 'company
 </tr>
 <?php } ?>
 
-<?php if (!empty($conf->barcode->enabled)) { ?>
+<?php if (isModEnabled('barcode')) { ?>
 <tr>
 	<td><?php echo $langs->trans('Gencod'); ?></td>
 	<td colspan="3"><?php echo $this->control->tpl['barcode']; ?></td>

+ 1 - 1
htdocs/societe/canvas/individual/tpl/card_create.tpl.php

@@ -113,7 +113,7 @@ if (empty($conf) || !is_object($conf)) {
 	<?php }
 }
 
-if (!empty($conf->barcode->enabled)) { ?>
+if (isModEnabled('barcode')) { ?>
 <tr>
 	<td><?php echo $langs->trans('Gencod'); ?></td>
 	<td colspan="3"><input type="text" name="barcode" value="<?php echo $this->control->tpl['barcode']; ?>"></td>

+ 1 - 1
htdocs/societe/canvas/individual/tpl/card_edit.tpl.php

@@ -117,7 +117,7 @@ if ($this->control->tpl['fournisseur']) {
 }
 ?>
 
-<?php if (!empty($conf->barcode->enabled)) { ?>
+<?php if (isModEnabled('barcode')) { ?>
 <tr>
 	<td><?php echo $langs->trans('Gencod'); ?></td>
 	<td colspan="3"><input type="text" name="barcode" value="<?php echo $this->control->tpl['barcode']; ?>"></td>

+ 1 - 1
htdocs/societe/canvas/individual/tpl/card_view.tpl.php

@@ -74,7 +74,7 @@ if ($this->control->tpl['action_delete']) {
 </tr>
 <?php } ?>
 
-<?php if (!empty($conf->barcode->enabled)) { ?>
+<?php if (isModEnabled('barcode')) { ?>
 <tr>
 	<td><?php echo $langs->trans('Gencod'); ?></td>
 	<td colspan="3"><?php echo $this->control->tpl['barcode']; ?></td>

+ 3 - 3
htdocs/societe/card.php

@@ -1510,7 +1510,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) {
 		print '</td></tr>';
 
 		// Barcode
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			print '<tr><td>'.$form->editfieldkey('Gencod', 'barcode', '', $object, 0).'</td>';
 			print '<td colspan="3">';
 			print img_picto('', 'barcode', 'class="pictofixedwidth"');
@@ -2207,7 +2207,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) {
 			}
 
 			// Barcode
-			if (!empty($conf->barcode->enabled)) {
+			if (isModEnabled('barcode')) {
 				print '<tr><td class="tdtop">'.$form->editfieldkey('Gencod', 'barcode', '', $object, 0).'</td>';
 				print '<td colspan="3">';
 				print img_picto('', 'barcode');
@@ -2687,7 +2687,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) {
 		}
 
 		// Barcode
-		if (!empty($conf->barcode->enabled)) {
+		if (isModEnabled('barcode')) {
 			print '<tr><td>';
 			print $langs->trans('Gencod').'</td><td>'.showValueWithClipboardCPButton(dol_escape_htmltag($object->barcode));
 			print '</td>';

+ 18 - 0
htdocs/societe/class/api_contacts.class.php

@@ -97,6 +97,10 @@ class Contacts extends DolibarrApi
 			$this->contact->fetchRoles();
 		}
 
+		if (isModEnabled('mailing')) {
+			$this->contact->getNoEmail();
+		}
+
 		return $this->_cleanObjectDatas($this->contact);
 	}
 
@@ -141,6 +145,10 @@ class Contacts extends DolibarrApi
 			$this->contact->fetchRoles();
 		}
 
+		if (isModEnabled('mailing')) {
+			$this->contact->getNoEmail();
+		}
+
 		return $this->_cleanObjectDatas($this->contact);
 	}
 
@@ -250,6 +258,9 @@ class Contacts extends DolibarrApi
 					if ($includeroles) {
 						$contact_static->fetchRoles();
 					}
+					if (isModEnabled('mailing')) {
+						$contact_static->getNoEmail();
+					}
 
 					$obj_ret[] = $this->_cleanObjectDatas($contact_static);
 				}
@@ -285,6 +296,9 @@ class Contacts extends DolibarrApi
 		if ($this->contact->create(DolibarrApiAccess::$user) < 0) {
 			throw new RestException(500, "Error creating contact", array_merge(array($this->contact->error), $this->contact->errors));
 		}
+		if (isModEnabled('mailing') && !empty($this->contact->email) && isset($this->contact->no_email)) {
+			$this->contact->setNoEmail($this->contact->no_email);
+		}
 		return $this->contact->id;
 	}
 
@@ -317,6 +331,10 @@ class Contacts extends DolibarrApi
 			$this->contact->$field = $value;
 		}
 
+		if (isModEnabled('mailing') && !empty($this->contact->email) && isset($this->contact->no_email)) {
+			$this->contact->setNoEmail($this->contact->no_email);
+		}
+
 		if ($this->contact->update($id, DolibarrApiAccess::$user, 1, '', '', 'update')) {
 			return $this->get($id);
 		}

+ 1 - 0
htdocs/societe/class/societe.class.php

@@ -1581,6 +1581,7 @@ class Societe extends CommonObject
 							$lmember->phone = $this->phone;
 							$lmember->state_id = $this->state_id;
 							$lmember->country_id = $this->country_id;
+							$lmember->default_lang = $this->default_lang;
 
 							$result = $lmember->update($user, 0, 1, 1, 1); // Use nosync to 1 to avoid cyclic updates
 							if ($result < 0) {

+ 3 - 3
htdocs/societe/list.php

@@ -187,7 +187,7 @@ if (($tmp = $langs->transnoentities("ProfId5".$mysoc->country_code)) && $tmp !=
 if (($tmp = $langs->transnoentities("ProfId6".$mysoc->country_code)) && $tmp != "ProfId6".$mysoc->country_code && $tmp != '-') {
 	$fieldstosearchall['s.idprof6'] = 'ProfId6';
 }
-if (!empty($conf->barcode->enabled)) {
+if (isModEnabled('barcode')) {
 	$fieldstosearchall['s.barcode'] = 'Gencod';
 }
 // Personalized search criterias. Example: $conf->global->THIRDPARTY_QUICKSEARCH_ON_FIELDS = 's.nom=ThirdPartyName;s.name_alias=AliasNameShort;s.code_client=CustomerCode'
@@ -217,7 +217,7 @@ $arrayfields = array(
 	's.rowid'=>array('label'=>"TechnicalID", 'position'=>1, 'checked'=>(!empty($conf->global->MAIN_SHOW_TECHNICAL_ID)), 'enabled'=>(!empty($conf->global->MAIN_SHOW_TECHNICAL_ID))),
 	's.nom'=>array('label'=>"ThirdPartyName", 'position'=>2, 'checked'=>1),
 	's.name_alias'=>array('label'=>"AliasNameShort", 'position'=>3, 'checked'=>1),
-	's.barcode'=>array('label'=>"Gencod", 'position'=>5, 'checked'=>1, 'enabled'=>(!empty($conf->barcode->enabled))),
+	's.barcode'=>array('label'=>"Gencod", 'position'=>5, 'checked'=>1, 'enabled'=>(isModEnabled('barcode'))),
 	's.code_client'=>array('label'=>"CustomerCodeShort", 'position'=>10, 'checked'=>$checkedcustomercode),
 	's.code_fournisseur'=>array('label'=>"SupplierCodeShort", 'position'=>11, 'checked'=>$checkedsuppliercode, 'enabled'=>((!empty($conf->fournisseur->enabled) && empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD)) || !empty($conf->supplier_order->enabled) || !empty($conf->supplier_invoice->enabled))),
 	's.code_compta'=>array('label'=>"CustomerAccountancyCodeShort", 'position'=>13, 'checked'=>$checkedcustomeraccountcode),
@@ -638,7 +638,7 @@ if ($search_type == '0') {
 if ($search_status != '' && $search_status >= 0) {
 	$sql .= natural_search("s.status", $search_status, 2);
 }
-if (!empty($conf->barcode->enabled) && $search_barcode) {
+if (isModEnabled('barcode') && $search_barcode) {
 	$sql .= natural_search("s.barcode", $search_barcode);
 }
 if ($search_price_level && $search_price_level != '-1') {

+ 2 - 2
htdocs/takepos/admin/setup.php

@@ -82,7 +82,7 @@ if ($action == 'set') {
 		$res = dolibarr_set_const($db, "TAKEPOS_SUMUP_AFFILIATE", GETPOST('TAKEPOS_SUMUP_AFFILIATE', 'alpha'), 'chaine', 0, '', $conf->entity);
 		$res = dolibarr_set_const($db, "TAKEPOS_SUMUP_APPID", GETPOST('TAKEPOS_SUMUP_APPID', 'alpha'), 'chaine', 0, '', $conf->entity);
 	}
-	if (!empty($conf->barcode->enabled)) {
+	if (isModEnabled('barcode')) {
 		$res = dolibarr_set_const($db, 'TAKEPOS_BARCODE_RULE_TO_INSERT_PRODUCT', GETPOST('TAKEPOS_BARCODE_RULE_TO_INSERT_PRODUCT', 'alpha'), 'chaine', 0, '', $conf->entity);
 	}
 
@@ -388,7 +388,7 @@ print ajax_constantonoff("TAKEPOS_SHOW_HT", array(), $conf->entity, 0, 0, 1, 0);
 print "</td></tr>\n";
 
 // Barcode rule to insert product
-if (!empty($conf->barcode->enabled)) {
+if (isModEnabled('barcode')) {
 	print '<tr class="oddeven"><td>';
 	print $form->textwithpicto($langs->trans("TakeposBarcodeRuleToInsertProduct"), $langs->trans("TakeposBarcodeRuleToInsertProductDesc"));
 	print '<td colspan="2">';

+ 14 - 7
htdocs/takepos/ajax/ajax.php

@@ -58,11 +58,14 @@ if (empty($user->rights->takepos->run)) {
 // Initialize technical object to manage hooks. Note that conf->hooks_modules contains array of hooks
 $hookmanager->initHooks(array('takeposproductsearch')); // new context for product search hooks
 
+
 /*
  * View
  */
 
 if ($action == 'getProducts') {
+	top_httphead('application/json');
+
 	$object = new Categorie($db);
 	if ($category == "supplements") {
 		$category = getDolGlobalInt('TAKEPOS_SUPPLEMENTS_CATEGORY');
@@ -88,9 +91,11 @@ if ($action == 'getProducts') {
 		}
 		echo json_encode($res);
 	} else {
-		echo 'Failed to load category with id='.$category;
+		echo 'Failed to load category with id='.dol_escape_htmltag($category);
 	}
 } elseif ($action == 'search' && $term != '') {
+	top_httphead('application/json');
+
 	// Change thirdparty with barcode
 	require_once DOL_DOCUMENT_ROOT.'/societe/class/societe.class.php';
 
@@ -123,7 +128,7 @@ if ($action == 'getProducts') {
 	}
 
 	$barcode_rules = getDolGlobalString('TAKEPOS_BARCODE_RULE_TO_INSERT_PRODUCT');
-	if (!empty($conf->barcode->enabled) && !empty($barcode_rules)) {
+	if (isModEnabled('barcode') && !empty($barcode_rules)) {
 		$barcode_rules_list = array();
 
 		// get barcode rules
@@ -329,8 +334,8 @@ if ($action == 'getProducts') {
 	require_once DOL_DOCUMENT_ROOT.'/core/class/dolreceiptprinter.class.php';
 	$printer = new dolReceiptPrinter($db);
 	// check printer for terminal
-	if ($conf->global->{'TAKEPOS_PRINTER_TO_USE'.$term} > 0) {
-		$printer->initPrinter($conf->global->{'TAKEPOS_PRINTER_TO_USE'.$term});
+	if (getDolGlobalInt('TAKEPOS_PRINTER_TO_USE'.$term) > 0) {
+		$printer->initPrinter(getDolGlobalInt('TAKEPOS_PRINTER_TO_USE'.$term));
 		// open cashdrawer
 		$printer->pulse();
 		$printer->close();
@@ -340,12 +345,14 @@ if ($action == 'getProducts') {
 	require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
 	$printer = new dolReceiptPrinter($db);
 	// check printer for terminal
-	if (($conf->global->{'TAKEPOS_PRINTER_TO_USE'.$term} > 0 || $conf->global->TAKEPOS_PRINT_METHOD == "takeposconnector") && $conf->global->{'TAKEPOS_TEMPLATE_TO_USE_FOR_INVOICES'.$term} > 0) {
+	if ((getDolGlobalInt('TAKEPOS_PRINTER_TO_USE'.$term) > 0 || getDolGlobalString('TAKEPOS_PRINT_METHOD') == "takeposconnector") && getDolGlobalInt('TAKEPOS_TEMPLATE_TO_USE_FOR_INVOICES'.$term) > 0) {
 		$object = new Facture($db);
 		$object->fetch($id);
-		$ret = $printer->sendToPrinter($object, $conf->global->{'TAKEPOS_TEMPLATE_TO_USE_FOR_INVOICES'.$term}, $conf->global->{'TAKEPOS_PRINTER_TO_USE'.$term});
+		$ret = $printer->sendToPrinter($object, getDolGlobalString('TAKEPOS_TEMPLATE_TO_USE_FOR_INVOICES'.$term), getDolGlobalString('TAKEPOS_PRINTER_TO_USE'.$term));
 	}
 } elseif ($action == 'getInvoice') {
+	top_httphead('application/json');
+
 	require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
 
 	$object = new Facture($db);
@@ -359,5 +366,5 @@ if ($action == 'getProducts') {
 	require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
 	require_once DOL_DOCUMENT_ROOT.'/core/class/dolreceiptprinter.class.php';
 	$printer = new dolReceiptPrinter($db);
-	$printer->sendToPrinter($object, $conf->global->{'TAKEPOS_TEMPLATE_TO_USE_FOR_INVOICES'.$term}, $conf->global->{'TAKEPOS_PRINTER_TO_USE'.$term});
+	$printer->sendToPrinter($object, getDolGlobalString('TAKEPOS_TEMPLATE_TO_USE_FOR_INVOICES'.$term), getDolGlobalString('TAKEPOS_PRINTER_TO_USE'.$term));
 }

+ 11 - 4
htdocs/takepos/floors.php

@@ -81,6 +81,8 @@ if ($action == "getTables") {
 		}
 		$rows[] = $row;
 	}
+
+	top_httphead('application/json');
 	echo json_encode($rows);
 	exit;
 }
@@ -119,13 +121,18 @@ if ($action == "add") {
  */
 
 // Title
+$head = '';
 $title = 'TakePOS - Dolibarr '.DOL_VERSION;
 if (!empty($conf->global->MAIN_APPLICATION_TITLE)) {
 	$title = 'TakePOS - '.$conf->global->MAIN_APPLICATION_TITLE;
 }
-top_htmlhead($head, $title, $disablejs, $disablehead, $arrayofjs, $arrayofcss);
+$arrayofcss = array('/takepos/css/pos.css.php?a=xxx');
+
+top_htmlhead($head, $title, 0, 0, '', $arrayofcss);
+
 ?>
-<link rel="stylesheet" href="css/pos.css.php?a=xxx">
+<body style="overflow: hidden">
+
 <style type="text/css">
 div.tablediv{
 	background-image:url(img/table.gif);
@@ -205,8 +212,7 @@ $( document ).ready(function() {
 });
 
 </script>
-</head>
-<body style="overflow: hidden">
+
 <?php if ($user->admin) {?>
 <div style="position: absolute; left: 0.1%; top: 0.8%; width:8%; height:11%;">
 	<?php if ($mode == "edit") {?>
@@ -233,5 +239,6 @@ $( document ).ready(function() {
 	</h1>
 	</center>
 </div>
+
 </body>
 </html>

+ 4 - 2
htdocs/takepos/freezone.php

@@ -78,6 +78,7 @@ if ($invoice->socid > 0) {
 }
 $vatRateDefault = get_default_tva($mysoc, $soc);
 
+
 /*
  * View
  */
@@ -87,6 +88,8 @@ $arrayofjs = array();
 
 top_htmlhead($head, '', 0, 0, $arrayofjs, $arrayofcss);
 ?>
+<body>
+
 <script>
 	var vatRate = '<?php echo dol_escape_js($vatRateDefault); ?>';
 
@@ -116,8 +119,7 @@ top_htmlhead($head, '', 0, 0, $arrayofjs, $arrayofcss);
 		$('#desc').focus()
 	});
 </script>
-</head>
-<body>
+
 <br>
 <center>
 <input type="text" id="desc" name="desc" class="takepospay" style="width:40%;" placeholder="<?php echo $langs->trans('Description'); ?>">

+ 7 - 1
htdocs/takepos/genimg/index.php

@@ -15,6 +15,8 @@
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
  */
 
+// This page return an image of a photo
+
 //if (! defined('NOREQUIREUSER'))	define('NOREQUIREUSER','1');	// Not disabled cause need to load personalized language
 //if (! defined('NOREQUIREDB'))		define('NOREQUIREDB','1');		// Not disabled cause need to load personalized language
 if (!defined('NOREQUIRESOC')) {
@@ -74,6 +76,7 @@ if ($query == "cat") {
 		exit;
 	}
 	header('Location: ../../public/theme/common/nophoto.png');
+	exit;
 } elseif ($query == "pro") {
 	require_once DOL_DOCUMENT_ROOT."/product/class/product.class.php";
 
@@ -86,15 +89,18 @@ if ($query == "cat") {
 	$file = array_pop($match);
 	if ($file == "") {
 		header('Location: ../../public/theme/common/nophoto.png');
+		exit;
 	} else {
 		if (!defined('INCLUDE_PHONEPAGE_FROM_PUBLIC_PAGE')) {
 			header('Location: '.$file.'&cache=1');
+			exit;
 		} else {
 			header('Location: '.$file.'&cache=1&publictakepos=1&modulepart=product');
+			exit;
 		}
 	}
 } else {
-	// TODO We don't need this. Size of image must be defined on HTML page, image must NOT be resize when downloaded.
+	// TODO We don't need this. Size of image must be defined on HTML page, image must NOT be resized when downloaded.
 
 	// The file
 	$filename = $query.".jpg";

+ 9 - 0
htdocs/takepos/genimg/qr.php

@@ -15,6 +15,8 @@
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
  */
 
+// This page return an image of a QR code
+
 if (!defined("NOLOGIN")) {
 	define("NOLOGIN", '1'); // If this page is public (can be called outside logged session)
 }
@@ -46,6 +48,13 @@ require '../../core/modules/barcode/doc/tcpdfbarcode.modules.php';
 $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
 $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file
 
+
+/*
+ * View
+ */
+
+// The buildBarCode does not include the http headers but this is a page that just return an image.
+
 if (GETPOSTISSET("key")) {
 	$key = GETPOST('key');
 	$module = new modTcpdfbarcode();

+ 22 - 20
htdocs/takepos/index.php

@@ -97,11 +97,11 @@ $MAXCATEG = (empty($conf->global->TAKEPOS_NB_MAXCATEG) ? $maxcategbydefaultforth
 $MAXPRODUCT = (empty($conf->global->TAKEPOS_NB_MAXPRODUCT) ? $maxproductbydefaultforthisdevice : $conf->global->TAKEPOS_NB_MAXPRODUCT);
 
 /*
-$constforcompanyid = 'CASHDESK_ID_THIRDPARTY'.$_SESSION["takeposterminal"];
-$soc = new Societe($db);
-if ($invoice->socid > 0) $soc->fetch($invoice->socid);
-else $soc->fetch($conf->global->$constforcompanyid);
-*/
+ $constforcompanyid = 'CASHDESK_ID_THIRDPARTY'.$_SESSION["takeposterminal"];
+ $soc = new Societe($db);
+ if ($invoice->socid > 0) $soc->fetch($invoice->socid);
+ else $soc->fetch($conf->global->$constforcompanyid);
+ */
 
 // Security check
 $result = restrictedArea($user, 'takepos', 0, '');
@@ -116,8 +116,13 @@ $form = new Form($db);
 
 $disablejs = 0;
 $disablehead = 0;
-$arrayofjs = array();
-$arrayofcss = array();
+$arrayofjs = array('/takepos/js/jquery.colorbox-min.js'); // TODO It seems we don't need this
+$arrayofcss = array('/takepos/css/pos.css.php', '/takepos/css/colorbox.css');
+
+if (getDolGlobalInt('TAKEPOS_COLOR_THEME') == 1) {
+	$arrayofcss[] =  '/takepos/css/colorful.css';
+}
+
 
 // Title
 $title = 'TakePOS - Dolibarr '.DOL_VERSION;
@@ -130,17 +135,7 @@ $head = '<meta name="apple-mobile-web-app-title" content="TakePOS"/>
 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>';
 top_htmlhead($head, $title, $disablejs, $disablehead, $arrayofjs, $arrayofcss);
 
-?>
-<link rel="stylesheet" href="css/pos.css.php">
-<link rel="stylesheet" href="css/colorbox.css" type="text/css" media="screen" />
-<?php
-if (getDolGlobalInt('TAKEPOS_COLOR_THEME') == 1) {
-	print '<link rel="stylesheet" href="css/colorful.css">';
-}
-?>
-<script type="text/javascript" src="js/jquery.colorbox-min.js"></script>	<!-- TODO It seems we don't need this -->
-<script type="text/javascript">
-<?php
+
 $categories = $categorie->get_full_arbo('product', ((getDolGlobalInt('TAKEPOS_ROOT_CATEGORY_ID') > 0) ? getDolGlobalInt('TAKEPOS_ROOT_CATEGORY_ID') : 0), 1);
 
 
@@ -172,6 +167,9 @@ $maincategories = dol_sort_array($maincategories, 'label');
 $subcategories = dol_sort_array($subcategories, 'label');
 ?>
 
+<body class="bodytakepos" style="overflow: hidden;">
+
+<script>
 var categories = <?php echo json_encode($maincategories); ?>;
 var subcategories = <?php echo json_encode($subcategories); ?>;
 
@@ -374,7 +372,11 @@ function LoadProducts(position, issubcat) {
 					$("#proprice"+ishow).attr("class", "productprice");
 					$("#proprice"+ishow).html(data[parseInt(idata)]['price_formated']);
 				}
+				console.log("#prodiv"+ishow+".data(rowid)="+data[idata]['id']);
+				console.log($("#prodiv"+ishow));
+
 				$("#prodiv"+ishow).data("rowid", data[idata]['id']);
+				console.log($('#prodiv4').data('rowid'));
 				$("#prodiv"+ishow).data("iscat", 0);
 				$("#prodiv"+ishow).attr("class","wrapper2");
 				$("#prowatermark"+ishow).hide();
@@ -464,7 +466,7 @@ function MoreProducts(moreorless) {
 }
 
 function ClickProduct(position, qty = 1) {
-	console.log("ClickProduct");
+	console.log("ClickProduct at position"+position);
 	$('#proimg'+position).animate({opacity: '0.5'}, 1);
 	$('#proimg'+position).animate({opacity: '1'}, 100);
 	if ($('#prodiv'+position).data('iscat')==1){
@@ -472,6 +474,7 @@ function ClickProduct(position, qty = 1) {
 		LoadProducts(position, true);
 	}
 	else{
+		console.log($('#prodiv4').data('rowid'));
 		idproduct=$('#prodiv'+position).data('rowid');
 		console.log("Click on product at position "+position+" for idproduct "+idproduct+", qty="+qty);
 		if (idproduct=="") return;
@@ -939,7 +942,6 @@ $( document ).ready(function() {
 });
 </script>
 
-<body class="bodytakepos" style="overflow: hidden;">
 <?php
 $keyCodeForEnter = getDolGlobalInt('CASHDESK_READER_KEYCODE_FOR_ENTER'.$_SESSION['takeposterminal']) > 0 ? getDolGlobalInt('CASHDESK_READER_KEYCODE_FOR_ENTER'.$_SESSION['takeposterminal']) : '';
 ?>

+ 31 - 21
htdocs/takepos/invoice.php

@@ -69,11 +69,6 @@ if (empty($user->rights->takepos->run) && !defined('INCLUDE_PHONEPAGE_FROM_PUBLI
 	accessforbidden();
 }
 
-
-/*
- * View
- */
-
 if ((getDolGlobalString('TAKEPOS_PHONE_BASIC_LAYOUT') == 1 && $conf->browser->layout == 'phone') || defined('INCLUDE_PHONEPAGE_FROM_PUBLIC_PAGE')) {
 	// DIRECT LINK TO THIS PAGE FROM MOBILE AND NO TERMINAL SELECTED
 	if ($_SESSION["takeposterminal"] == "") {
@@ -84,24 +79,9 @@ if ((getDolGlobalString('TAKEPOS_PHONE_BASIC_LAYOUT') == 1 && $conf->browser->la
 			exit;
 		}
 	}
-	$title = 'TakePOS - Dolibarr '.DOL_VERSION;
-	if (!empty($conf->global->MAIN_APPLICATION_TITLE)) {
-		$title = 'TakePOS - '.$conf->global->MAIN_APPLICATION_TITLE;
-	}
-	$head = '<meta name="apple-mobile-web-app-title" content="TakePOS"/>
-	<meta name="apple-mobile-web-app-capable" content="yes">
-	<meta name="mobile-web-app-capable" content="yes">
-	<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>';
-	$arrayofcss = array(
-	'/takepos/css/pos.css.php',
-	'/takepos/js/jquery.colorbox-min.js'
-	);
-	$arrayofjs = array('/takepos/js/jquery.colorbox-min.js');
-	$disablejs = 0;
-	$disablehead = 0;
-	top_htmlhead($head, $title, $disablejs, $disablehead, $arrayofjs, $arrayofcss);
 }
 
+
 /**
  * Abort invoice creationg with a given error message
  *
@@ -955,7 +935,31 @@ if (empty($reshook)) {
 
 $form = new Form($db);
 
+// llxHeader
+if ((getDolGlobalString('TAKEPOS_PHONE_BASIC_LAYOUT') == 1 && $conf->browser->layout == 'phone') || defined('INCLUDE_PHONEPAGE_FROM_PUBLIC_PAGE')) {
+	$title = 'TakePOS - Dolibarr '.DOL_VERSION;
+	if (!empty($conf->global->MAIN_APPLICATION_TITLE)) {
+		$title = 'TakePOS - '.$conf->global->MAIN_APPLICATION_TITLE;
+	}
+	$head = '<meta name="apple-mobile-web-app-title" content="TakePOS"/>
+	<meta name="apple-mobile-web-app-capable" content="yes">
+	<meta name="mobile-web-app-capable" content="yes">
+	<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>';
+	$arrayofcss = array(
+		'/takepos/css/pos.css.php',
+	);
+	$arrayofjs = array('/takepos/js/jquery.colorbox-min.js');
+	$disablejs = 0;
+	$disablehead = 0;
+	top_htmlhead($head, $title, $disablejs, $disablehead, $arrayofjs, $arrayofcss);
+
+	print '<body>'."\n";
+} else {
+	top_httphead('text/html', 1);
+}
+
 ?>
+<!-- invoice.php -->
 <script type="text/javascript">
 var selectedline=0;
 var selectedtext="";
@@ -1688,3 +1692,9 @@ if ($action == "search") {
 }
 
 print '</div>';
+
+// llxFooter
+if ((getDolGlobalString('TAKEPOS_PHONE_BASIC_LAYOUT') == 1 && $conf->browser->layout == 'phone') || defined('INCLUDE_PHONEPAGE_FROM_PUBLIC_PAGE')) {
+	print '</body></html>';
+}
+

+ 29 - 24
htdocs/takepos/pay.php

@@ -1,6 +1,6 @@
 <?php
-/* Copyright (C) 2018	Andreu Bisquerra	<jove@bisquerra.com>
- * Copyright (C) 2021	Thibault FOUCART	<support@ptibogxiv.net>
+/* Copyright (C) 2018		Andreu Bisquerra	<jove@bisquerra.com>
+ * Copyright (C) 2021-2022	Thibault FOUCART	<support@ptibogxiv.net>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -42,6 +42,7 @@ if (!defined('NOREQUIREHTML')) {
 require '../main.inc.php'; // Load $user and permissions
 require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture.class.php';
 require_once DOL_DOCUMENT_ROOT.'/stripe/class/stripe.class.php';
+require_once DOL_DOCUMENT_ROOT.'/core/class/hookmanager.class.php';
 
 $langs->loadLangs(array("main", "bills", "cashdesk", "banks"));
 
@@ -49,10 +50,36 @@ $place = (GETPOST('place', 'aZ09') ? GETPOST('place', 'aZ09') : '0'); // $place
 
 $invoiceid = GETPOST('invoiceid', 'int');
 
+$hookmanager->initHooks(array('takepospay'));
+
 if (empty($user->rights->takepos->run)) {
 	accessforbidden();
 }
 
+
+/*
+ * View
+ */
+
+$arrayofcss = array('/takepos/css/pos.css.php');
+$arrayofjs = array();
+
+$head = '';
+$title = '';
+$disablejs = 0;
+$disablehead = 0;
+
+$head='<link rel="stylesheet" href="css/pos.css.php">';
+if (getDolGlobalInt('TAKEPOS_COLOR_THEME') == 1) {
+	$head .= '<link rel="stylesheet" href="css/colorful.css">';
+}
+
+top_htmlhead($head, $title, $disablejs, $disablehead, $arrayofjs, $arrayofcss);
+
+?>
+<body>
+<?php
+
 if (!empty($conf->stripe->enabled)) {
 	$service = 'StripeTest';
 	$servicestatus = 0;
@@ -99,10 +126,6 @@ function fetchConnectionToken() {
 </script>
 <?php }
 
-/*
- * View
- */
-
 if (!empty($conf->stripe->enabled) && isset($keyforstripeterminalbank) && (empty($conf->global->STRIPE_LIVE) || GETPOST('forcesandbox', 'alpha'))) {
 	dol_htmloutput_mesg($langs->trans('YouAreCurrentlyInSandboxMode', 'Stripe'), '', 'warning', 1);
 }
@@ -175,16 +198,6 @@ if ($invoiceid > 0) {
 </script>
 <?php
 
-$arrayofcss = array('/takepos/css/pos.css.php');
-$arrayofjs = array();
-
-$head = '';
-$title = '';
-$disablejs = 0;
-$disablehead = 0;
-
-top_htmlhead($head, $title, $disablejs, $disablehead, $arrayofjs, $arrayofcss);
-
 // Define list of possible payments
 $arrayOfValidPaymentModes = array();
 $arrayOfValidBankAccount = array();
@@ -219,14 +232,6 @@ if ($resql) {
 	}
 }
 ?>
-<link rel="stylesheet" href="css/pos.css.php">
-<?php
-if ($conf->global->TAKEPOS_COLOR_THEME == 1) {
-	print '<link rel="stylesheet" href="css/colorful.css">';
-}
-?>
-</head>
-<body>
 
 <script>
 <?php

+ 17 - 11
htdocs/takepos/phone.php

@@ -69,10 +69,27 @@ if (empty($user->rights->takepos->run) && !defined('INCLUDE_PHONEPAGE_FROM_PUBLI
 	accessforbidden();
 }
 
+
 /*
  * View
  */
 
+if (empty($action)) {
+	// Code for llxHeader()
+	$title = 'TakePOS - Dolibarr '.DOL_VERSION;
+	if (!empty($conf->global->MAIN_APPLICATION_TITLE)) {
+		$title = 'TakePOS - '.$conf->global->MAIN_APPLICATION_TITLE;
+	}
+	$head = '<meta name="apple-mobile-web-app-title" content="TakePOS"/>
+	<meta name="apple-mobile-web-app-capable" content="yes">
+	<meta name="mobile-web-app-capable" content="yes">
+	<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>';
+	$arrayofcss = array('/takepos/css/phone.css');
+	top_htmlhead($head, $title, 0, 0, '', '');
+} else {
+	top_httphead('text/html', 1);
+}
+
 if ($action == "productinfo") {
 	$prod = new Product($db);
 	$prod->fetch($idproduct);
@@ -139,17 +156,6 @@ if ($action == "productinfo") {
 		}
 	}
 } else {
-	// Title
-	$title = 'TakePOS - Dolibarr '.DOL_VERSION;
-	if (!empty($conf->global->MAIN_APPLICATION_TITLE)) {
-		$title = 'TakePOS - '.$conf->global->MAIN_APPLICATION_TITLE;
-	}
-	$head = '<meta name="apple-mobile-web-app-title" content="TakePOS"/>
-<meta name="apple-mobile-web-app-capable" content="yes">
-<meta name="mobile-web-app-capable" content="yes">
-<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>';
-	$arrayofcss = array('/takepos/css/phone.css');
-	top_htmlhead($head, $title, $disablejs, $disablehead, $arrayofjs, $arrayofcss);
 	?>
 <script type="text/javascript">
 	<?php

+ 3 - 2
htdocs/takepos/printbox.php

@@ -67,7 +67,9 @@ $arrayofjs = array('/takepos/js/jquery.colorbox-min.js');
 
 $head = '';
 top_htmlhead($head, '', 0, 0, $arrayofjs, $arrayofcss);
+
 ?>
+<body>
 <script>
 	/**
 	 * Save (validate)
@@ -82,8 +84,7 @@ top_htmlhead($head, '', 0, 0, $arrayofjs, $arrayofcss);
 		$('#label').focus()
 	});
 </script>
-</head>
-<body>
+
 <br>
 <center>
 <?php

+ 1 - 2
htdocs/takepos/receipt.php

@@ -66,7 +66,7 @@ if (empty($user->rights->takepos->run)) {
  * View
  */
 
-top_httphead('text/html');
+top_httphead('text/html', 1);
 
 if ($place > 0) {
 	$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."facture where ref='(PROV-POS".$db->escape($_SESSION["takeposterminal"]."-".$place).")'";
@@ -90,7 +90,6 @@ if (!empty($hookmanager->resPrint)) {
 
 // IMPORTANT: This file is sended to 'Takepos Printing' application. Keep basic file. No external files as css, js... If you need images use absolute path.
 ?>
-<html>
 <body>
 <style>
 .right {

+ 1 - 0
htdocs/takepos/reduction.php

@@ -74,6 +74,7 @@ if ($invoiceid > 0) {
 	}
 }
 
+$head = '';
 $arrayofcss = array('/takepos/css/pos.css.php');
 $arrayofjs  = array();
 

+ 13 - 2
htdocs/takepos/send.php

@@ -61,6 +61,11 @@ $invoice->fetch($facid);
 $customer = new Societe($db);
 $customer->fetch($invoice->socid);
 
+
+/*
+ * Actions
+ */
+
 if ($action == "send") {
 	include_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
 	include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php';
@@ -86,11 +91,17 @@ if ($action == "send") {
 	}
 	exit;
 }
+
+
+/*
+ * View
+ */
+
 $arrayofcss = array('/takepos/css/pos.css.php');
 $arrayofjs  = array();
-top_htmlhead($head, $title, $disablejs, $disablehead, $arrayofjs, $arrayofcss);
+top_htmlhead($head, '', 0, 0, $arrayofjs, $arrayofcss);
+
 ?>
-</head>
 <body class="center">
 
 <script>

+ 7 - 0
htdocs/takepos/smpcb.php

@@ -47,6 +47,13 @@ if (GETPOSTISSET('status')) {
 	die(strtoupper($_SESSION['SMP_CURRENT_PAYMENT']));
 }
 
+
+/*
+ * View
+ */
+
+top_httphead('text/html', 1);
+
 if (GETPOST('smp-status')) {
 	print '<html lang="en">';
 	print '<head>';

+ 8 - 8
htdocs/takepos/split.php

@@ -53,6 +53,11 @@ if (empty($user->rights->takepos->run)) {
 	accessforbidden();
 }
 
+
+/*
+ * Actions
+ */
+
 if ($action=="split") {
 	$line = GETPOST('line', 'int');
 	$split = GETPOST('split', 'int');
@@ -139,6 +144,9 @@ if ($invoiceid > 0) {
 }
 
 $arrayofcss = array('/takepos/css/pos.css.php');
+if (getDolGlobalInt('TAKEPOS_COLOR_THEME') == 1) {
+	$arrayofcss[] = '/takepos/css/colorful.css';
+}
 $arrayofjs = array();
 
 $head = '';
@@ -152,15 +160,7 @@ top_htmlhead($head, $title, $disablejs, $disablehead, $arrayofjs, $arrayofcss);
 $arrayOfValidPaymentModes = array();
 $arrayOfValidBankAccount = array();
 
-
-?>
-<link rel="stylesheet" href="css/pos.css.php">
-<?php
-if ($conf->global->TAKEPOS_COLOR_THEME == 1) {
-	print '<link rel="stylesheet" href="css/colorful.css">';
-}
 ?>
-</head>
 <body class="takepossplitphp">
 
 <script>

+ 1 - 1
htdocs/website/index.php

@@ -2745,7 +2745,7 @@ if (!GETPOST('hide_websitemenu')) {
 			if ($website->status == $website::STATUS_VALIDATED) {
 				print '<input type="submit" class="buttonDelete bordertransp" name="deletesite" value="'.$langs->trans("Delete").'" disabled="disabled" title="'.dol_escape_htmltag($langs->trans("WebsiteMustBeDisabled")).'">';
 			} else {
-				print '<input type="submit" class="buttonDelete bordertransp" name="deletesite" value="'.$langs->trans("Delete").'"'.($atleastonepage ? ' disabled="disabled"' : '').'>';
+				print '<input type="submit" class="buttonDelete bordertransp" name="deletesite" value="'.$langs->trans("Delete").'">';
 			}
 
 			// Regenerate all pages