Skip to content

Commit

Permalink
Support selection of the highest tax rate in the basket for shipping (#…
Browse files Browse the repository at this point in the history
…1937)

Currently the shipping tables add-on only supports returning the default
tax class/rate.

However countries can have different requirements for how tax should be
calculated on shipping depending on what is in the basket, for example
in the UK shipping should take the most expensive tax rate in the
basket.

This PR updates the add-on with a new config option
(`lunar.shipping-tables.shipping_rate_tax_calculation`) which currently
has two options: `default` for selecting the default rate, or `highest`
for selecting the highest rate in the basket.

---------

Co-authored-by: Author <actions@github.com>
  • Loading branch information
ryanmitchell and actions-user authored Nov 19, 2024
1 parent 1ce7206 commit b7df5d0
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 1 deletion.
7 changes: 7 additions & 0 deletions packages/table-rate-shipping/config/shipping-tables.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,11 @@

return [
'enabled' => env('LUNAR_SHIPPING_TABLES_ENABLED', true),

/*
* What method should we use for a shipping rate tax calculation?
* Options are 'default' for the system-wide default tax rate,
* or 'highest' to select the highest tax rate in the cart
*/
'shipping_rate_tax_calculation' => 'default',
];
27 changes: 26 additions & 1 deletion packages/table-rate-shipping/src/Models/ShippingRate.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class ShippingRate extends BaseModel implements Contracts\ShippingRate, Purchasa
*/
protected $guarded = [];

private ?TaxClass $resolvedTaxClass;

protected static function booted()
{
self::deleting(function (self $shippingRate) {
Expand Down Expand Up @@ -73,7 +75,7 @@ public function getUnitQuantity(): int
*/
public function getTaxClass(): TaxClass
{
return TaxClass::getDefault();
return $this->resolvedTaxClass ?? TaxClass::getDefault();
}

public function getTaxReference(): ?string
Expand Down Expand Up @@ -139,6 +141,10 @@ public function getThumbnail(): ?string
*/
public function getShippingOption(Cart $cart): ?ShippingOption
{
if (config('lunar.shipping-tables.shipping_rate_tax_calculation') == 'highest') {
$this->resolvedTaxClass = $this->resolveHighestTaxRateInCart($cart);
}

return $this->shippingMethod->driver()->resolve(
new ShippingOptionRequest(
shippingRate: $this,
Expand All @@ -156,4 +162,23 @@ public function getTotalInventory(): int
{
return 1;
}

private function resolveHighestTaxRateInCart(Cart $cart): ?TaxClass
{
$highestRate = false;
$highestTaxClass = null;

foreach ($cart->lines as $cartLine) {
if ($cartLine->purchasable->taxClass) {
foreach ($cartLine->purchasable->taxClass->taxRateAmounts as $amount) {
if ($highestRate === false || $amount->percentage > $highestRate) {
$highestRate = $amount->percentage;
$highestTaxClass = $cartLine->purchasable->taxClass;
}
}
}
}

return $highestTaxClass;
}
}
118 changes: 118 additions & 0 deletions tests/shipping/Unit/Resolvers/ShippingOptionResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
use Lunar\Models\CartAddress;
use Lunar\Models\Country;
use Lunar\Models\Currency;
use Lunar\Models\Price;
use Lunar\Models\ProductVariant;
use Lunar\Models\TaxClass;
use Lunar\Models\TaxRateAmount;
use Lunar\Shipping\DataTransferObjects\ShippingOptionLookup;
use Lunar\Shipping\Facades\Shipping;
use Lunar\Shipping\Models\ShippingMethod;
Expand Down Expand Up @@ -94,3 +97,118 @@

$this->assertcount(1, $options);
});

test('sets tax rate to the highest basket rate', function () {
config()->set('lunar.shipping-tables.shipping_rate_tax_calculation', 'highest');

$currency = Currency::factory()->create([
'default' => true,
]);

$country = Country::factory()->create();

TaxClass::factory()->create([
'default' => true,
'name' => 'default',
]);

$higherRate = TaxClass::factory()->create([
'default' => false,
'name' => 'higher',
]);

$taxAmount = TaxRateAmount::factory()->create();
$taxAmount->percentage = 90;

$higherRate->taxRateAmounts()->save($taxAmount);

$customerGroup = \Lunar\Models\CustomerGroup::factory()->create([
'default' => true,
]);

$shippingZone = ShippingZone::factory()->create([
'type' => 'countries',
]);

$shippingZone->countries()->attach($country);

$shippingMethod = ShippingMethod::factory()->create([
'driver' => 'ship-by',
'data' => [
'minimum_spend' => [
"{$currency->code}" => 200,
],
],
]);

$shippingMethod->customerGroups()->sync([
$customerGroup->id => ['enabled' => true, 'visible' => true, 'starts_at' => now(), 'ends_at' => null],
]);

$shippingRate = \Lunar\Shipping\Models\ShippingRate::factory()
->create([
'shipping_method_id' => $shippingMethod->id,
'shipping_zone_id' => $shippingZone->id,
]);

$shippingRate->prices()->createMany([
[
'price' => 600,
'min_quantity' => 1,
'currency_id' => $currency->id,
],
[
'price' => 500,
'min_quantity' => 700,
'currency_id' => $currency->id,
],
[
'price' => 0,
'min_quantity' => 800,
'currency_id' => $currency->id,
],
]);

$cart = $this->createCart($currency, 500);

$purchasable = ProductVariant::factory()->create();
$purchasable->stock = 50;
$purchasable->tax_class_id = $higherRate->id;
$purchasable->save();

Price::factory()->create([
'price' => 300,
'min_quantity' => 1,
'currency_id' => $currency->id,
'priceable_type' => $purchasable->getMorphClass(),
'priceable_id' => $purchasable->id,
]);

$cart->lines()->create([
'purchasable_type' => $purchasable->getMorphClass(),
'purchasable_id' => $purchasable->id,
'quantity' => 1,
]);

$cart->shippingAddress()->create(
CartAddress::factory()->make([
'country_id' => $country->id,
'state' => null,
])->toArray()
);

$shippingRates = Shipping::shippingRates(
$cart->refresh()->calculate()
)->get();

$options = Shipping::shippingOptions()->cart(
$cart->refresh()->calculate()
)->get(
new ShippingOptionLookup(
shippingRates: $shippingRates
)
);

$this->assertcount(1, $options);
$this->assertSame($options->first()->option->taxClass->id, $higherRate->id);
});

0 comments on commit b7df5d0

Please sign in to comment.