ldc-boutique-cnc-sable/tests/Unit/Domain/Services/PricingServiceTest.php
2026-10-27 09:58:12 +01:00

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);
});
});