NEW Accountancy - Auxiliary account - Propose an ajax search on list (#37570)

* Add perf indication

* WIP

* NEW Accountancy - Auxiliary account - Propose an ajax search on list

* CI

* Update html.formaccounting.class.php

* Refactor input element creation for search field

* CI

* CI

* CI

* Reduce change

* Phan commen

* Update auxaccount.php

---------

Co-authored-by: Laurent Destailleur <eldy@destailleur.fr>
This commit is contained in:
Alexandre SPANGARO 2026-03-25 18:30:28 +01:00 committed by GitHub
parent 3560acdfaa
commit 083a1ef978
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 342 additions and 142 deletions

View file

@ -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").' - <span class="opacitymedium">'.$langs->trans("FreeInput").'</span>',
(string) 1 => $langs->trans("Yes").' - <span class="opacitymedium">'.$langs->trans("DropDownList").'</span>',
(string) 2 => $langs->trans("Yes").' - <span class="opacitymedium">'.$langs->trans("NumberOfKeyToSearch", 1).'</span>',
(string) 3 => $langs->trans("Yes").' - <span class="opacitymedium">'.$langs->trans("NumberOfKeyToSearch", 2).'</span>',
(string) 4 => $langs->trans("Yes").' - <span class="opacitymedium">'.$langs->trans("NumberOfKeyToSearch", 3).'</span>',
);
$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');

View file

@ -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 '<input type="text" class="maxwidth150" name="subledger_account" value="'.(GETPOSTISSET("subledger_account") ? GETPOST("subledger_account", "alpha") : $line->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 '<br><input type="text" class="maxwidth150" name="subledger_label" value="'.(GETPOSTISSET("subledger_label") ? GETPOST("subledger_label", "alpha") : $line->subledger_label).'" placeholder="'.dol_escape_htmltag($langs->trans("SubledgerAccountLabel")).'">';
print '</td>';
@ -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 '</td>';
print '<td>';
// 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 '<input type="text" class="maxwidth150" name="new_subledger_account" value="" placeholder="' . dol_escape_htmltag($langs->trans("SubledgerAccount")) . '">';
}
print $formaccounting->select_auxaccount('', 'subledger_account', 1, 'maxwidth250', '', 'subledger_label');
print '<br><input type="text" class="maxwidth150" name="new_subledger_label" value="" placeholder="' . dol_escape_htmltag($langs->trans("SubledgerAccountLabel")) . '">';
print '</td>';
print '<td><input type="text" name="operation_label" class="flat minwidth150"></td>';

View file

@ -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 .= '<input type="text" class="maxwidth150" name="search_accountancy_code_start" value="'.dol_escape_htmltag($search_accountancy_code_start).'" placeholder="'.$langs->trans('From').'">';
}
$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 .= '<input type="text" class="maxwidth150" name="search_accountancy_code_end" value="'.dol_escape_htmltag($search_accountancy_code_end).'" placeholder="'.$langs->trans('to').'">';
}
$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');
}

View file

@ -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 '</td>';
print '<td>';
// 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 '<input type="text" class="maxwidth150" name="subledger_account" value="'.(GETPOSTISSET("subledger_account") ? GETPOST("subledger_account", "alpha") : $line->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 '<br><input type="text" class="maxwidth150" name="subledger_label" id="subledger_label" value="'.(GETPOSTISSET("subledger_label") ? GETPOST("subledger_label", "alpha") : $line->subledger_label).'" placeholder="'.dol_escape_htmltag($langs->trans("SubledgerAccountLabel")).'">';
print '</td>';
@ -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 '</td>';
print '<td>';
// 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 '<input type="text" class="maxwidth150" name="subledger_account" value="" placeholder="' . dol_escape_htmltag($langs->trans("SubledgerAccount")) . '">';
}
print $formaccounting->select_auxaccount('', 'subledger_account', 1, 'maxwidth250', '', 'subledger_label');
print '<br><input type="text" class="maxwidth150" name="subledger_label" id="subledger_label" value="" placeholder="' . dol_escape_htmltag($langs->trans("SubledgerAccountLabel")) . '">';
print '</td>';
print '<td><input type="text" class="minwidth200" name="label_operation" value="' . dol_escape_htmltag($label_operation) . '"/></td>';

View file

