Skip to content

Commit b8a7444

Browse files
committed
Issue #2872190 by bojanz: Create tax rate resolvers
1 parent 1173b61 commit b8a7444

File tree

9 files changed

+184
-9
lines changed

9 files changed

+184
-9
lines changed

modules/tax/commerce_tax.services.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,16 @@ services:
33
class: Drupal\commerce_tax\TaxTypeManager
44
parent: default_plugin_manager
55

6+
commerce_tax.chain_tax_rate_resolver:
7+
class: Drupal\commerce_tax\Resolver\ChainTaxRateResolver
8+
tags:
9+
- { name: service_collector, call: addResolver, tag: commerce_tax.tax_rate_resolver }
10+
11+
commerce_tax.default_tax_rate_resolver:
12+
class: Drupal\commerce_tax\Resolver\DefaultTaxRateResolver
13+
tags:
14+
- { name: commerce_tax.tax_rate_resolver, priority: -100 }
15+
616
commerce_tax.tax_order_processor:
717
class: Drupal\commerce_tax\TaxOrderProcessor
818
arguments: ['@entity_type.manager']

modules/tax/src/Plugin/Commerce/TaxType/Custom.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Drupal\commerce_tax\Plugin\Commerce\TaxType;
44

55
use Drupal\commerce_price\RounderInterface;
6+
use Drupal\commerce_tax\Resolver\ChainTaxRateResolverInterface;
67
use Drupal\commerce_tax\TaxZone;
78
use Drupal\Component\Uuid\UuidInterface;
89
use Drupal\Component\Utility\Html;
@@ -43,11 +44,13 @@ class Custom extends LocalTaxTypeBase {
4344
* The event dispatcher.
4445
* @param \Drupal\commerce_price\RounderInterface $rounder
4546
* The rounder.
47+
* @param \Drupal\commerce_tax\ChainTaxRateResolverInterface $chain_rate_resolver
48+
* The chain tax rate resolver.
4649
* @param \Drupal\Component\Uuid\UuidInterface $uuid_generator
4750
* The UUID generator.
4851
*/
49-
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EventDispatcherInterface $event_dispatcher, RounderInterface $rounder, UuidInterface $uuid_generator) {
50-
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $event_dispatcher, $rounder);
52+
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EventDispatcherInterface $event_dispatcher, RounderInterface $rounder, ChainTaxRateResolverInterface $chain_rate_resolver, UuidInterface $uuid_generator) {
53+
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $event_dispatcher, $rounder, $chain_rate_resolver);
5154

5255
$this->uuidGenerator = $uuid_generator;
5356
}
@@ -63,6 +66,7 @@ public static function create(ContainerInterface $container, array $configuratio
6366
$container->get('entity_type.manager'),
6467
$container->get('event_dispatcher'),
6568
$container->get('commerce_price.rounder'),
69+
$container->get('commerce_tax.chain_tax_rate_resolver'),
6670
$container->get('uuid')
6771
);
6872
}

modules/tax/src/Plugin/Commerce/TaxType/LocalTaxTypeBase.php

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Drupal\commerce_price\RounderInterface;
1010
use Drupal\commerce_store\Entity\StoreInterface;
1111
use Drupal\commerce_tax\TaxZone;
12+
use Drupal\commerce_tax\Resolver\ChainTaxRateResolverInterface;
1213
use Drupal\Core\Entity\EntityTypeManagerInterface;
1314
use Drupal\profile\Entity\ProfileInterface;
1415
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -26,6 +27,13 @@ abstract class LocalTaxTypeBase extends TaxTypeBase implements LocalTaxTypeInter
2627
*/
2728
protected $rounder;
2829

