From 84d6002ab74eec8af5fb72148376b76724c0ef01 Mon Sep 17 00:00:00 2001 From: Alberto Peripolli Date: Tue, 19 Jan 2021 10:56:29 +0100 Subject: [PATCH 1/4] GB brexit and XI Northern Ireland continue to stay on EU https://tulli.fi/en/-/brexit-and-country-codes-from-1-january-2021-#:~:text=The%20country%20code%20for%20Northern,be%20used%20in%20customs%20declarations. --- src/Countries.php | 3 ++- src/Validator.php | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Countries.php b/src/Countries.php index 0699c91..a036aea 100644 --- a/src/Countries.php +++ b/src/Countries.php @@ -258,6 +258,7 @@ class Countries implements \Iterator, \ArrayAccess 'VI' => 'Virgin Islands, U.S.', 'WF' => 'Wallis And Futuna', 'EH' => 'Western Sahara', + 'XI' => 'Northern Ireland', 'YE' => 'Yemen', 'ZM' => 'Zambia', 'ZW' => 'Zimbabwe', @@ -278,7 +279,7 @@ public function hasCountryCode(string $code) : bool */ public function isCountryCodeInEU(string $code) : bool { - $eu = ['AT', 'BE', 'BG', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'HU', 'HR', 'IE', 'IT', 'LT', 'LU', 'LV', 'MT', 'NL', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK']; + $eu = ['AT', 'BE', 'BG', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GR', 'HU', 'HR', 'IE', 'IT', 'LT', 'LU', 'LV', 'MT', 'NL', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK', 'XI']; return in_array($code, $eu); } diff --git a/src/Validator.php b/src/Validator.php index 803dd56..a80751d 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -25,7 +25,6 @@ class Validator 'ES' => '([A-Z]\d{7}[A-Z]|\d{8}[A-Z]|[A-Z]\d{8})', 'FI' => '\d{8}', 'FR' => '[A-Z\d]{2}\d{9}', - 'GB' => '(\d{9}|\d{12}|(GD|HA)\d{3})', 'HR' => '\d{11}', 'HU' => '\d{8}', 'IE' => '([A-Z\d]{8}|[A-Z\d]{9})', @@ -40,7 +39,8 @@ class Validator 'RO' => '\d{2,10}', 'SE' => '\d{12}', 'SI' => '\d{8}', - 'SK' => '\d{10}' + 'SK' => '\d{10}', + 'XI' => '(\d{9}|\d{12}|(GD|HA)\d{3})', ]; /** From e5014d7f16ca8a2daa9b3a26d103ef43c6b959f9 Mon Sep 17 00:00:00 2001 From: Alberto Peripolli Date: Thu, 21 Sep 2023 17:16:52 +0200 Subject: [PATCH 2/4] Add exceptions compatibility --- src/Clients/IbericodeVatRatesClient.php | 7 ++++--- src/Period.php | 23 ++++++++++++++++++++--- src/Rates.php | 9 +++++---- tests/CountriesTest.php | 2 +- tests/RatesTest.php | 23 +++++++++++++++++++++-- tests/ValidatorTest.php | 2 -- 6 files changed, 51 insertions(+), 15 deletions(-) diff --git a/src/Clients/IbericodeVatRatesClient.php b/src/Clients/IbericodeVatRatesClient.php index 1f89d97..0c42d51 100644 --- a/src/Clients/IbericodeVatRatesClient.php +++ b/src/Clients/IbericodeVatRatesClient.php @@ -35,12 +35,13 @@ public function fetch() : array private function parseResponse(string $response_body) : array { - $result = json_decode($response_body, false); + $result = json_decode($response_body, true); + $return = []; - foreach ($result->items as $country => $periods) { + foreach ($result['items'] as $country => $periods) { foreach ($periods as $i => $period) { - $periods[$i] = new Period(new \DateTimeImmutable($period->effective_from), (array) $period->rates); + $periods[$i] = new Period(new \DateTimeImmutable($period['effective_from']), (array) $period['rates'], $period['exceptions'] ?? []); } $return[$country] = $periods; diff --git a/src/Period.php b/src/Period.php index ee6579e..33c28fb 100644 --- a/src/Period.php +++ b/src/Period.php @@ -17,11 +17,13 @@ class Period { private $effectiveFrom; private $rates = []; + private $exceptions = []; - public function __construct(DateTimeInterface $effectiveFrom, array $rates) + public function __construct(DateTimeInterface $effectiveFrom, array $rates, array $exceptions = []) { $this->effectiveFrom = $effectiveFrom; $this->rates = $rates; + $this->exceptions = $exceptions; } public function getEffectiveFrom() : DateTimeInterface @@ -29,12 +31,27 @@ public function getEffectiveFrom() : DateTimeInterface return $this->effectiveFrom; } - public function getRate(string $level) : float + public function getRate(string $level, ?string $postcode = null) : float { if (!isset($this->rates[$level])) { throw new InvalidArgumentException("Invalid rate level: {$level}"); } - return $this->rates[$level]; + return $this->getExceptionRate($level, $postcode) ?? $this->rates[$level]; + } + + private function getExceptionRate(string $level, ?string $postcode): ?float + { + if(!$postcode){ + return null; + } + + foreach ($this->exceptions as $exception){ + if(preg_match('/^'.$exception['postcode'].'$/', $postcode)){ + return $exception[$level] ?? $exception[Rates::RATE_STANDARD]; + } + } + + return null; } } diff --git a/src/Rates.php b/src/Rates.php index cbbc3ce..8cf08b6 100644 --- a/src/Rates.php +++ b/src/Rates.php @@ -68,6 +68,7 @@ private function load() private function loadFromFile() { $contents = file_get_contents($this->storagePath); + $data = unserialize($contents, [ 'allowed_classes' => [ Period::class, @@ -135,10 +136,10 @@ private function resolvePeriod(string $countryCode, DateTimeInterface $datetime) * @return float * @throws \Exception */ - public function getRateForCountry(string $countryCode, string $level = self::RATE_STANDARD) : float + public function getRateForCountry(string $countryCode, string $level = self::RATE_STANDARD, ?string $postcode = null) : float { $todayMidnight = new \DateTimeImmutable('today midnight'); - return $this->getRateForCountryOnDate($countryCode, $todayMidnight, $level); + return $this->getRateForCountryOnDate($countryCode, $todayMidnight, $level, $postcode); } /** @@ -148,9 +149,9 @@ public function getRateForCountry(string $countryCode, string $level = self::RAT * @return float * @throws Exception */ - public function getRateForCountryOnDate(string $countryCode, \DateTimeInterface $datetime, string $level = self::RATE_STANDARD) : float + public function getRateForCountryOnDate(string $countryCode, \DateTimeInterface $datetime, string $level = self::RATE_STANDARD, ?string $postcode = null) : float { $activePeriod = $this->resolvePeriod($countryCode, $datetime); - return $activePeriod->getRate($level); + return $activePeriod->getRate($level, $postcode); } } diff --git a/tests/CountriesTest.php b/tests/CountriesTest.php index 85381bc..f818cba 100644 --- a/tests/CountriesTest.php +++ b/tests/CountriesTest.php @@ -13,7 +13,7 @@ public function testIterator() { $countries = new Countries(); - $this->assertCount(249, $countries); + $this->assertCount(250, $countries); } public function testArrayAccess() diff --git a/tests/RatesTest.php b/tests/RatesTest.php index 78b62f2..e8c50da 100644 --- a/tests/RatesTest.php +++ b/tests/RatesTest.php @@ -7,12 +7,11 @@ use Ibericode\Vat\Exception; use Ibericode\Vat\Period; use Ibericode\Vat\Rates; -use PHPUnit\Framework\Error\Error; use PHPUnit\Framework\TestCase; class RatesTest extends TestCase { - protected function setUp() : void + protected function setUp(): void { if (file_exists('vendor/rates')) { unlink('vendor/rates'); @@ -39,6 +38,17 @@ private function getRatesClientMock() new Period(new \DateTimeImmutable('2019/01/01'), [ 'standard' => 21.00, 'reduced' => 9.00, + ], [ + [ + "name" => "Park Frankendael", + "postcode" => "1097", + "standard" => 0 + ], + [ + "name" => "Park de Meer", + "postcode" => "(1098|1099)", + "standard" => 0 + ] ]) ] ]); @@ -53,6 +63,15 @@ public function testGetRateForCountry() $this->assertEquals(21.0, $rates->getRateForCountry('NL')); } + public function testGetRateForCountryAndPostcode() + { + $client = $this->getRatesClientMock(); + $rates = new Rates('vendor/rates', 30, $client); + $this->assertEquals(0, $rates->getRateForCountry('NL', Rates::RATE_STANDARD, '1097')); + $this->assertEquals(0, $rates->getRateForCountry('NL', Rates::RATE_STANDARD, '1099')); + $this->assertEquals(0, $rates->getRateForCountry('NL', Rates::RATE_STANDARD, '1098')); + } + public function testGetRateForCountryOnDate() { $client = $this->getRatesClientMock(); diff --git a/tests/ValidatorTest.php b/tests/ValidatorTest.php index 895a81c..94a0530 100644 --- a/tests/ValidatorTest.php +++ b/tests/ValidatorTest.php @@ -36,7 +36,6 @@ public function testValidateVatNumberFormat() 'FRA2345678901', 'FRAB345678901', 'FR1B345678901', - 'GB999999973', 'HU12345678', 'HR12345678901', 'IE1234567X', @@ -74,7 +73,6 @@ public function testValidateVatNumberFormat() 'ESX1234567', 'FI1234567', 'FR1234567890', - 'GB99999997', 'HU1234567', 'HR1234567890', 'IE123456X', From 88f0b63f16c8c8483714b0b11e212822161f7a3b Mon Sep 17 00:00:00 2001 From: Alberto Peripolli Date: Tue, 14 Nov 2023 17:06:58 +0100 Subject: [PATCH 3/4] Update Validator.php --- src/Validator.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Validator.php b/src/Validator.php index 39700a9..8ccbd9a 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -26,6 +26,7 @@ class Validator 'EU' => '\d{9}', 'FI' => '\d{8}', 'FR' => '[A-Z\d]{2}\d{9}', + 'GB' => '(\d{9}|\d{12}|(GD|HA)\d{3})', 'HR' => '\d{11}', 'HU' => '\d{8}', 'IE' => '([A-Z\d]{8}|[A-Z\d]{9})', From 00e0fc2f57252e59dea49efad8ec09f61dc0814d Mon Sep 17 00:00:00 2001 From: Alberto Peripolli Date: Tue, 14 Nov 2023 17:08:25 +0100 Subject: [PATCH 4/4] Update ValidatorTest.php --- tests/ValidatorTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/ValidatorTest.php b/tests/ValidatorTest.php index 94a0530..895a81c 100644 --- a/tests/ValidatorTest.php +++ b/tests/ValidatorTest.php @@ -36,6 +36,7 @@ public function testValidateVatNumberFormat() 'FRA2345678901', 'FRAB345678901', 'FR1B345678901', + 'GB999999973', 'HU12345678', 'HR12345678901', 'IE1234567X', @@ -73,6 +74,7 @@ public function testValidateVatNumberFormat() 'ESX1234567', 'FI1234567', 'FR1234567890', + 'GB99999997', 'HU1234567', 'HR1234567890', 'IE123456X',