@ -1045,19 +1045,12 @@ if (!empty($arrayfields['t.numero_compte']['checked'])) {
// Subledger account
if (!empty($arrayfields['t.subledger_account']['checked'])) {
print '<td class="liste_titre">';
// 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 '<div class="nowrap">';
//print $langs->trans('From').' ';
print $formaccounting->select_auxaccount($search_accountancy_aux_code_start, 'search_accountancy_aux_code_start', $langs->trans('From'), 'maxwidth150', 'subledgeraccount');
print '</div>';
print '<div class="nowrap">';
print $formaccounting->select_auxaccount($search_accountancy_aux_code_end, 'search_accountancy_aux_code_end', $langs->trans('to'), 'maxwidth150', 'subledgeraccount');
print '</div>';
} else {
print '<input type="text" class="maxwidth75" name="search_accountancy_aux_code" value="'.dol_escape_htmltag($search_accountancy_aux_code).'">';
}
print '<div class="nowrap">';
print $formaccounting->select_auxaccount($search_accountancy_aux_code_start, 'search_accountancy_aux_code_start', $langs->trans('From'), 'maxwidth150', 'subledgeraccount');
print '</div>';
print '<div class="nowrap">';
print $formaccounting->select_auxaccount($search_accountancy_aux_code_end, 'search_accountancy_aux_code_end', $langs->trans('to'), 'maxwidth150', 'subledgeraccount');
print '</div>';
print '</td>';
}
// Label operation

View file

@ -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 '<td class="liste_titre">';
// 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 '<div class="nowrap">';
//print $langs->trans('From').' ';
print $formaccounting->select_auxaccount($search_accountancy_aux_code_start, 'search_accountancy_aux_code_start', $langs->trans('From'), 'maxwidth250', 'subledgeraccount');
print '</div>';
print '<div class="nowrap">';
print $formaccounting->select_auxaccount($search_accountancy_aux_code_end, 'search_accountancy_aux_code_end', $langs->trans('to'), 'maxwidth250', 'subledgeraccount');
print '</div>';
} else {
print '<input type="text" class="maxwidth75" name="search_accountancy_aux_code" value="'.dol_escape_htmltag($search_accountancy_aux_code).'">';
}
print '<div class="nowrap">';
print $formaccounting->select_auxaccount($search_accountancy_aux_code_start, 'search_accountancy_aux_code_start', $langs->trans('From'), 'maxwidth250', 'subledgeraccount');
print '</div>';
print '<div class="nowrap">';
print $formaccounting->select_auxaccount($search_accountancy_aux_code_end, 'search_accountancy_aux_code_end', $langs->trans('to'), 'maxwidth250', 'subledgeraccount');
print '</div>';
print '</td>';
}
// Label operation

View file

@ -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 .= '<div class="divsearchfield">';
$moreforfilter .= $langs->trans('AccountAccounting').': ';
$moreforfilter .= '<div class="nowrap inline-block">';
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 .= '<input type="text" class="maxwidth150" name="search_accountancy_code_start" value="'.dol_escape_htmltag($search_accountancy_code_start).'" placeholder="'.$langs->trans('From').'">';
}
$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 .= '<input type="text" class="maxwidth150" name="search_accountancy_code_end" value="'.dol_escape_htmltag($search_accountancy_code_end).'" placeholder="'.$langs->trans('to').'">';
}
$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');
}

View file

@ -1,6 +1,7 @@
<?php
/* Copyright (C) 2013-2019 Laurent Destailleur <eldy@users.sourceforge.net>
* Copyright (C) 2024-2025 Frédéric France <frederic.france@free.fr>
* Copyright (C) 2026 Alexandre Spangaro <alexandre@inovea-conseil.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
@ -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 '<br>';
$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 '</div>';
// 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 '<br>';
$db->free($resql);
}
print '<br>';

View file

@ -1,5 +1,5 @@
<?php
/* Copyright (C) 2017-2025 Alexandre Spangaro <alexandre@inovea-conseil.com>
/* Copyright (C) 2017-2026 Alexandre Spangaro <alexandre@inovea-conseil.com>
* Copyright (C) 2018-2025 Frédéric France <frederic.france@free.fr>
* Copyright (C) 2023 Laurent Destailleur <eldy@users.sourceforge.net>
* Copyright (C) 2023 Joachim Kueter <git-jk@bloxera.com>
@ -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 '<tr><td>'.$langs->trans("SubledgerAccount").'</td>';
print '<td>';
if (getDolGlobalString('ACCOUNTANCY_COMBO_FOR_AUX')) {
print $formaccounting->select_auxaccount($subledger_account, 'subledger_account', 1, '');
} else {
print '<input type="text" class="maxwidth200 maxwidthonsmartphone" name="subledger_account" value="'.$subledger_account.'">';
}
print $formaccounting->select_auxaccount($subledger_account, 'subledger_account', 1, '');
print '</td></tr>';
} else { // For external software
print '<tr><td>'.$langs->trans("SubledgerAccount").'</td>';
@ -738,7 +734,7 @@ if ($id) {
print $form->editfieldkey('SubledgerAccount', 'subledger_account', $object->subledger_account, $object, (int) (!$alreadyaccounted && $permissiontoadd), 'string', '', 0);
print '</td><td>';
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 {

View file

@ -1,5 +1,5 @@
<?php
/* Copyright (C) 2017-2024 Alexandre Spangaro <alexandre@inovea-conseil.com>
/* Copyright (C) 2017-2026 Alexandre Spangaro <alexandre@inovea-conseil.com>
* Copyright (C) 2017 Laurent Destailleur <eldy@users.sourceforge.net>
* Copyright (C) 2018-2024 Frédéric France <frederic.france@free.fr>
* Copyright (C) 2020 Tobias Sekan <tobias.sekan@startmail.com>
@ -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 '<td class="liste_titre">';
print '<div class="nowrap">';
if (getDolGlobalString('ACCOUNTANCY_COMBO_FOR_AUX')) {
print $formaccounting->select_auxaccount($search_accountancy_subledger, 'search_accountancy_subledger', 1, 'maxwidth150');
} else {
print '<input type="text" class="maxwidth150 maxwidthonsmartphone" name="search_accountancy_subledger" value="'.$search_accountancy_subledger.'">';
}
print $formaccounting->select_auxaccount($search_accountancy_subledger, 'search_accountancy_subledger', 1, 'maxwidth150');
print '</div>';
print '</td>';
}

View file

@ -0,0 +1,145 @@
<?php
/* Copyright (C) 2026 Alexandre Spangaro <alexandre@inovea-conseil.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
* 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 <https://www.gnu.org/licenses/>.
*/
/**
* \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();

View file

@ -3,7 +3,7 @@
* Copyright (C) 2013-2014 Olivier Geffroy <jeff@jeffinfo.com>
* Copyright (C) 2015 Ari Elbaz (elarifr) <github@accedinfo.com>
* Copyright (C) 2016 Marcos García <marcosgdf@gmail.com>
* Copyright (C) 2016-2025 Alexandre Spangaro <alexandre@inovea-conseil.com>
* Copyright (C) 2016-2026 Alexandre Spangaro <alexandre@inovea-conseil.com>
* Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
* Copyright (C) 2024-2026 Frédéric France <frederic.france@free.fr>
*
@ -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 .= '<input type="text" class="'.$morecss.'" name="'.$htmlname.'" id="'.$htmlname.'" value="'.$selectid.'" />';
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 .= '<input type="text" class="'.$morecss.'" name="search_'.$htmlname.'" id="search_'.$htmlname.'" value="'.dol_escape_htmltag($selected_input_value).'" placeholder="'.$langs->trans($placeholder).'" />';
// 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 .= '<script nonce="'.getNonce().'">
jQuery(document).ready(function() {
$("#'.dol_escape_js($htmlname).'").on("change", function() {
var searchVal = $("#search_'.dol_escape_js($htmlname).'").val();
var match = /\(([^)]+)\)/.exec(searchVal);
if (match) {
$("input[name=\"'.dol_escape_js($labelhtmlname).'\"]").val(match[1]);
}
});
});
</script>';
}
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 .= '<script nonce="'.getNonce().'">
jQuery(document).ready(() => {
$("#'.$htmlname.'").on("select2:select", function(e) {
var regExp = /\(([^)]+)\)/;
const match = regExp.exec(e.params.data.text);
$(\'input[name="'.dol_escape_js($labelhtmlname).'"]\').val(match[1]);
});
});
</script>';
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]); }
});
});
</script>';
}
return $out;

View file

@ -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

View file

@ -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