Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@
'label' => 'Min. Spend',
],
'min_weight' => [
'label' => 'Min. Weight',
'helper_text' => 'Enter weight in kilograms',
'label' => 'Min. Weight (:unit)',
'helper_text' => 'Enter weight in :unit',
],
'price' => [
'label' => 'Price',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,10 @@ public function resolve(ShippingOptionRequest $shippingOptionRequest): ?Shipping
$tier = $subTotal;

if ($chargeBy == 'weight') {
$tier = $cart->lines->sum(
fn ($line) => $line->purchasable->weight->to('weight.kg')->convert()->getValue() * $line->quantity
);
$weightUnit = $data['weight_unit'] ?? 'kg';
$tier = (int) round($cart->lines->sum(
fn ($line) => $line->purchasable->weight->to("weight.{$weightUnit}")->convert()->getValue() * $line->quantity
) * 100);
}

// Do we have a suitable tier price?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,15 +118,14 @@ function () {
->required(),
Forms\Components\TextInput::make('min_quantity')
->label(fn (Get $get) => static::isWeightCharge($get)
? __('lunarpanel.shipping::relationmanagers.shipping_rates.form.prices.repeater.min_weight.label')
? __('lunarpanel.shipping::relationmanagers.shipping_rates.form.prices.repeater.min_weight.label', ['unit' => strtoupper(static::getShippingWeightUnit($get('../../shipping_method_id')))])
: __('lunarpanel.shipping::relationmanagers.shipping_rates.form.prices.repeater.min_spend.label')
)
->helperText(fn (Get $get) => static::isWeightCharge($get)
? __('lunarpanel.shipping::relationmanagers.shipping_rates.form.prices.repeater.min_weight.helper_text')
? __('lunarpanel.shipping::relationmanagers.shipping_rates.form.prices.repeater.min_weight.helper_text', ['unit' => strtoupper(static::getShippingWeightUnit($get('../../shipping_method_id')))])
: null
)
// Unit symbol — intentionally not translated.
->suffix(fn (Get $get) => static::isWeightCharge($get) ? 'kg' : null)
->suffix(fn (Get $get) => static::isWeightCharge($get) ? static::getShippingWeightUnit($get('../../shipping_method_id')) : null)
->numeric()
->required(),
])->afterStateHydrated(
Expand All @@ -143,7 +142,7 @@ static function (Forms\Components\Repeater $component, ?Model $record = null): v
'customer_group_id' => $price->customer_group_id,
'price' => $price->price->decimal,
'currency_id' => $price->currency_id,
'min_quantity' => $chargeBy == 'cart_total' ? $price->min_quantity / $currency->factor : $price->min_quantity,
'min_quantity' => $chargeBy == 'cart_total' ? $price->min_quantity / $currency->factor : $price->min_quantity / 100,
];
})->toArray()
);
Expand Down Expand Up @@ -225,6 +224,19 @@ private static function getShippingChargeBy(ShippingMethodContract|int|null $met
return ($method?->data['charge_by'] ?? null) ?? 'cart_total';
}

private static function getShippingWeightUnit(ShippingMethodContract|int|null $method): string
{
if (blank($method)) {
return 'kg';
}

if (! $method instanceof ShippingMethodContract) {
$method = ShippingMethod::find($method);
}

return ($method?->data['weight_unit'] ?? null) ?? 'kg';
}

