Skip to content

Commit 24776b8

Browse files
megachrizbojanz
authored andcommitted
Issue #2897190 by MegaChriz, jonnyeom, bojanz, sagesolutions, edurenye, Hubbs, dev.tim: Tax calculations do not take discounts (promotions) into account
1 parent e1ca6fe commit 24776b8

4 files changed

Lines changed: 124 additions & 3 deletions

File tree

modules/promotion/commerce_promotion.services.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ services:
77
class: Drupal\commerce_promotion\PromotionOrderProcessor
88
arguments: ['@entity_type.manager']
99
tags:
10-
- { name: commerce_order.order_processor, priority: 50, adjustment_type: promotion }
10+
- { name: commerce_order.order_processor, priority: 100, adjustment_type: promotion }
1111

1212
commerce_promotion.usage:
1313
class: Drupal\commerce_promotion\PromotionUsage

modules/tax/commerce_tax.services.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ services:
1717
class: Drupal\commerce_tax\TaxOrderProcessor
1818
arguments: ['@entity_type.manager']
1919
tags:
20-
- { name: commerce_order.order_processor, priority: 100, adjustment_type: tax }
20+
- { name: commerce_order.order_processor, priority: 50, adjustment_type: tax }

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,10 @@ public function apply(OrderInterface $order) {
145145

146146
foreach ($rates as $zone_id => $rate) {
147147
$zone = $zones[$zone_id];
148-
$unit_price = $order_item->getUnitPrice();
149148
$percentage = $rate->getPercentage();
149+
// Determine the final unit price.
150+
// This calculation must be done with a non-adjusted unit price.
151+
$unit_price = $order_item->getUnitPrice();
150152
$tax_amount = $percentage->calculateTaxAmount($unit_price, $prices_include_tax);
151153
if ($this->shouldRound()) {
152154
$tax_amount = $this->rounder->round($tax_amount);
@@ -159,6 +161,14 @@ public function apply(OrderInterface $order) {
159161
$unit_price = $unit_price->add($tax_amount);
160162
$order_item->setUnitPrice($unit_price);
161163
}
164+
// Now determine the actual tax amount, with the adjustments applied.
165+
$adjusted_unit_price = $order_item->getAdjustedUnitPrice(['promotion', 'fee']);
166+
if (!$adjusted_unit_price->equals($unit_price)) {
167+
$tax_amount = $percentage->calculateTaxAmount($adjusted_unit_price, $prices_include_tax);
168+
if ($this->shouldRound()) {
169+
$tax_amount = $this->rounder->round($tax_amount);
170+
}
171+
}
162172

163173
$order_item->addAdjustment(new Adjustment([
164174
'type' => 'tax',

modules/tax/tests/src/Kernel/Plugin/Commerce/TaxType/CustomTest.php

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Drupal\Tests\commerce_tax\Kernel\Plugin\Commerce\TaxType;
44

5+
use Drupal\commerce_order\Adjustment;
56
use Drupal\commerce_order\Entity\Order;
67
use Drupal\commerce_order\Entity\OrderItem;
78
use Drupal\commerce_order\Entity\OrderItemType;
@@ -188,6 +189,116 @@ public function testApplication() {
188189
$this->assertEquals(new Price('8.61', 'USD'), $order_item->getUnitPrice());
189190
}
190191

192+
/**
193+
* @covers ::apply
194+
*/
195+
public function testDiscountedPrices() {
196+
// Tax-inclusive prices + display-inclusive taxes.
197+
// A 10.33 USD price with a 1.00 USD discount should have a 9.33 USD total.
198+
$order = $this->buildOrder('RS', 'RS', [], TRUE);
199+
$order_items = $order->getItems();
200+
$order_item = reset($order_items);
201+
$order_item->addAdjustment(new Adjustment([
202+
'type' => 'promotion',
203+
'label' => t('Discount'),
204+
'amount' => new Price('-1', 'USD'),
205+
]));
206+
$this->plugin->apply($order);
207+
$order_items = $order->getItems();
208+
$order_item = reset($order_items);
209+
210+
$adjustments = $order->collectAdjustments();
211+
$tax_adjustment = end($adjustments);
212+
$this->assertCount(2, $adjustments);
213+
$this->assertEquals('tax', $tax_adjustment->getType());
214+
$this->assertEquals(t('VAT'), $tax_adjustment->getLabel());
215+
$this->assertEquals(new Price('1.56', 'USD'), $tax_adjustment->getAmount());
216+
$this->assertEquals('0.2', $tax_adjustment->getPercentage());
217+
$this->assertEquals(new Price('10.33', 'USD'), $order_item->getUnitPrice());
218+
$this->assertEquals(new Price('9.33', 'USD'), $order_item->getAdjustedUnitPrice());
219+
220+
// Non-tax-inclusive prices + display-inclusive taxes.
221+
// A 10.33 USD price is 12.40 USD with 20% tax included.
222+
// A 1.00 USD discount should result in a 11.40 USD total.
223+
$order = $this->buildOrder('RS', 'RS', []);
224+
$order_items = $order->getItems();
225+
$order_item = reset($order_items);
226+
$order_item->addAdjustment(new Adjustment([
227+
'type' => 'promotion',
228+
'label' => t('Discount'),
229+
'amount' => new Price('-1', 'USD'),
230+
]));
231+
$this->plugin->apply($order);
232+
$order_items = $order->getItems();
233+
$order_item = reset($order_items);
234+
235+
$adjustments = $order->collectAdjustments();
236+
$tax_adjustment = end($adjustments);
237+
$this->assertCount(2, $adjustments);
238+
$this->assertEquals('tax', $tax_adjustment->getType());
239+
$this->assertEquals(t('VAT'), $tax_adjustment->getLabel());
240+
$this->assertEquals(new Price('2.28', 'USD'), $tax_adjustment->getAmount());
241+
$this->assertEquals('0.2', $tax_adjustment->getPercentage());
242+
$this->assertEquals(new Price('12.40', 'USD'), $order_item->getUnitPrice());
243+
$this->assertEquals(new Price('11.40', 'USD'), $order_item->getAdjustedUnitPrice());
244+
245+
// Non-tax-inclusive prices + non-display-inclusive taxes.
246+
// A 10.33 USD price with a 1.00 USD discount is 9.33 USD.
247+
// And 9.33 USD plus 20% tax is 11.20 USD.
248+
$configuration = $this->plugin->getConfiguration();
249+
$configuration['display_inclusive'] = FALSE;
250+
$this->plugin->setConfiguration($configuration);
251+
$order = $this->buildOrder('RS', 'RS', []);
252+
$order_items = $order->getItems();
253+
$order_item = reset($order_items);
254+
$order_item->addAdjustment(new Adjustment([
255+
'type' => 'promotion',
256+
'label' => t('Discount'),
257+
'amount' => new Price('-1', 'USD'),
258+
]));
259+
$this->plugin->apply($order);
260+
$order_items = $order->getItems();
261+
$order_item = reset($order_items);
262+
263+
$adjustments = $order->collectAdjustments();
264+
$tax_adjustment = end($adjustments);
265+
$this->assertCount(2, $adjustments);
266+
$this->assertEquals('tax', $tax_adjustment->getType());
267+
$this->assertEquals(t('VAT'), $tax_adjustment->getLabel());
268+
$this->assertEquals(new Price('1.87', 'USD'), $tax_adjustment->getAmount());
269+
$this->assertEquals('0.2', $tax_adjustment->getPercentage());
270+
$this->assertEquals(new Price('10.33', 'USD'), $order_item->getUnitPrice());
271+
$this->assertEquals(new Price('11.20', 'USD'), $order_item->getAdjustedUnitPrice());
272+
273+
// Tax-inclusive prices + non-display-inclusive taxes.
274+
// A 10.33 USD price is 8.61 USD once the 20% tax is removed.
275+
// A 1.00 USD discount gives 7.61 USD + 20% VAT -> 8.88 USD.
276+
$configuration = $this->plugin->getConfiguration();
277+
$configuration['display_inclusive'] = FALSE;
278+
$this->plugin->setConfiguration($configuration);
279+
$order = $this->buildOrder('RS', 'RS', [], TRUE);
280+
$order_items = $order->getItems();
281+
$order_item = reset($order_items);
282+
$order_item->addAdjustment(new Adjustment([
283+
'type' => 'promotion',
284+
'label' => t('Discount'),
285+
'amount' => new Price('-1', 'USD'),
286+
]));
287+
$this->plugin->apply($order);
288+
$order_items = $order->getItems();
289+
$order_item = reset($order_items);
290+
291+
$adjustments = $order->collectAdjustments();
292+
$tax_adjustment = end($adjustments);
293+
$this->assertCount(2, $adjustments);
294+
$this->assertEquals('tax', $tax_adjustment->getType());
295+
$this->assertEquals(t('VAT'), $tax_adjustment->getLabel());
296+
$this->assertEquals(new Price('1.27', 'USD'), $tax_adjustment->getAmount());
297+
$this->assertEquals('0.2', $tax_adjustment->getPercentage());
298+
$this->assertEquals(new Price('8.61', 'USD'), $order_item->getUnitPrice());
299+
$this->assertEquals(new Price('8.88', 'USD'), $order_item->getAdjustedUnitPrice());
300+
}
301+
191302
/**
192303
* @covers ::apply
193304
*/

0 commit comments

Comments
 (0)