diff --git a/htdocs/accountancy/admin/index.php b/htdocs/accountancy/admin/index.php index 68b92a922ea..17b9583f36e 100644 --- a/htdocs/accountancy/admin/index.php +++ b/htdocs/accountancy/admin/index.php @@ -68,7 +68,17 @@ $formSetup = new FormSetup($db); // Main options $formSetup->newItem('BANK_DISABLE_DIRECT_INPUT')->setAsYesNo(); -$formSetup->newItem('ACCOUNTANCY_COMBO_FOR_AUX')->setAsYesNo(); +// Auxiliary account select mode +$arrval = array( + (string) 0 => $langs->trans("No").' - '.$langs->trans("FreeInput").'', + (string) 1 => $langs->trans("Yes").' - '.$langs->trans("DropDownList").'', + (string) 2 => $langs->trans("Yes").' - '.$langs->trans("NumberOfKeyToSearch", 1).'', + (string) 3 => $langs->trans("Yes").' - '.$langs->trans("NumberOfKeyToSearch", 2).'', + (string) 4 => $langs->trans("Yes").' - '.$langs->trans("NumberOfKeyToSearch", 3).'', +); +$item = $formSetup->newItem('ACCOUNTANCY_AUXACCOUNT_USE_SEARCH_TO_SELECT'); +$item->setAsSelect($arrval); +$item->helpText = $langs->trans('UseSearchToSelectAuxAccountTooltip'); $item = $formSetup->newItem('ACCOUNTING_MANAGE_ZERO')->setAsYesNo(); $item->helpText = $langs->trans('ACCOUNTING_MANAGE_ZERO2'); diff --git a/htdocs/accountancy/admin/template/card.php b/htdocs/accountancy/admin/template/card.php index d6bd5e6c8f6..fb27fefa6a7 100644 --- a/htdocs/accountancy/admin/template/card.php +++ b/htdocs/accountancy/admin/template/card.php @@ -576,11 +576,7 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea // It does not use the setup of "key pressed" to select a thirdparty and this hang browser on large databases. // Also, it is not possible to use a value that is not in the list. // Also, the label is not automatically filled when a value is selected. - if (getDolGlobalString('ACCOUNTANCY_COMBO_FOR_AUX')) { - print $formaccounting->select_auxaccount((GETPOSTISSET("subledger_account") ? GETPOST("subledger_account", "alpha") : $line->subledger_account), 'subledger_account', 1, 'maxwidth250', '', 'subledger_label'); - } else { - print 'subledger_account).'" placeholder="'.dol_escape_htmltag($langs->trans("SubledgerAccount")).'">'; - } + print $formaccounting->select_auxaccount((GETPOSTISSET("subledger_account") ? GETPOST("subledger_account", "alpha") : $line->subledger_account), 'subledger_account', 1, 'maxwidth250', '', 'subledger_label'); // Add also input for subledger label print '
subledger_label).'" placeholder="'.dol_escape_htmltag($langs->trans("SubledgerAccountLabel")).'">'; print ''; @@ -620,15 +616,7 @@ if ($object->id > 0 && (empty($action) || ($action != 'edit' && $action != 'crea print $formaccounting->select_account('', 'general_account', 1, [], 1, 1, 'maxwidth300'); print ''; print ''; - // TODO For the moment we keep a free input text instead of a combo. The select_auxaccount has problem because: - // It does not use the setup of "key pressed" to select a thirdparty and this hang browser on large databases. - // Also, it is not possible to use a value that is not in the list. - // Also, the label is not automatically filled when a value is selected. - if (getDolGlobalString('ACCOUNTANCY_COMBO_FOR_AUX')) { - print $formaccounting->select_auxaccount('', 'subledger_account', 1, 'maxwidth250', '', 'subledger_label'); - } else { - print ''; - } + print $formaccounting->select_auxaccount('', 'subledger_account', 1, 'maxwidth250', '', 'subledger_label'); print '
'; print ''; print ''; diff --git a/htdocs/accountancy/bookkeeping/balance.php b/htdocs/accountancy/bookkeeping/balance.php index a91b5973aa2..f7aa22e0954 100644 --- a/htdocs/accountancy/bookkeeping/balance.php +++ b/htdocs/accountancy/bookkeeping/balance.php @@ -417,21 +417,13 @@ if ($action != 'export') { // Accountancy account $moreforfilter .= $langs->trans('AccountAccounting').': '; if ($type == 'sub') { - if (getDolGlobalString('ACCOUNTANCY_COMBO_FOR_AUX')) { - $moreforfilter .= $formaccounting->select_auxaccount($search_accountancy_code_start, 'search_accountancy_code_start', $langs->trans('From'), 'maxwidth200'); - } else { - $moreforfilter .= ''; - } + $moreforfilter .= $formaccounting->select_auxaccount($search_accountancy_code_start, 'search_accountancy_code_start', $langs->trans('From'), 'maxwidth200'); } else { $moreforfilter .= $formaccounting->select_account($search_accountancy_code_start, 'search_accountancy_code_start', $langs->trans('From'), array(), 1, 1, 'maxwidth200', 'accounts'); } $moreforfilter .= ' '; if ($type == 'sub') { - if (getDolGlobalString('ACCOUNTANCY_COMBO_FOR_AUX')) { - $moreforfilter .= $formaccounting->select_auxaccount($search_accountancy_code_end, 'search_accountancy_code_end', $langs->trans('to'), 'maxwidth200'); - } else { - $moreforfilter .= ''; - } + $moreforfilter .= $formaccounting->select_auxaccount($search_accountancy_code_end, 'search_accountancy_code_end', $langs->trans('to'), 'maxwidth200'); } else { $moreforfilter .= $formaccounting->select_account($search_accountancy_code_end, 'search_accountancy_code_end', $langs->trans('to'), array(), 1, 1, 'maxwidth200', 'accounts'); } diff --git a/htdocs/accountancy/bookkeeping/card.php b/htdocs/accountancy/bookkeeping/card.php index 3936dc3e824..635f9e540cb 100644 --- a/htdocs/accountancy/bookkeeping/card.php +++ b/htdocs/accountancy/bookkeeping/card.php @@ -1175,15 +1175,7 @@ if ($action == 'create') { print $formaccounting->select_account((GETPOSTISSET("accountingaccount_number") ? GETPOST("accountingaccount_number", "alpha") : $line->numero_compte), 'accountingaccount_number', 1, array(), 1, 1, 'minwidth200 maxwidth500'); print ''; print ''; - // TODO For the moment we keep a free input text instead of a combo. The select_auxaccount has problem because: - // - It does not use the setup of "key pressed" to select a thirdparty and this hang browser on large databases. - // - Also, it is not possible to use a value that is not in the list. - // - Also, the label is not automatically filled when a value is selected. - if (getDolGlobalString('ACCOUNTANCY_COMBO_FOR_AUX')) { - print $formaccounting->select_auxaccount((GETPOSTISSET("subledger_account") ? GETPOST("subledger_account", "alpha") : $line->subledger_account), 'subledger_account', 1, 'maxwidth250', '', 'subledger_label'); - } else { - print 'subledger_account).'" placeholder="'.dol_escape_htmltag($langs->trans("SubledgerAccount")).'">'; - } + print $formaccounting->select_auxaccount((GETPOSTISSET("subledger_account") ? GETPOST("subledger_account", "alpha") : $line->subledger_account), 'subledger_account', 1, 'maxwidth250', '', 'subledger_label'); // Add also input for subledger label print '
subledger_label).'" placeholder="'.dol_escape_htmltag($langs->trans("SubledgerAccountLabel")).'">'; print ''; @@ -1203,15 +1195,7 @@ if ($action == 'create') { print $formaccounting->select_account($action == 'add' ? GETPOST('accountingaccount_number') : '', 'accountingaccount_number', 1, array(), 1, 1, 'minwidth200 maxwidth500'); print ''; print ''; - // TODO For the moment we keep a free input text instead of a combo. The select_auxaccount has problem because: - // It does not use the setup of "key pressed" to select a thirdparty and this hang browser on large databases. - // Also, it is not possible to use a value that is not in the list. - // Also, the label is not automatically filled when a value is selected. - if (getDolGlobalString('ACCOUNTANCY_COMBO_FOR_AUX')) { - print $formaccounting->select_auxaccount('', 'subledger_account', 1, 'maxwidth250', '', 'subledger_label'); - } else { - print ''; - } + print $formaccounting->select_auxaccount('', 'subledger_account', 1, 'maxwidth250', '', 'subledger_label'); print '
'; print ''; print ''; diff --git a/htdocs/accountancy/bookkeeping/export.php b/htdocs/accountancy/bookkeeping/export.php index 63f82b1137f..65b605e3a3e 100644 --- a/htdocs/accountancy/bookkeeping/export.php +++ b/htdocs/accountancy/bookkeeping/export.php @@ -1045,19 +1045,12 @@ if (!empty($arrayfields['t.numero_compte']['checked'])) { // Subledger account if (!empty($arrayfields['t.subledger_account']['checked'])) { print ''; - // TODO For the moment we keep a free input text instead of a combo. The select_auxaccount has problem because it does not - // use setup of "keypress to select thirdparty" and this hangs browser on large databases. - if (getDolGlobalString('ACCOUNTANCY_COMBO_FOR_AUX')) { - print '
'; - //print $langs->trans('From').' '; - print $formaccounting->select_auxaccount($search_accountancy_aux_code_start, 'search_accountancy_aux_code_start', $langs->trans('From'), 'maxwidth150', 'subledgeraccount'); - print '
'; - print '
'; - print $formaccounting->select_auxaccount($search_accountancy_aux_code_end, 'search_accountancy_aux_code_end', $langs->trans('to'), 'maxwidth150', 'subledgeraccount'); - print '
'; - } else { - print ''; - } + print '
'; + print $formaccounting->select_auxaccount($search_accountancy_aux_code_start, 'search_accountancy_aux_code_start', $langs->trans('From'), 'maxwidth150', 'subledgeraccount'); + print '
'; + print '
'; + print $formaccounting->select_auxaccount($search_accountancy_aux_code_end, 'search_accountancy_aux_code_end', $langs->trans('to'), 'maxwidth150', 'subledgeraccount'); + print '
'; print ''; } // Label operation diff --git a/htdocs/accountancy/bookkeeping/list.php b/htdocs/accountancy/bookkeeping/list.php index d00d2ca7a39..65edf8a050e 100644 --- a/htdocs/accountancy/bookkeeping/list.php +++ b/htdocs/accountancy/bookkeeping/list.php @@ -498,7 +498,7 @@ if (empty($reshook)) { $nb_lettering = $lettering->bookkeepingLetteringAll($toselect, true); if ($nb_lettering < 0) { setEventMessages('', $lettering->errors, 'errors'); - $error++; + $error += 1; } } @@ -513,16 +513,16 @@ if (empty($reshook)) { $nbok++; } else { setEventMessages($object->error, $object->errors, 'errors'); - $error++; + $error += 1; break; } } elseif ($result < 0) { setEventMessages($object->error, $object->errors, 'errors'); - $error++; + $error += 1; break; } elseif (isset($object->date_validation) && $object->date_validation != '') { setEventMessages($langs->trans("ValidatedRecordWhereFound"), null, 'errors'); - $error++; + $error += 1; break; } } @@ -551,7 +551,7 @@ if (empty($reshook)) { if (!$error && $action == 'clonebookkeepingwriting' && $confirm == "yes" && $user->hasRight('accounting', 'mouvements', 'creer')) { $result = $object->newCloneMass($toselect, $journal_code, $massdate); if ($result == -1) { - $error++; + $error += 1; } if ($error) { $db->commit(); @@ -566,7 +566,7 @@ if (empty($reshook)) { if (!$error && $action == 'assignaccountbookkeepingwriting' && $confirm == "yes" && $user->hasRight('accounting', 'mouvements', 'creer')) { $result = $object->assignAccountMass($toselect, (int) $account); if ($result == -1) { - $error++; + $error += 1; } if (!$error) { $db->commit(); @@ -581,7 +581,7 @@ if (empty($reshook)) { if (!$error && $action == 'returnaccountbookkeepingwriting' && $confirm == "yes" && $user->hasRight('accounting', 'mouvements', 'creer')) { $result = $object->newReturnAccount($toselect, $journal_code, $massdate); if ($result == -1) { - $error++; + $error += 1; } if (!$error) { $db->commit(); @@ -599,7 +599,7 @@ if (empty($reshook)) { $nb_lettering = $lettering->bookkeepingLetteringAll($toselect); if ($nb_lettering < 0) { setEventMessages('', $lettering->errors, 'errors'); - $error++; + $error += 1; $nb_lettering = max(0, abs($nb_lettering) - 2); } elseif ($nb_lettering == 0) { $nb_lettering = 0; @@ -630,7 +630,7 @@ if (empty($reshook)) { $nb_lettering = $lettering->bookkeepingLetteringAll($toselect, true); if ($nb_lettering < 0) { setEventMessages('', $lettering->errors, 'errors'); - $error++; + $error += 1; $nb_lettering = max(0, abs($nb_lettering) - 2); } elseif ($nb_lettering == 0) { $nb_lettering = 0; @@ -1026,19 +1026,12 @@ if (!empty($arrayfields['t.numero_compte']['checked'])) { // Subledger account if (!empty($arrayfields['t.subledger_account']['checked'])) { print ''; - // TODO For the moment we keep a free input text instead of a combo. The select_auxaccount has problem because it does not - // use setup of keypress to select thirdparty and this hang browser on large database. - if (getDolGlobalString('ACCOUNTANCY_COMBO_FOR_AUX')) { - print '
'; - //print $langs->trans('From').' '; - print $formaccounting->select_auxaccount($search_accountancy_aux_code_start, 'search_accountancy_aux_code_start', $langs->trans('From'), 'maxwidth250', 'subledgeraccount'); - print '
'; - print '
'; - print $formaccounting->select_auxaccount($search_accountancy_aux_code_end, 'search_accountancy_aux_code_end', $langs->trans('to'), 'maxwidth250', 'subledgeraccount'); - print '
'; - } else { - print ''; - } + print '
'; + print $formaccounting->select_auxaccount($search_accountancy_aux_code_start, 'search_accountancy_aux_code_start', $langs->trans('From'), 'maxwidth250', 'subledgeraccount'); + print '
'; + print '
'; + print $formaccounting->select_auxaccount($search_accountancy_aux_code_end, 'search_accountancy_aux_code_end', $langs->trans('to'), 'maxwidth250', 'subledgeraccount'); + print '
'; print ''; } // Label operation diff --git a/htdocs/accountancy/bookkeeping/listbyaccount.php b/htdocs/accountancy/bookkeeping/listbyaccount.php index 3f3ae99c1f0..4827dedec49 100644 --- a/htdocs/accountancy/bookkeeping/listbyaccount.php +++ b/htdocs/accountancy/bookkeeping/listbyaccount.php @@ -494,6 +494,7 @@ if (empty($reshook)) { $uploaddir = $conf->societe->dir_output; global $error; + /** @var int $error */ include DOL_DOCUMENT_ROOT.'/core/actions_massactions.inc.php'; if (!$error && $action == 'deletebookkeepingwriting' && $confirm == "yes" && $user->hasRight('accounting', 'mouvements', 'supprimer')) { @@ -504,7 +505,7 @@ if (empty($reshook)) { $nb_lettering = $lettering->bookkeepingLetteringAll($toselect, true); if ($nb_lettering < 0) { setEventMessages('', $lettering->errors, 'errors'); - $error++; + $error += 1; } } @@ -518,16 +519,16 @@ if (empty($reshook)) { $nbok++; } else { setEventMessages($object->error, $object->errors, 'errors'); - $error++; + $error += 1; break; } } elseif ($result < 0) { setEventMessages($object->error, $object->errors, 'errors'); - $error++; + $error += 1; break; } elseif (isset($object->date_validation) && $object->date_validation != '') { setEventMessages($langs->trans("ValidatedRecordWhereFound"), null, 'errors'); - $error++; + $error += 1; break; } } @@ -556,7 +557,7 @@ if (empty($reshook)) { if (!$error && $action == 'clonebookkeepingwriting' && $confirm == "yes" && $user->hasRight('accounting', 'mouvements', 'creer')) { $result = $object->newCloneMass($toselect, $journal_code, $massdate); if ($result == -1) { - $error++; + $error += 1; } if ($error) { $db->commit(); @@ -571,7 +572,7 @@ if (empty($reshook)) { if (!$error && $action == 'assignaccountbookkeepingwriting' && $confirm == "yes" && $user->hasRight('accounting', 'mouvements', 'creer')) { $result = $object->assignAccountMass($toselect, (int) $account); if ($result == -1) { - $error++; + $error += 1; } if (!$error) { $db->commit(); @@ -586,7 +587,7 @@ if (empty($reshook)) { if (!$error && $action == 'returnaccountbookkeepingwriting' && $confirm == "yes" && $user->hasRight('accounting', 'mouvements', 'creer')) { $result = $object->newReturnAccount($toselect, $journal_code, $massdate); if ($result == -1) { - $error++; + $error += 1; } if (!$error) { $db->commit(); @@ -604,7 +605,7 @@ if (empty($reshook)) { $nb_lettering = $lettering->bookkeepingLetteringAll($toselect); if ($nb_lettering < 0) { setEventMessages('', $lettering->errors, 'errors'); - $error++; + $error += 1; $nb_lettering = max(0, abs($nb_lettering) - 2); } elseif ($nb_lettering == 0) { $nb_lettering = 0; @@ -645,7 +646,7 @@ if (empty($reshook)) { $nb_lettering = $lettering->bookkeepingLetteringAll($toselect, true); if ($nb_lettering < 0) { setEventMessages('', $lettering->errors, 'errors'); - $error++; + $error += 1; $nb_lettering = max(0, abs($nb_lettering) - 2); } elseif ($nb_lettering == 0) { $nb_lettering = 0; @@ -817,7 +818,7 @@ if (!getDolGlobalInt('MAIN_DISABLE_FULL_SCANLIST')) { if ($nbtotalofrecords < 0) { setEventMessages($object->error, $object->errors, 'errors'); - $error++; + $error += 1; } } @@ -995,21 +996,13 @@ $moreforfilter .= '
'; $moreforfilter .= $langs->trans('AccountAccounting').': '; $moreforfilter .= '
'; if ($type == 'sub') { - if (getDolGlobalString('ACCOUNTANCY_COMBO_FOR_AUX')) { - $moreforfilter .= $formaccounting->select_auxaccount($search_accountancy_code_start, 'search_accountancy_code_start', $langs->trans('From'), 'maxwidth200'); - } else { - $moreforfilter .= ''; - } + $moreforfilter .= $formaccounting->select_auxaccount($search_accountancy_code_start, 'search_accountancy_code_start', $langs->trans('From'), 'maxwidth200'); } else { $moreforfilter .= $formaccounting->select_account($search_accountancy_code_start, 'search_accountancy_code_start', $langs->trans('From'), array(), 1, 1, 'maxwidth200'); } $moreforfilter .= ' '; if ($type == 'sub') { - if (getDolGlobalString('ACCOUNTANCY_COMBO_FOR_AUX')) { - $moreforfilter .= $formaccounting->select_auxaccount($search_accountancy_code_end, 'search_accountancy_code_end', $langs->trans('to'), 'maxwidth200'); - } else { - $moreforfilter .= ''; - } + $moreforfilter .= $formaccounting->select_auxaccount($search_accountancy_code_end, 'search_accountancy_code_end', $langs->trans('to'), 'maxwidth200'); } else { $moreforfilter .= $formaccounting->select_account($search_accountancy_code_end, 'search_accountancy_code_end', $langs->trans('to'), array(), 1, 1, 'maxwidth200'); } diff --git a/htdocs/admin/system/perf.php b/htdocs/admin/system/perf.php index 66616cc9be0..367936711ae 100644 --- a/htdocs/admin/system/perf.php +++ b/htdocs/admin/system/perf.php @@ -1,6 +1,7 @@ * Copyright (C) 2024-2025 Frédéric France + * Copyright (C) 2026 Alexandre Spangaro * * 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 @@ -37,7 +38,7 @@ require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; */ // Load translation files required by the page -$langs->loadLangs(array("install", "other", "admin", "products", "mrp")); +$langs->loadLangs(array("install", "other", "admin", "products", "mrp", "accountancy")); if (!$user->admin) { accessforbidden(); @@ -593,7 +594,7 @@ if ($resql) { print '
'; $db->free($resql); } -// Contact combo list +// Project combo list $sql = "SELECT COUNT(*) as nb"; $sql .= " FROM ".MAIN_DB_PREFIX."projet as s"; $resql = $db->query($sql); @@ -636,6 +637,35 @@ if ($resql) { $db->free($resql); } print '
'; +// Auxiliary account combo list (accounting) +// Check performance for auxiliary accounts (third parties with accounting code + users with accountancy_code) +$sql = "SELECT"; +$sql .= " (SELECT COUNT(*) FROM " . MAIN_DB_PREFIX . "societe"; +$sql .= " WHERE entity IN (" . getEntity('societe') . ")"; +$sql .= " AND (client IN (1,3) OR fournisseur = 1)"; +$sql .= " AND (code_compta != '' OR code_compta_fournisseur != '')) as nb_soc,"; +$sql .= " (SELECT COUNT(*) FROM " . MAIN_DB_PREFIX . "user"; +$sql .= " WHERE entity IN (" . getEntity('user') . ")"; +$sql .= " AND accountancy_code != '') as nb_user"; + +$resql = $db->query($sql); +if ($resql) { + $limitforoptim = 2000; + $obj = $db->fetch_object($resql); + $nb = (int) $obj->nb_soc + (int) $obj->nb_user; + + if ($nb > $limitforoptim) { + if (!getDolGlobalInt('ACCOUNTANCY_AUXACCOUNT_USE_SEARCH_TO_SELECT')) { + print img_picto('', 'warning', 'class="pictofixedwidth"') . ' ' . $langs->trans("YouHaveXObjectUseComboOptim", $nb, $langs->transnoentitiesnoconv("SubledgerAccount"), 'ACCOUNTANCY_AUXACCOUNT_USE_SEARCH_TO_SELECT'); + } else { + print img_picto('', 'tick', 'class="pictofixedwidth"') . ' ' . $langs->trans("YouHaveXObjectAndSearchOptimOn", $nb, $langs->transnoentitiesnoconv("SubledgerAccount"), 'ACCOUNTANCY_AUXACCOUNT_USE_SEARCH_TO_SELECT', getDolGlobalInt('ACCOUNTANCY_AUXACCOUNT_USE_SEARCH_TO_SELECT')); + } + } else { + print img_picto('', 'tick', 'class="pictofixedwidth"') . ' ' . $langs->trans("NbOfObjectIsLowerThanNoPb", $nb, $langs->transnoentitiesnoconv("SubledgerAccount")); + } + print '
'; + $db->free($resql); +} print '
'; diff --git a/htdocs/compta/bank/various_payment/card.php b/htdocs/compta/bank/various_payment/card.php index 08929eb862f..eb3d8fa33b3 100644 --- a/htdocs/compta/bank/various_payment/card.php +++ b/htdocs/compta/bank/various_payment/card.php @@ -1,5 +1,5 @@ +/* Copyright (C) 2017-2026 Alexandre Spangaro * Copyright (C) 2018-2025 Frédéric France * Copyright (C) 2023 Laurent Destailleur * Copyright (C) 2023 Joachim Kueter @@ -65,7 +65,7 @@ $amount = GETPOST("amount"); $paymenttype = GETPOST("paymenttype", "aZ09"); $accountancy_code = GETPOST("accountancy_code", "alpha"); $projectid = GETPOSTINT('projectid') ? GETPOSTINT('projectid') : GETPOSTINT('fk_project'); -if (isModEnabled('accounting') && getDolGlobalString('ACCOUNTANCY_COMBO_FOR_AUX')) { +if (isModEnabled('accounting') && getDolGlobalString('ACCOUNTANCY_AUXACCOUNT_USE_SEARCH_TO_SELECT') > 0) { $subledger_account = GETPOST("subledger_account", "alpha") > 0 ? GETPOST("subledger_account", "alpha") : ''; } else { $subledger_account = GETPOST("subledger_account", "alpha"); @@ -570,11 +570,7 @@ if ($action == 'create') { /** @var FormAccounting $formaccounting */ print ''.$langs->trans("SubledgerAccount").''; print ''; - if (getDolGlobalString('ACCOUNTANCY_COMBO_FOR_AUX')) { - print $formaccounting->select_auxaccount($subledger_account, 'subledger_account', 1, ''); - } else { - print ''; - } + print $formaccounting->select_auxaccount($subledger_account, 'subledger_account', 1, ''); print ''; } else { // For external software print ''.$langs->trans("SubledgerAccount").''; @@ -738,7 +734,7 @@ if ($id) { print $form->editfieldkey('SubledgerAccount', 'subledger_account', $object->subledger_account, $object, (int) (!$alreadyaccounted && $permissiontoadd), 'string', '', 0); print ''; if ($action == 'editsubledger_account' && (!$alreadyaccounted && $permissiontoadd)) { - if (getDolGlobalString('ACCOUNTANCY_COMBO_FOR_AUX')) { + if (getDolGlobalString('ACCOUNTANCY_AUXACCOUNT_USE_SEARCH_TO_SELECT') > 0) { /** @var FormAccounting $formaccounting */ print $formaccounting->formAccountingAccount($_SERVER['PHP_SELF'] . '?id=' . $object->id, $object->subledger_account, 'subledger_account', 1, 1, '', 1); } else { diff --git a/htdocs/compta/bank/various_payment/list.php b/htdocs/compta/bank/various_payment/list.php index f7454eaef24..323cdefe5cb 100644 --- a/htdocs/compta/bank/various_payment/list.php +++ b/htdocs/compta/bank/various_payment/list.php @@ -1,5 +1,5 @@ +/* Copyright (C) 2017-2026 Alexandre Spangaro * Copyright (C) 2017 Laurent Destailleur * Copyright (C) 2018-2024 Frédéric France * Copyright (C) 2020 Tobias Sekan @@ -308,7 +308,7 @@ if ($search_bank_entry > 0) { if ($search_accountancy_account > 0) { $sql .= " AND v.accountancy_code = ".((int) $search_accountancy_account); } -if (getDolGlobalString('ACCOUNTANCY_COMBO_FOR_AUX') && ($search_accountancy_subledger > 0)) { +if (getDolGlobalString('ACCOUNTANCY_AUXACCOUNT_USE_SEARCH_TO_SELECT') > 0 && ($search_accountancy_subledger > 0)) { $sql .= " AND v.subledger_account = '".$db->escape($search_accountancy_subledger)."'"; } else { if ($search_accountancy_subledger != '' && $search_accountancy_subledger != '-1') { @@ -581,13 +581,7 @@ if (!empty($arrayfields['subledger']['checked'])) { /** @var FormAccounting $formaccounting */ print ''; print '
'; - - if (getDolGlobalString('ACCOUNTANCY_COMBO_FOR_AUX')) { - print $formaccounting->select_auxaccount($search_accountancy_subledger, 'search_accountancy_subledger', 1, 'maxwidth150'); - } else { - print ''; - } - + print $formaccounting->select_auxaccount($search_accountancy_subledger, 'search_accountancy_subledger', 1, 'maxwidth150'); print '
'; print ''; } diff --git a/htdocs/core/ajax/auxaccount.php b/htdocs/core/ajax/auxaccount.php new file mode 100644 index 00000000000..ff4f35d9fea --- /dev/null +++ b/htdocs/core/ajax/auxaccount.php @@ -0,0 +1,145 @@ + + * + * 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 + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * \file htdocs/core/ajax/auxaccount.php + * \ingroup Accountancy (Double entries) + * \brief Ajax endpoint to search auxiliary accounts (thirdparties + users) + * Called by select_auxaccount() when ACCOUNTANCY_AUXACCOUNT_USE_SEARCH_TO_SELECT >= 2 + */ + +// Minimum ajax defines +if (!defined('NOTOKENRENEWAL')) { + define('NOTOKENRENEWAL', 1); // Disables token renewal +} +if (!defined('NOREQUIREMENU')) { + define('NOREQUIREMENU', '1'); +} +if (!defined('NOREQUIREHTML')) { + define('NOREQUIREHTML', '1'); +} +if (!defined('NOREQUIREAJAX')) { + define('NOREQUIREAJAX', '1'); +} +if (!defined('NOHEADERNOFOOTER')) { + define('NOHEADERNOFOOTER', '1'); +} + +require '../../main.inc.php'; + +/** + * @var Conf $conf + * @var DoliDB $db + * @var Translate $langs + * @var User $user + */ + +// Security — must be logged in +if (!$user->id) { + accessforbidden(); +} + +// Security +if (!isModEnabled('accounting') || !$user->hasRight('accounting', 'mouvements', 'lire')) { + accessforbidden(); +} + +$htmlname = GETPOST('htmlname', 'aZ09') ?: 'account_num_aux'; +$searchkey = GETPOST(str_replace('.', '_', $htmlname), 'alpha'); +$outjson = GETPOSTINT('outjson'); +$limit = getDolGlobalInt('ACCOUNTANCY_AUXACCOUNT_SEARCH_LIMIT', 100); +$minLength = getDolGlobalInt('ACCOUNTANCY_AUXACCOUNT_USE_SEARCH_TO_SELECT', 2) - 1; + +$results = array(); + +if (strlen($searchkey) >= $minLength) { + // Search anywhere by default (use LIKE '%term%') + $prefix = getDolGlobalString('ACCOUNTANCY_AUXACCOUNT_DONOTSEARCH_ANYWHERE') ? '' : '%'; + + // --- Thirdparties (customers and/or suppliers) --- + $sql = "SELECT code_compta AS code_client, code_compta_fournisseur AS code_fourn, nom AS name"; + $sql .= " FROM ".$db->prefix()."societe"; + $sql .= " WHERE entity IN (".getEntity('societe').")"; + $sql .= " AND (client IN (1,3) OR fournisseur = 1)"; + $sql .= " AND (code_compta LIKE '".$db->escape($prefix.$searchkey)."%'"; + $sql .= " OR code_compta_fournisseur LIKE '".$db->escape($prefix.$searchkey)."%'"; + $sql .= " OR nom LIKE '".$db->escape($prefix.$searchkey)."%')"; + $sql .= $db->order("nom", "ASC"); + $sql .= $db->plimit($limit, 0); + + $resql = $db->query($sql); + if ($resql) { + while ($obj = $db->fetch_object($resql)) { + if (!empty($obj->code_client)) { + $key = $obj->code_client; + $results[$key] = array( + 'key' => $key, + 'value' => $key, // value shown in input once selected + 'label' => $key.' ('.$obj->name.')', // label shown in dropdown list + ); + } + if (!empty($obj->code_fourn) && !isset($results[$obj->code_fourn])) { + $key = $obj->code_fourn; + $results[$key] = array( + 'key' => $key, + 'value' => $key, + 'label' => $key.' ('.$obj->name.')', + ); + } + } + $db->free($resql); + } else { + dol_syslog("auxaccount.php: error societe query ".$db->lasterror(), LOG_ERR); + } + + // --- Users --- + $sql2 = "SELECT DISTINCT accountancy_code, lastname, firstname"; + $sql2 .= " FROM ".$db->prefix()."user"; + $sql2 .= " WHERE entity IN (".getEntity('user').")"; + $sql2 .= " AND accountancy_code != ''"; + $sql2 .= " AND (accountancy_code LIKE '".$db->escape($prefix.$searchkey)."%'"; + $sql2 .= " OR lastname LIKE '".$db->escape($prefix.$searchkey)."%'"; + $sql2 .= " OR firstname LIKE '".$db->escape($prefix.$searchkey)."%')"; + $sql2 .= $db->order("accountancy_code", "ASC"); + $sql2 .= $db->plimit($limit, 0); + + $resql2 = $db->query($sql2); + if ($resql2) { + while ($obj = $db->fetch_object($resql2)) { + if (!empty($obj->accountancy_code) && !isset($results[$obj->accountancy_code])) { + $key = $obj->accountancy_code; + $fullname = dolGetFirstLastname($obj->firstname, $obj->lastname); + $results[$key] = array( + 'key' => $key, + 'value' => $key, + 'label' => $key.' ('.$fullname.')', + ); + } + } + $db->free($resql2); + } else { + dol_syslog("auxaccount.php: error user query ".$db->lasterror(), LOG_ERR); + } + + ksort($results); +} + +// Output JSON — format expected by ajax_autocompleter(): array of {key, value, label} +top_httphead('application/json'); +echo json_encode(array_values($results)); + +$db->close(); diff --git a/htdocs/core/class/html.formaccounting.class.php b/htdocs/core/class/html.formaccounting.class.php index e1480a7ab16..fb3d024f60b 100644 --- a/htdocs/core/class/html.formaccounting.class.php +++ b/htdocs/core/class/html.formaccounting.class.php @@ -3,7 +3,7 @@ * Copyright (C) 2013-2014 Olivier Geffroy * Copyright (C) 2015 Ari Elbaz (elarifr) * Copyright (C) 2016 Marcos García - * Copyright (C) 2016-2025 Alexandre Spangaro + * Copyright (C) 2016-2026 Alexandre Spangaro * Copyright (C) 2024-2026 MDW * Copyright (C) 2024-2026 Frédéric France * @@ -486,20 +486,99 @@ class FormAccounting extends Form // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps /** * Return list of auxiliary accounts. Cumulate list from customers, suppliers and users. + * Behavior depends on constant ACCOUNTANCY_AUXACCOUNT_USE_SEARCH_TO_SELECT: + * 0 = free text input, no autocomplete, no list + * 1 = native HTML select with full list loaded, with select2 (original behavior) + * 2 = ajax search triggered after 1 character typed + * 3 = ajax search triggered after 2 characters typed + * 4 = ajax search triggered after 3 characters typed (recommended for large datasets) * - * @param string $selectid Preselected pcg_type - * @param string $htmlname Name of field in html form - * @param int|string $showempty Add an empty field - * @param string $morecss More css - * @param string $usecache Key to use to store result into a cache. Next call with same key will reuse the cache. - * @param string $labelhtmlname HTML name of label for autofill of account from name. - * @return string|int<-1,-1> String with HTML select, or -1 if error + * @param string $selectid Preselected account number + * @param string $htmlname Name of field in html form + * @param int|string $showempty Add an empty field + * @param string $morecss More css + * @param string $usecache Key to use for cache (mode 0 only) + * @param string $labelhtmlname HTML name of label input for autofill of subledger label + * @param string $selected_input_value Displayed text of preselected entry (ajax modes 2/3/4 only) + * @return string|int<-1,-1> HTML string or -1 on error */ - public function select_auxaccount($selectid, $htmlname = 'account_num_aux', $showempty = 0, $morecss = 'minwidth100 maxwidth300 maxwidthonsmartphone', $usecache = '', $labelhtmlname = '') + public function select_auxaccount($selectid, $htmlname = 'account_num_aux', $showempty = 0, $morecss = 'minwidth100 maxwidth300 maxwidthonsmartphone', $usecache = '', $labelhtmlname = '', $selected_input_value = '') { // phpcs:enable - global $conf; + global $conf, $langs; + $out = ''; + $auxaccount_mode = getDolGlobalInt('ACCOUNTANCY_AUXACCOUNT_USE_SEARCH_TO_SELECT', 0); + + // MODE 0: free text input - no list, no autocomplete + if ($auxaccount_mode == 0) { + $out .= ''; + return $out; + } + + // MODES 2/3/4: ajax - on-demand loading via endpoint + // minLength: mode 2 -> 1 char, mode 3 -> 2 chars, etc + if (!empty($conf->use_javascript_ajax) && $auxaccount_mode >= 2) { + require_once DOL_DOCUMENT_ROOT.'/core/lib/ajax.lib.php'; + + $minLength = $auxaccount_mode - 1; + $urloption = 'htmlname='.urlencode($htmlname).'&outjson=1'; + + // Resolve displayed text from selectid if no label was provided (e.g. on page reload) + if ($selectid && empty($selected_input_value)) { + // Search in thirdparties first + $sql = "SELECT nom AS name FROM ".$this->db->prefix()."societe"; + $sql .= " WHERE entity IN (".getEntity('societe').")"; + $sql .= " AND (code_compta = '".$this->db->escape($selectid)."'"; + $sql .= " OR code_compta_fournisseur = '".$this->db->escape($selectid)."')"; + $sql .= $this->db->plimit(1, 0); + $resql = $this->db->query($sql); + if ($resql && ($obj = $this->db->fetch_object($resql))) { + $selected_input_value = $selectid.' ('.$obj->name.')'; + $this->db->free($resql); + } else { + // Fallback: search in users + $sql2 = "SELECT lastname, firstname FROM ".$this->db->prefix()."user"; + $sql2 .= " WHERE entity IN (".getEntity('user').")"; + $sql2 .= " AND accountancy_code = '".$this->db->escape($selectid)."'"; + $sql2 .= $this->db->plimit(1, 0); + $resql2 = $this->db->query($sql2); + if ($resql2 && ($obj2 = $this->db->fetch_object($resql2))) { + $selected_input_value = $selectid.' ('.dolGetFirstLastname($obj2->firstname, $obj2->lastname).')'; + $this->db->free($resql2); + } else { + $selected_input_value = $selectid; + } + } + } + + // Visible text input — targeted by ajax_autocompleter() JavaScript via "input#search_$htmlname" + $placeholder = (!empty($showempty) && !is_numeric($showempty)) ? $showempty : $langs->trans("Search"); + $out .= ''; + + // Hidden input + jQuery UI autocomplete JS — the hidden id="$htmlname" is generated by ajax_autocompleter() itself + $out .= ajax_autocompleter($selectid, $htmlname, DOL_URL_ROOT.'/core/ajax/auxaccount.php', $urloption, $minLength, 0, array()); + + // Autofill subledger label — listen on "change" of the hidden field + // ajax_autocompleter() calls .val(ui.item.id).trigger("change") on the hidden field upon selection + if (!empty($labelhtmlname)) { + $out .= ''; + } + + return $out; + } + + // MODE 1 (or ajax disabled): native HTML select with full list + select2 - original behavior $aux_account = array(); if ($usecache && !empty($this->options_cache[$usecache])) { @@ -528,11 +607,10 @@ class FormAccounting extends Form dol_syslog(get_class($this)."::select_auxaccount ".$this->error, LOG_ERR); return -1; } - $this->db->free($resql); // Auxiliary user account - $sql = "SELECT DISTINCT accountancy_code, lastname, firstname "; + $sql = "SELECT DISTINCT accountancy_code, lastname, firstname"; $sql .= " FROM ".$this->db->prefix()."user"; $sql .= " WHERE entity IN (".getEntity('user').")"; $sql .= " ORDER BY accountancy_code"; @@ -558,21 +636,20 @@ class FormAccounting extends Form } } - // Build select - $out = ''; + // Build native select — select2 is automatically activated by Form::selectarray() with $useajax=1 $out .= Form::selectarray($htmlname, $aux_account, $selectid, ($showempty ? (is_numeric($showempty) ? 1 : $showempty) : 0), 0, 0, '', 0, 0, 0, '', $morecss, 1); - //automatic filling if we give the name of the subledger_label input + + // Autofill subledger label on select2 selection event if (!empty($conf->use_javascript_ajax) && !empty($labelhtmlname)) { $out .= ''; + jQuery(document).ready(() => { + $("#'.dol_escape_js($htmlname).'").on("select2:select", function(e) { + var regExp = /\(([^)]+)\)/; + const match = regExp.exec(e.params.data.text); + if (match) { $("input[name=\"'.dol_escape_js($labelhtmlname).'\"]").val(match[1]); } + }); + }); + '; } return $out; diff --git a/htdocs/install/mysql/migration/23.0.0-24.0.0.sql b/htdocs/install/mysql/migration/23.0.0-24.0.0.sql index a1960e624ab..0776d39479d 100644 --- a/htdocs/install/mysql/migration/23.0.0-24.0.0.sql +++ b/htdocs/install/mysql/migration/23.0.0-24.0.0.sql @@ -193,6 +193,8 @@ INSERT INTO llx_c_email_templates (entity, module, type_template, lang, private, ALTER TABLE llx_c_ticket_category ADD COLUMN fk_ticket_type integer NULL; +UPDATE llx_const SET name = __ENCRYPT('ACCOUNTANCY_AUXACCOUNT_USE_SEARCH_TO_SELECT')__ WHERE __DECRYPT('name')__ = 'ACCOUNTANCY_COMBO_FOR_AUX'; + ALTER TABLE llx_prelevement_bons ADD COLUMN fk_user_modif integer; create table llx_product_lang_extrafields diff --git a/htdocs/langs/en_US/accountancy.lang b/htdocs/langs/en_US/accountancy.lang index 50bb879da49..0eebc8b7244 100644 --- a/htdocs/langs/en_US/accountancy.lang +++ b/htdocs/langs/en_US/accountancy.lang @@ -170,7 +170,10 @@ ACCOUNTING_BANK_CONCILIATED=Transfer into accounting only the lines reconciliate BANK_DISABLE_DIRECT_INPUT=Disable direct recording of transaction in bank account ACCOUNTANCY_ER_DATE_RECORD=Use the end date of the period of an expense report as the date for the transfer to accounting, instead of the date of the expense ACCOUNTING_ENABLE_EXPORT_DRAFT_JOURNAL=Enable draft export on journal -ACCOUNTANCY_COMBO_FOR_AUX=Enable combo list for subsidiary account (may be slow if you have a lot of third parties, break ability to search on a part of value) +ACCOUNTANCY_AUXACCOUNT_USE_SEARCH_TO_SELECT=Enable combo list for subsidiary account (may be slow if you have a lot of third parties, optimize the selection of auxiliary accounts) +UseSearchToSelectAuxAccountTooltip=By default, the full list of auxiliary accounts is loaded into a combo list. When the number of third parties is high (more than 2000), this can slow down pages. You can activate a search mode to load only matching results on demand via Ajax, or switch to a free text input. +FreeInput=Free text input +DropDownList=Drop-down list ACCOUNTING_DATE_START_BINDING=Disable binding & transfer in accountancy when date is below this date (the transactions before this date will be excluded by default) ACCOUNTING_DEFAULT_PERIOD_ON_TRANSFER=On the page to transfer data into accountancy, what is the period selected by default ACCOUNTING_LABEL_OPERATION_ON_TRANSFER=When accounting is generated, default operation label