30+
/**
31+
* The chain tax rate resolver.
32+
*
33+
* @var \Drupal\commerce_tax\ChainTaxRateResolverInterface
34+
*/
35+
protected $chainRateResolver;
36+
2937
/**
3038
* Constructs a new LocalTaxTypeBase object.
3139
*
@@ -41,11 +49,14 @@ abstract class LocalTaxTypeBase extends TaxTypeBase implements LocalTaxTypeInter
4149
* The event dispatcher.
4250
* @param \Drupal\commerce_price\RounderInterface $rounder
4351
* The rounder.
52+
* @param \Drupal\commerce_tax\ChainTaxRateResolverInterface $chain_rate_resolver
53+
* The chain tax rate resolver.
4454
*/
45-
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EventDispatcherInterface $event_dispatcher, RounderInterface $rounder) {
55+
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, EventDispatcherInterface $event_dispatcher, RounderInterface $rounder, ChainTaxRateResolverInterface $chain_rate_resolver) {
4656
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $event_dispatcher);
4757

4858
$this->rounder = $rounder;
59+
$this->chainRateResolver = $chain_rate_resolver;
4960
}
5061

5162
/**
@@ -58,7 +69,8 @@ public static function create(ContainerInterface $container, array $configuratio
5869
$plugin_definition,
5970
$container->get('entity_type.manager'),
6071
$container->get('event_dispatcher'),
61-
$container->get('commerce_price.rounder')
72+
$container->get('commerce_price.rounder'),
73+
$container->get('commerce_tax.chain_tax_rate_resolver')
6274
);
6375
}
6476

@@ -93,8 +105,8 @@ public function apply(OrderInterface $order) {
93105

94106
$zones = $this->resolveZones($order_item, $customer_profile);
95107
foreach ($zones as $zone) {
96-
$rate = $this->resolveRate($zone, $order_item, $customer_profile);
97-
if (!$rate) {
108+
$rate = $this->chainRateResolver->resolve($zone, $order_item, $customer_profile);
109+
if (!is_object($rate)) {
98110
// No applicable rate found.
99111
continue;
100112
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
namespace Drupal\commerce_tax\Resolver;
4+
5+
use Drupal\commerce_order\Entity\OrderItemInterface;
6+
use Drupal\commerce_tax\TaxZone;
7+
use Drupal\profile\Entity\ProfileInterface;
8+
9+
class ChainTaxRateResolver implements ChainTaxRateResolverInterface {
10+
11+
/**
12+
* The resolvers.
13+
*
14+
* @var \Drupal\commerce_tax\Resolver\TaxRateResolverInterface[]
15+
*/
16+
protected $resolvers = [];
17+
18+
/**
19+
* Constructs a new ChainTaxRateResolver object.
20+
*
21+
* @param \Drupal\commerce_tax\Resolver\TaxRateResolverInterface[] $resolvers
22+
* The resolvers.
23+
*/
24+
public function __construct(array $resolvers = []) {
25+
$this->resolvers = $resolvers;
26+
}
27+
28+
/**
29+
* {@inheritdoc}
30+
*/
31+
public function addResolver(TaxRateResolverInterface $resolver) {
32+
$this->resolvers[] = $resolver;
33+
}
34+
35+
/**
36+
* {@inheritdoc}
37+
*/
38+
public function getResolvers() {
39+
return $this->resolvers;
40+
}
41+
42+
/**
43+
* {@inheritdoc}
44+
*/
45+
public function resolve(TaxZone $zone, OrderItemInterface $order_item, ProfileInterface $customer_profile) {
46+
$result = NULL;
47+
foreach ($this->resolvers as $resolver) {
48+
$result = $resolver->resolve($zone, $order_item, $customer_profile);
49+
if ($result) {
50+
break;
51+
}
52+
}
53+
54+
return $result;
55+
}
56+
57+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Drupal\commerce_tax\Resolver;
4+
5+
/**
6+
* Runs the added resolvers one by one until one of them returns the tax rate.
7+
*
8+
* Each resolver in the chain can be another chain, which is why this interface
9+
* extends the tax rate resolver one.
10+
*/
11+
interface ChainTaxRateResolverInterface extends TaxRateResolverInterface {
12+
13+
/**
14+
* Adds a resolver.
15+
*
16+
* @param \Drupal\commerce_tax\Resolver\TaxRateResolverInterface $resolver
17+
* The resolver.
18+
*/
19+
public function addResolver(TaxRateResolverInterface $resolver);
20+
21+
/**
22+
* Gets all added resolvers.
23+
*
24+
* @return \Drupal\commerce_tax\Resolver\TaxRateResolverInterface[]
25+
* The resolvers.
26+
*/
27+
public function getResolvers();
28+
29+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Drupal\commerce_tax\Resolver;
4+
5+
use Drupal\commerce_order\Entity\OrderItemInterface;
6+
use Drupal\commerce_tax\TaxZone;
7+
use Drupal\profile\Entity\ProfileInterface;
8+
9+
/**
10+
* Returns the tax zone's default tax rate.
11+
*/
12+
class DefaultTaxRateResolver implements TaxRateResolverInterface {
13+
14+
/**
15+
* {@inheritdoc}
16+
*/
17+
public function resolve(TaxZone $zone, OrderItemInterface $order_item, ProfileInterface $customer_profile) {
18+
$rates = $zone->getRates();
19+
// Take the default rate, or fallback to the first rate.
20+
$resolved_rate = reset($rates);
21+
foreach ($rates as $rate) {
22+
if ($rate->isDefault()) {
23+
$resolved_rate = $rate;
24+
break;
25+
}
26+
}
27+
return $resolved_rate;
28+
}
29+
30+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace Drupal\commerce_tax\Resolver;
4+
5+
use Drupal\commerce_order\Entity\OrderItemInterface;
6+
use Drupal\commerce_tax\TaxZone;
7+
use Drupal\profile\Entity\ProfileInterface;
8+
9+
/**
10+
* Defines the interface for tax rate resolvers.
11+
*/
12+
interface TaxRateResolverInterface {
13+
14+
// Stops resolving when there is no applicable tax rate (cause the
15+
// provided order item is exempt from sales tax, for example).
16+
const NO_APPLICABLE_TAX_RATE = 'no_applicable_tax_rate';
17+
18+
/**
19+
* Resolves the tax rate for the given tax zone.
20+
*
21+
* @param \Drupal\commerce_tax\TaxZone $zone
22+
* The tax zone.
23+
* @param \Drupal\commerce_order\Entity\OrderItemInterface $order_item
24+
* The order item.
25+
* @param \Drupal\profile\Entity\ProfileInterface $customer_profile
26+
* The customer profile. Contains the address and tax number.
27+
*
28+
* @return \Drupal\commerce_tax\TaxRate|string|null
29+
* The tax rate, NO_APPLICABLE_TAX_RATE, or NULL.
30+
*/
31+
public function resolve(TaxZone $zone, OrderItemInterface $order_item, ProfileInterface $customer_profile);
32+
33+
}

modules/tax/src/TaxRate.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public function getAmounts() {
9494
}
9595

9696
/**
97-
* Gets the amount valid for the provided date.
97+
* Gets the amount valid for the given date.
9898
*
9999
* @param \Drupal\Core\Datetime\DrupalDateTime $date
100100
* The date.
@@ -106,7 +106,7 @@ public function getAmount(DrupalDateTime $date = NULL) {
106106
// Default to the current date.
107107
$date = $date ?: new DrupalDateTime();
108108
// Amount start/end dates don't include the time, so discard the time
109-
// portion of the provided date to make the matching precise.
109+
// portion of the given date to make the matching precise.
110110
$date->setTime(0, 0);
111111
foreach ($this->amounts as $amount) {
112112
$start_date = $amount->getStartDate();

modules/tax/src/TaxZone.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public function getRates() {
109109
}
110110

111111
/**
112-
* Checks whether the provided address belongs to the zone.
112+
* Checks whether the given address belongs to the zone.
113113
*
114114
* @param \CommerceGuys\Addressing\AddressInterface $address
115115
* The address.

0 commit comments

Comments
 (0)