diff --git a/src/Clients/IbericodeVatRatesClient.php b/src/Clients/IbericodeVatRatesClient.php index 5247212..d7601e8 100644 --- a/src/Clients/IbericodeVatRatesClient.php +++ b/src/Clients/IbericodeVatRatesClient.php @@ -36,12 +36,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/Countries.php b/src/Countries.php index c89cf0f..b0d24f9 100644 --- a/src/Countries.php +++ b/src/Countries.php @@ -262,7 +262,9 @@ class Countries implements \Iterator, \ArrayAccess 'VI' => 'U.S. Virgin Islands', 'VN' => 'Vietnam', 'VU' => 'Vanuatu', - 'WF' => 'Wallis & Futuna', + 'WF' => 'Wallis And Futuna', + 'EH' => 'Western Sahara', + 'XI' => 'Northern Ireland', 'WS' => 'Samoa', 'YE' => 'Yemen', 'YT' => 'Mayotte', @@ -285,7 +287,7 @@ public function hasCountryCode(string $code): bool */ public function getCountryCodesInEU(): array { - return ['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']; + return ['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']; } /** diff --git a/src/Period.php b/src/Period.php index 3b59d59..6df76b2 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 a98a10b..507619b 100644 --- a/src/Rates.php +++ b/src/Rates.php @@ -67,6 +67,7 @@ private function load(): void private function loadFromFile(): void { $contents = file_get_contents($this->storagePath); + $data = unserialize($contents, [ 'allowed_classes' => [ Period::class, @@ -131,25 +132,27 @@ private function resolvePeriod(string $countryCode, DateTimeInterface $datetime) /** * @param string $countryCode ISO-3166-1-alpha2 country code * @param string $level + * @param ?string $postcode * @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); } /** * @param string $countryCode ISO-3166-1-alpha2 country code * @param DateTimeInterface $datetime * @param string $level + * @param ?string $postcode * @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/src/Validator.php b/src/Validator.php index d276ba9..16d7759 100644 --- a/src/Validator.php +++ b/src/Validator.php @@ -43,6 +43,7 @@ class Validator 'SI' => '\d{8}', 'SK' => '\d{10}', 'SM' => '\d{5}', + 'XI' => '(\d{9}|\d{12}|(GD|HA)\d{3})', ]; /** 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 12e865b..e8c50da 100644 --- a/tests/RatesTest.php +++ b/tests/RatesTest.php @@ -7,7 +7,6 @@ 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 @@ -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();