* FIX #37826 country_code must be an ISO code, not a label On a proposal/order, the 2nd+ line got VAT 0% with the error "no vat rates defined for country 'France'/'Suisse'". Root cause: the seller country_code held a translated country label instead of the ISO code, so the "c.code IN (...)" lookup in Form::load_cache_vatrates() matched nothing. - Societe::setMysoc(): the canonical MAIN_INFO_SOCIETE_COUNTRY is "id:code:label"; a legacy "id:label" value put the label into country_code. Only treat the full 3-token form as "id:code:label" and otherwise rebuild code/label from the authoritative country id. - Form::load_tva(): defensively recover the seller ISO code from its country id when country_code is not a known c_country code, so a bad value can never silently force the VAT rate to 0%. Signed-off-by: Jam Balaya <jambalaya.pyoncafe@outlook.jp> * FIX #37826 avoid useless SQL per page: recover country ISO code from id only when code is not a 2-char ISO code --------- Signed-off-by: Jam Balaya <jambalaya.pyoncafe@outlook.jp>
This commit is contained in:
parent
6d828c29fc
commit
b2eae1b7d7
3 changed files with 67 additions and 8 deletions
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue