Skip to content

Commit 4646147

Browse files
Merge pull request #7 from bpolaszek/master
New feature: fetch rate from an older date. Thanks @bpolaszek!
2 parents 80fef00 + 9bc7d24 commit 4646147

File tree

6 files changed

+180
-36
lines changed

6 files changed

+180
-36
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ This library exposes 3 main classes to interact with, `Rates`, `Countries` and `
3434
$rates = new DvK\Vat\Rates\Rates();
3535
$rates->country('NL'); // 21
3636
$rates->country('NL', 'standard'); // 21
37+
$rates->country('NL', 'standard', new \Datetime('2010-01-01')); // 19
3738
$rates->country('NL', 'reduced'); // 6
3839
$rates->all(); // array in country code => rates format
3940
```

src/Rates/Caches/NullCache.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public function get($key, $default = null)
2020
/**
2121
* @param string $key
2222
* @param mixed $value
23-
* @param null|int|DateInterval $ttl
23+
* @param null|int|\DateInterval $ttl
2424
*
2525
* @return bool
2626
*/
@@ -58,7 +58,7 @@ public function getMultiple($keys, $default = null)
5858

5959
/**
6060
* @param iterable $values
61-
* @param null|int|DateInterval $ttl
61+
* @param null|int|\DateInterval $ttl
6262
*
6363
* @return bool
6464
*/

src/Rates/Clients/JsonVat.php

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,9 @@ public function fetch() {
3030
if( empty( $response_body ) ) {
3131
throw new ClientException( "Error fetching rates from {$url}.");
3232
}
33-
$data = json_decode($response_body);
3433

35-
// build map with country codes => rates
36-
$map = array();
37-
foreach ($data->rates as $rate) {
38-
$map[$rate->country_code] = $rate->periods[0]->rates;
39-
}
40-
41-
return $map;
34+
$data = json_decode($response_body, true);
35+
$output = array_combine(array_column($data['rates'], 'country_code'), $data['rates']);
36+
return $output;
4237
}
4338
}

src/Rates/Interfaces/Client.php

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,30 @@ interface Client {
1515
* This methods should return an associative array in the following format:
1616
*
1717
* [
18-
* 'DE' => [
19-
* 'standard' => 19,
20-
* 'reduced' => 7.0,
21-
* ],
22-
* ...
18+
* 'NL' => [
19+
* 'name' => 'Netherlands',
20+
* 'code' => 'NL',
21+
* 'country_code' => 'NL',
22+
* 'periods' => [
23+
* [
24+
* 'effective_from' => '2012-10-01',
25+
* 'rates' => [
26+
* 'reduced' => 6.0,
27+
* 'standard' => 21.0,
28+
* ],
29+
* ],
30+
* [
31+
* 'effective_from' => '0000-01-01',
32+
* 'rates' => [
33+
* 'reduced' => 5.0,
34+
* 'standard' => 19.0,
35+
* ],
36+
* ],
37+
* ],
38+
* ]
2339
* ]
2440
*
41+
*
2542
* @throws ClientException
2643
*
2744
* @return array

src/Rates/Rates.php

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class Rates
1717
protected $map = array();
1818

1919
/**
20-
* @var Cache
20+
* @var CacheInterface
2121
*/
2222
protected $cache;
2323

@@ -30,7 +30,7 @@ class Rates
3030
* Rates constructor.
3131
*
3232
* @param Client $client (optional)
33-
* @param Cache $cache (optional)
33+
* @param CacheInterface $cache (optional)
3434
*/
3535
public function __construct( Client $client = null, CacheInterface $cache = null )
3636
{
@@ -80,25 +80,50 @@ public function all()
8080
/**
8181
* @param string $country
8282
* @param string $rate
83+
* @param \DateTimeInterface $applicableDate - optionnal - the applicable date
8384
*
8485
* @return double
8586
*
8687
* @throws Exception
8788
*/
88-
public function country($country, $rate = 'standard')
89+
public function country($country, $rate = 'standard', \DateTimeInterface $applicableDate = null)
8990
{
9091
$country = strtoupper($country);
9192
$country = $this->getCountryCode($country);
9293

94+
if (null === $applicableDate) {
95+
$applicableDate = new \DateTime('today midnight');
96+
}
97+
9398
if (!isset($this->map[$country])) {
9499
throw new Exception('Invalid country code.');
95100
}
96101

97-
if (!isset($this->map[$country]->$rate)) {
102+
$periods = $this->map[$country]['periods'];
103+
104+
// Sort by date desc
105+
usort($periods, function ($period1, $period2) {
106+
return new \DateTime($period1['effective_from']) > new \DateTime($period2['effective_from']) ? -1 : 1;
107+
});
108+
109+
foreach ($periods AS $period) {
110+
if (new \DateTime($period['effective_from']) > $applicableDate) {
111+
continue;
112+
}
113+
else {
114+
break;
115+
}
116+
}
117+
118+
if (empty($period)) {
119+
throw new Exception('Unable to find a rate applicable at that date.');
120+
}
121+
122+
if (!isset($period['rates'][$rate])) {
98123
throw new Exception('Invalid rate.');
99124
}
100125

101-
return $this->map[$country]->$rate;
126+
return $period['rates'][$rate];
102127
}
103128

104129
/**

tests/RatesTest.php

Lines changed: 122 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -45,38 +45,102 @@ public function getCacheMock() {
4545
* @covers Rates::country
4646
*/
4747
public function test_country() {
48-
$data = array(
49-
'NL' => (object) [
50-
'standard' => 21,
51-
'reduced' => 15
48+
$data = [
49+
'NL' => [
50+
'name' => 'Netherlands',
51+
'code' => 'NL',
52+
'country_code' => 'NL',
53+
'periods' =>
54+
[
55+
[
56+
'effective_from' => '2020-01-01',
57+
'rates' =>
58+
[
59+
'reduced' => 7.0,
60+
'standard' => 22.0,
61+
],
62+
],
63+
[
64+
'effective_from' => '2012-10-01',
65+
'rates' =>
66+
[
67+
'reduced' => 6.0,
68+
'standard' => 21.0,
69+
],
70+
],
71+
[
72+
'effective_from' => '0000-01-01',
73+
'rates' =>
74+
[
75+
'reduced' => 5.0,
76+
'standard' => 19.0,
77+
],
78+
],
79+
],
5280
]
53-
);
81+
];
5482
$mock = $this->getClientMock();
5583
$mock
5684
->method('fetch')
5785
->will(self::returnValue( $data ));
5886

5987
$rates = new Rates($mock, null);
6088

89+
// Return correct VAT rates
90+
self::assertEquals( $rates->country('NL'), 21 );
91+
self::assertEquals( $rates->country('NL', 'reduced'), 6 );
92+
93+
// Return correct VAT rates on an older period
94+
self::assertEquals($rates->country('NL', 'standard', new \DateTimeImmutable('2010-01-01')), 19);
95+
self::assertEquals($rates->country('NL', 'reduced', new \DateTimeImmutable('2010-01-01')), 5);
96+
97+
// Return correct VAT rates on an future period
98+
self::assertEquals($rates->country('NL', 'standard', new \DateTimeImmutable('2022-01-01')), 22);
99+
self::assertEquals($rates->country('NL', 'reduced', new \DateTimeImmutable('2022-01-01')), 7);
100+
61101
// Exception when supplying country code for which we have no rate
62102
self::expectException( 'Exception' );
63103
$rates->country('US');
64-
65-
// Return correct VAT rates
66-
self::assertEquals( $rates->country('NL'), 21 );
67-
self::assertEquals( $rates->country('NL', 'reduced'), 15 );
68104
}
69105

70106
/**
71107
* @covers Rates::all()
72108
*/
73109
public function test_all() {
74-
$data = array(
75-
'NL' => (object) [
76-
'standard' => 21,
77-
'reduced' => 15
110+
$data = [
111+
'NL' => [
112+
'name' => 'Netherlands',
113+
'code' => 'NL',
114+
'country_code' => 'NL',
115+
'periods' =>
116+
[
117+
[
118+
'effective_from' => '2020-01-01',
119+
'rates' =>
120+
[
121+
'reduced' => 7.0,
122+
'standard' => 22.0,
123+
],
124+
],
125+
[
126+
'effective_from' => '2012-10-01',
127+
'rates' =>
128+
[
129+
'reduced' => 6.0,
130+
'standard' => 21.0,
131+
],
132+
],
133+
[
134+
'effective_from' => '0000-01-01',
135+
'rates' =>
136+
[
137+
'reduced' => 5.0,
138+
'standard' => 19.0,
139+
],
140+
],
141+
],
78142
]
79-
);
143+
];
80144
$mock = $this->getClientMock();
81145
$mock
82146
->method('fetch')
@@ -91,7 +155,40 @@ public function test_all() {
91155
*/
92156
public function test_ratesAreLoadedFromCache() {
93157
$mock = $this->getCacheMock();
94-
$data = array( 'NL' => (object) [ 'standard' => 21, 'reduced' => 15 ]);
158+
$data = [
159+
'NL' => [
160+
'name' => 'Netherlands',
161+
'code' => 'NL',
162+
'country_code' => 'NL',
163+
'periods' =>
164+
[
165+
[
166+
'effective_from' => '2020-01-01',
167+
'rates' =>
168+
[
169+
'reduced' => 7.0,
170+
'standard' => 22.0,
171+
],
172+
],
173+
[
174+
'effective_from' => '2012-10-01',
175+
'rates' =>
176+
[
177+
'reduced' => 6.0,
178+
'standard' => 21.0,
179+
],
180+
],
181+
[
182+
'effective_from' => '0000-01-01',
183+
'rates' =>
184+
[
185+
'reduced' => 5.0,
186+
'standard' => 19.0,
187+
],
188+
],
189+
],
190+
]
191+
];
95192

96193
$mock
97194
->method('get')
@@ -103,8 +200,17 @@ public function test_ratesAreLoadedFromCache() {
103200
self::assertNotEmpty($rates->all());
104201
self::assertEquals($rates->all(), $data);
105202

203+
// Return correct VAT rates
106204
self::assertEquals($rates->country('NL'), 21);
107-
self::assertEquals($rates->country('NL', 'reduced'), 15);
205+
self::assertEquals($rates->country('NL', 'reduced'), 6);
206+
207+
// Return correct VAT rates on an older period
208+
self::assertEquals($rates->country('NL', 'standard', new \DateTimeImmutable('2010-01-01')), 19);
209+
self::assertEquals($rates->country('NL', 'reduced', new \DateTimeImmutable('2010-01-01')), 5);
210+
211+
// Return correct VAT rates on an future period
212+
self::assertEquals($rates->country('NL', 'standard', new \DateTimeImmutable('2022-01-01')), 22);
213+
self::assertEquals($rates->country('NL', 'reduced', new \DateTimeImmutable('2022-01-01')), 7);
108214
}
109215

110216
/**

0 commit comments

Comments
 (0)