234 lines
7.7 KiB
PHP
234 lines
7.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Unit\Domain\Services;
|
|
|
|
use Domain\Enums\SellUnit;
|
|
use Domain\Enums\TaxRate;
|
|
use Domain\Enums\Units\InvoicingUnit;
|
|
use Domain\Models\Entities\Product;
|
|
use Domain\Models\ValueObjects\DiscountValue;
|
|
use Domain\Models\ValueObjects\Entities\ProductId;
|
|
use Domain\Models\ValueObjects\ProductPricing;
|
|
use Domain\Models\ValueObjects\ProductWeight;
|
|
use Domain\Services\PricingService;
|
|
use Money\Currency;
|
|
use Money\Money;
|
|
use Ramsey\Uuid\Uuid;
|
|
|
|
function createBaseProduct(array $attributes = []): Product
|
|
{
|
|
$currency = new Currency('EUR');
|
|
|
|
$defaultAttributes = [
|
|
'id' => new ProductId(Uuid::uuid7()),
|
|
'price' => new Money(1000, $currency), // 10.00 €
|
|
'tax_rate' => TaxRate::TVA_20,
|
|
'product_weight' => null,
|
|
'invoicing_unit' => InvoicingUnit::PCE,
|
|
'sell_unit' => SellUnit::PCE,
|
|
'validDiscount' => null,
|
|
'status' => true,
|
|
'orderable_start' => null,
|
|
'orderable_end' => null,
|
|
'hasInco' => false,
|
|
];
|
|
|
|
$data = array_merge($defaultAttributes, $attributes);
|
|
|
|
return new Product(
|
|
id: $data['id'],
|
|
price: $data['price'],
|
|
tax_rate: $data['tax_rate'],
|
|
product_weight: $data['product_weight'],
|
|
invoicing_unit: $data['invoicing_unit'],
|
|
sell_unit: $data['sell_unit'],
|
|
validDiscount: $data['validDiscount'] ? clone $data['validDiscount'] : null,
|
|
status: $data['status'],
|
|
orderable_start: $data['orderable_start'],
|
|
orderable_end: $data['orderable_end'],
|
|
hasInco: $data['hasInco']
|
|
);
|
|
}
|
|
|
|
function createDiscount(Money $amount, bool $isPercentage = false, float $percentageRate = 0.0): DiscountValue
|
|
{
|
|
if ($isPercentage) {
|
|
return DiscountValue::fromPercentage((int) $percentageRate);
|
|
}
|
|
|
|
return DiscountValue::fromAmount($amount);
|
|
}
|
|
|
|
function createWeight(int $grams): ProductWeight
|
|
{
|
|
return new ProductWeight($grams, false);
|
|
}
|
|
|
|
dataset('weight_ratios', [
|
|
'500g weight on 20€/kg price' => [500, 2000, '1000'], // 20.00€ * (500/1000) = 10.00€ HT
|
|
'250g weight on 10€/kg price' => [250, 1000, '250'], // 10.00€ * (250/1000) = 2.50€ HT
|
|
'1500g weight on 10€/kg price' => [1500, 1000, '1500'], // 10.00€ * (1500/1000) = 15.00€ HT
|
|
]);
|
|
|
|
describe(PricingService::class, function (): void {
|
|
it('calculates standard pricing for a product without discount', function (): void {
|
|
// GIVEN
|
|
$currency = new Currency('EUR');
|
|
$basePrice = new Money(1000, $currency);
|
|
|
|
$product = createBaseProduct([
|
|
'price' => $basePrice,
|
|
'tax_rate' => TaxRate::TVA_20,
|
|
]);
|
|
|
|
$service = new PricingService();
|
|
|
|
// WHEN
|
|
$result = $service->calculateForProduct($product);
|
|
|
|
// THEN
|
|
expect($result)->toBeInstanceOf(ProductPricing::class)
|
|
->and($result->basePriceHt()->getAmount())->toBe('1000')
|
|
->and($result->basePriceTtc()->getAmount())->toBe('1200')
|
|
->and($result->discountAmountHt()->getAmount())->toBe('0')
|
|
->and($result->discountedPriceHt()->getAmount())->toBe('1000')
|
|
->and($result->discountedPriceTtc()->getAmount())->toBe('1200')
|
|
->and($result->discountRate)->toBe(0.0)
|
|
->and($result->hasDiscount)->toBeFalse();
|
|
});
|
|
|
|
it('calculates base price HT depending on weight when invoicing by KGM and selling by PCE', function (int $grams, int $inputPriceCents, string $expectedPriceHtCents): void {
|
|
// GIVEN
|
|
$currency = new Currency('EUR');
|
|
$product = createBaseProduct([
|
|
'price' => new Money($inputPriceCents, $currency),
|
|
'invoicing_unit' => InvoicingUnit::KGM,
|
|
'sell_unit' => SellUnit::PCE,
|
|
'product_weight' => createWeight($grams),
|
|
]);
|
|
|
|
$service = new PricingService();
|
|
|
|
// WHEN
|
|
$result = $service->calculateForProduct($product);
|
|
|
|
// THEN
|
|
expect($result->basePriceHt()->getAmount())->toBe($expectedPriceHtCents);
|
|
})->with('weight_ratios');
|
|
|
|
it('applies a fixed discount amount and calculates the discount rate', function (): void {
|
|
// GIVEN
|
|
$currency = new Currency('EUR');
|
|
$discount = createDiscount(
|
|
amount: new Money(200, $currency),
|
|
isPercentage: false
|
|
);
|
|
|
|
$product = createBaseProduct([
|
|
'price' => new Money(1000, $currency),
|
|
'validDiscount' => $discount,
|
|
]);
|
|
|
|
$service = new PricingService();
|
|
|
|
// WHEN
|
|
$result = $service->calculateForProduct($product);
|
|
|
|
// THEN
|
|
expect($result->hasDiscount)->toBeTrue()
|
|
->and($result->discountAmountHt()->getAmount())->toBe('200')
|
|
->and($result->discountedPriceHt()->getAmount())->toBe('800')
|
|
->and($result->discountedPriceTtc()->getAmount())->toBe('960')
|
|
->and($result->discountRate)->toBe(20.0);
|
|
});
|
|
|
|
it('applies a percentage discount and uses the specific rate from the discount object', function (): void {
|
|
// GIVEN
|
|
$currency = new Currency('EUR');
|
|
$discount = createDiscount(
|
|
amount: new Money(150, $currency),
|
|
isPercentage: true,
|
|
percentageRate: 15.0
|
|
);
|
|
|
|
$product = createBaseProduct([
|
|
'price' => new Money(1000, $currency),
|
|
'validDiscount' => $discount,
|
|
]);
|
|
|
|
$service = new PricingService();
|
|
|
|
// WHEN
|
|
$result = $service->calculateForProduct($product);
|
|
|
|
// THEN
|
|
expect($result->hasDiscount)->toBeTrue()
|
|
->and($result->discountAmountHt()->getAmount())->toBe('150')
|
|
->and($result->discountedPriceHt()->getAmount())->toBe('850')
|
|
->and($result->discountRate)->toBe(0.15);
|
|
});
|
|
|
|
it('calculates the virtual price per KG TTC for a PCE product with a defined weight', function (): void {
|
|
// GIVEN
|
|
$currency = new Currency('EUR');
|
|
|
|
$product = createBaseProduct([
|
|
'price' => new Money(1000, $currency),
|
|
'tax_rate' => TaxRate::TVA_20,
|
|
'invoicing_unit' => InvoicingUnit::PCE,
|
|
'product_weight' => createWeight(250),
|
|
'status' => true,
|
|
]);
|
|
|
|
$service = new PricingService();
|
|
|
|
// WHEN
|
|
$result = $service->calculateForProduct($product);
|
|
|
|
// THEN
|
|
expect($result->priceKgTtc()->getAmount())->toBe('4800');
|
|
});
|
|
|
|
it('returns standard price as price per KG TTC if the product status is false', function (): void {
|
|
// GIVEN
|
|
$currency = new Currency('EUR');
|
|
$product = createBaseProduct([
|
|
'price' => new Money(1000, $currency),
|
|
'invoicing_unit' => InvoicingUnit::PCE,
|
|
'product_weight' => createWeight(250),
|
|
'status' => false,
|
|
]);
|
|
|
|
$service = new PricingService();
|
|
|
|
// WHEN
|
|
$result = $service->calculateForProduct($product);
|
|
|
|
// THEN
|
|
expect($result->priceKgTtc()->getAmount())->toBe('1200');
|
|
});
|
|
|
|
it('returns a 0.0 discount rate if base price HT is 0', function (): void {
|
|
// GIVEN
|
|
$currency = new Currency('EUR');
|
|
$discount = createDiscount(
|
|
amount: new Money(0, $currency),
|
|
isPercentage: false
|
|
);
|
|
|
|
$product = createBaseProduct([
|
|
'price' => new Money(0, $currency),
|
|
'validDiscount' => $discount,
|
|
]);
|
|
|
|
$service = new PricingService();
|
|
|
|
// WHEN
|
|
$result = $service->calculateForProduct($product);
|
|
|
|
// THEN
|
|
expect($result->discountRate)->toBe(0.0);
|
|
});
|
|
});
|