Merge branch 'develop' of git@github.com:Dolibarr/dolibarr.git into develop

This commit is contained in:
Laurent Destailleur 2026-07-17 11:31:57 +02:00
commit 1d5373325f
3 changed files with 67 additions and 8 deletions

View file

@ -8032,12 +8032,22 @@ class Form
//exit;
// Define list of countries to use to search VAT rates to show
// First we defined code_country to use to find list
if (is_object($societe_vendeuse)) {
$code_country = "'" . $societe_vendeuse->country_code . "'";
} else {
$code_country = "'" . $mysoc->country_code . "'"; // Pour compatibilite ascendente
// First we defined code_country to use to find list.
// country_code must be a c_country ISO code (e.g. 'FR', 'CH'). In some setups it may hold a
// country label (e.g. 'Suisse') instead, which would make the "c.code IN (...)" lookup done by
// load_cache_vatrates() match nothing and wrongly force the VAT rate to 0%. A valid ISO code is
// always 2 chars, so when the value is not a well formed ISO code (empty or a label) and we have
// a valid country id, we recover the ISO code from the authoritative country id. This way we do
// not run any SQL on each page access when we already have a valid ISO code.
$sellercountrycode = is_object($societe_vendeuse) ? $societe_vendeuse->country_code : $mysoc->country_code;
$sellercountryid = is_object($societe_vendeuse) ? $societe_vendeuse->country_id : $mysoc->country_id;
if ((int) $sellercountryid > 0 && strlen((string) $sellercountrycode) != 2) {
$tmpcountrycode = dol_getIdFromCode($this->db, (string) $sellercountryid, 'c_country', 'rowid', 'code');
if (!empty($tmpcountrycode) && !is_numeric($tmpcountrycode)) {
$sellercountrycode = $tmpcountrycode;
}
}
$code_country = "'" . $sellercountrycode . "'"; // Pour compatibilite ascendente
if ($societe_vendeuse == $mysoc && getDolGlobalString('SERVICE_ARE_ECOMMERCE_200238EC')) { // If option to have vat for end customer for services is on
require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php';

View file

@ -4771,11 +4771,14 @@ class Societe extends CommonObject
if (getDolGlobalString('MAIN_INFO_SOCIETE_COUNTRY')) {
$tmp = explode(':', getDolGlobalString('MAIN_INFO_SOCIETE_COUNTRY'));
$country_id = (is_numeric($tmp[0])) ? (int) $tmp[0] : 0;
if (!empty($tmp[1])) { // If $conf->global->MAIN_INFO_SOCIETE_COUNTRY is "id:code:label"
if (!empty($tmp[1]) && !empty($tmp[2])) { // MAIN_INFO_SOCIETE_COUNTRY is the canonical "id:code:label"
$country_code = $tmp[1];
$country_label = $tmp[2];
} else {
// For backward compatibility
} elseif ($country_id > 0) {
// For backward compatibility. The value is "id" only, or a legacy "id:label" where the
// second token is a country label (e.g. 'Suisse') and not an ISO code. In both cases we
// must not keep a label in country_code (it would break code-based lookups like VAT rates),
// so we rebuild code and label from the authoritative country id.
dol_syslog("Your country setup use an old syntax. Reedit it using setup area.", LOG_WARNING);
include_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
$country_code = getCountry($country_id, '2', $this->db); // This need a SQL request, but it's the old feature that should not be used anymore

View file

@ -568,4 +568,50 @@ class SocieteTest extends CommonClassTest
print __METHOD__." result=".$result."\n";
return $result;
}
/**
* Test that Societe::setMysoc() always resolves country_code to an ISO country code.
*
* Regression test for issue #37826: when MAIN_INFO_SOCIETE_COUNTRY uses the legacy
* "id:label" (2-token) syntax, the second token is a country label (e.g. 'France'),
* not an ISO code. setMysoc() must not leave that label in country_code (it would break
* code-based lookups such as VAT rates and force a 0% rate), but rebuild it from the id.
*
* @return void
*/
public function testSetMysocCountryCode()
{
global $conf,$user,$langs,$db;
$conf = $this->savconf;
$user = $this->savuser;
$langs = $this->savlangs;
$db = $this->savdb;
require_once dirname(__FILE__).'/../../htdocs/core/lib/company.lib.php';
$frid = (int) getCountry('FR', '3', $db);
$this->assertGreaterThan(0, $frid, 'Country FR must exist in dictionary c_country');
$savcountryconst = getDolGlobalString('MAIN_INFO_SOCIETE_COUNTRY');
// Canonical 3-token syntax "id:code:label".
$conf->global->MAIN_INFO_SOCIETE_COUNTRY = $frid.':FR:France';
$soc3 = new Societe($db);
$soc3->setMysoc($conf);
// Legacy 2-token syntax "id:label" (the #37826 case), second token is a label not a code.
$conf->global->MAIN_INFO_SOCIETE_COUNTRY = $frid.':France';
$soc2 = new Societe($db);
$soc2->setMysoc($conf);
// Restore the constant before asserting so a failure does not leak state to other tests.
$conf->global->MAIN_INFO_SOCIETE_COUNTRY = $savcountryconst;
$this->assertEquals('FR', $soc3->country_code, 'country_code must be the ISO code from a 3-token constant');
$this->assertEquals($frid, $soc3->country_id);
$this->assertEquals('FR', $soc2->country_code, 'country_code must be an ISO code, not a label, from a legacy 2-token constant (#37826)');
$this->assertEquals($frid, $soc2->country_id);
print __METHOD__." ok\n";
}
}