Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support selection of the highest tax rate in the basket for shipping #1937

Open
wants to merge 9 commits into
base: 1.x
Choose a base branch
from
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);
});