private static function isWeightCharge(Get $get): bool
{
return static::getShippingChargeBy($get('../../shipping_method_id')) === 'weight';
Expand Down Expand Up @@ -265,7 +277,7 @@ function ($price) use ($chargeBy, $currencies) {
if ($chargeBy == 'cart_total') {
$price['min_quantity'] = (int) ($price['min_quantity'] * $currency->factor);
} else {
$price['min_quantity'] = (int) $price['min_quantity'];
$price['min_quantity'] = (int) ($price['min_quantity'] * 100);
}

$price['price'] = (int) ($price['price'] * $currency->factor);
Expand Down
8 changes: 4 additions & 4 deletions tests/shipping/Unit/Drivers/ShippingMethods/ShipByTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -428,8 +428,8 @@

$shippingRate->prices()->createMany([
['price' => 1000, 'min_quantity' => 1, 'currency_id' => $currency->id],
['price' => 600, 'min_quantity' => 5, 'currency_id' => $currency->id],
['price' => 200, 'min_quantity' => 10, 'currency_id' => $currency->id],
['price' => 600, 'min_quantity' => 500, 'currency_id' => $currency->id],
['price' => 200, 'min_quantity' => 1000, 'currency_id' => $currency->id],
]);

$makeWeightCart = function (float $weightKg) use ($currency): Cart {
Expand Down Expand Up @@ -497,7 +497,7 @@

$shippingRate->prices()->createMany([
['price' => 1000, 'min_quantity' => 1, 'currency_id' => $currency->id],
['price' => 500, 'min_quantity' => 5, 'currency_id' => $currency->id],
['price' => 500, 'min_quantity' => 500, 'currency_id' => $currency->id],
]);

$variant = ProductVariant::factory()->create([
Expand Down Expand Up @@ -549,7 +549,7 @@

$shippingRate->prices()->createMany([
['price' => 1000, 'min_quantity' => 1, 'currency_id' => $currency->id],
['price' => 400, 'min_quantity' => 5, 'currency_id' => $currency->id],
['price' => 400, 'min_quantity' => 500, 'currency_id' => $currency->id],
]);

$variantKg = ProductVariant::factory()->create(['weight_value' => 2.0, 'weight_unit' => 'kg']);
Expand Down
199 changes: 199 additions & 0 deletions tests/shipping/Unit/Drivers/ShippingMethods/ShipByWeightUnitTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
<?php

use Illuminate\Foundation\Testing\RefreshDatabase;
use Lunar\DataTypes\ShippingOption;
use Lunar\Models\Cart;
use Lunar\Models\Currency;
use Lunar\Models\Price;
use Lunar\Models\ProductVariant;
use Lunar\Models\TaxClass;
use Lunar\Shipping\DataTransferObjects\ShippingOptionRequest;
use Lunar\Shipping\Drivers\ShippingMethods\ShipBy;
use Lunar\Shipping\Models\ShippingMethod;
use Lunar\Shipping\Models\ShippingRate;
use Lunar\Shipping\Models\ShippingZone;
use Lunar\Tests\Shipping\TestCase;
use Lunar\Tests\Shipping\TestUtils;

uses(TestCase::class)->group('shipping', 'shipping-driver', 'shipping-driver-shipby');

uses(RefreshDatabase::class);
uses(TestUtils::class);

test('weight-based shipping uses configurable weight_unit from shipping method data', function () {
$currency = Currency::factory()->create(['default' => true]);
TaxClass::factory()->create(['default' => true]);

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

$shippingMethod = ShippingMethod::factory()->create([
'driver' => 'ship-by',
'data' => [
'charge_by' => 'weight',
'weight_unit' => 'g',
],
]);

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

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

$variant = ProductVariant::factory()->create([
'weight_value' => 300.0,
'weight_unit' => 'g',
]);
$variant->stock = 100;

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

$cart = Cart::factory()->create(['currency_id' => $currency->id]);
$cart->lines()->create([
'purchasable_type' => $variant->getMorphClass(),
'purchasable_id' => $variant->id,
'quantity' => 2,
]);
$cart = $cart->calculate();

$option = (new ShipBy)->resolve(new ShippingOptionRequest(
shippingRate: $shippingRate,
cart: $cart,
));

expect($option)->toBeInstanceOf(ShippingOption::class)
->and($option->price->value)->toEqual(500);
});

test('weight-based shipping defaults to kg when weight_unit is not set', function () {
$currency = Currency::factory()->create(['default' => true]);
TaxClass::factory()->create(['default' => true]);

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

$shippingMethod = ShippingMethod::factory()->create([
'driver' => 'ship-by',
'data' => [
'charge_by' => 'weight',
],
]);

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

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

$variant = ProductVariant::factory()->create([
'weight_value' => 3.0,
'weight_unit' => 'kg',
]);
$variant->stock = 100;

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

$cart = Cart::factory()->create(['currency_id' => $currency->id]);
$cart->lines()->create([
'purchasable_type' => $variant->getMorphClass(),
'purchasable_id' => $variant->id,
'quantity' => 2,
]);
$cart = $cart->calculate();

$option = (new ShipBy)->resolve(new ShippingOptionRequest(
shippingRate: $shippingRate,
cart: $cart,
));

expect($option)->toBeInstanceOf(ShippingOption::class)
->and($option->price->value)->toEqual(600);
});

test('weight_unit configuration persists in shipping method data', function () {
$shippingMethod = ShippingMethod::factory()->create([
'driver' => 'ship-by',
'data' => [
'charge_by' => 'weight',
'weight_unit' => 'lbs',
],
]);

$shippingMethod->refresh();

expect($shippingMethod->data['weight_unit'])->toEqual('lbs')
->and($shippingMethod->data['charge_by'])->toEqual('weight');
});

test('weight-based shipping converts product units to shipping method weight_unit with x100 scale', function () {
$currency = Currency::factory()->create(['default' => true]);
TaxClass::factory()->create(['default' => true]);

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

$shippingMethod = ShippingMethod::factory()->create([
'driver' => 'ship-by',
'data' => [
'charge_by' => 'weight',
'weight_unit' => 'kg',
],
]);

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

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

$variantKg = ProductVariant::factory()->create(['weight_value' => 2.0, 'weight_unit' => 'kg']);
$variantKg->stock = 100;
Price::factory()->create([
'price' => 500, 'min_quantity' => 1, 'currency_id' => $currency->id,
'priceable_type' => $variantKg->getMorphClass(), 'priceable_id' => $variantKg->id,
]);

$variantG = ProductVariant::factory()->create(['weight_value' => 5000.0, 'weight_unit' => 'g']);
$variantG->stock = 100;
Price::factory()->create([
'price' => 500, 'min_quantity' => 1, 'currency_id' => $currency->id,
'priceable_type' => $variantG->getMorphClass(), 'priceable_id' => $variantG->id,
]);

$cart = Cart::factory()->create(['currency_id' => $currency->id]);
$cart->lines()->createMany([
['purchasable_type' => $variantKg->getMorphClass(), 'purchasable_id' => $variantKg->id, 'quantity' => 1],
['purchasable_type' => $variantG->getMorphClass(), 'purchasable_id' => $variantG->id, 'quantity' => 1],
]);
$cart = $cart->calculate();

$option = (new ShipBy)->resolve(new ShippingOptionRequest(
shippingRate: $shippingRate,
cart: $cart,
));

expect($option)->toBeInstanceOf(ShippingOption::class)
->and($option->price->value)->toEqual(500);
});