296 lines
10 KiB
PHP
296 lines
10 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Unit\Domain\Services;
|
|
|
|
use Carbon\CarbonImmutable;
|
|
use Domain\Enums\OrderStatus;
|
|
use Domain\Enums\ProductBrand;
|
|
use Domain\Enums\TaxRate;
|
|
use Domain\Enums\Units\OrderUnit;
|
|
use Domain\Models\Entities\Basket;
|
|
use Domain\Models\Entities\Order;
|
|
use Domain\Models\Entities\Shop;
|
|
use Domain\Models\Entities\Shopper;
|
|
use Domain\Models\ValueObjects\BasketProduct;
|
|
use Domain\Models\ValueObjects\DeliveryAddress;
|
|
use Domain\Models\ValueObjects\DiscountRate;
|
|
use Domain\Models\ValueObjects\Email;
|
|
use Domain\Models\ValueObjects\Entities\BasketId;
|
|
use Domain\Models\ValueObjects\Entities\OrderId;
|
|
use Domain\Models\ValueObjects\Entities\ProductId;
|
|
use Domain\Models\ValueObjects\Entities\ShopId;
|
|
use Domain\Models\ValueObjects\Entities\ShopperId;
|
|
use Domain\Models\ValueObjects\IncoId;
|
|
use Domain\Models\ValueObjects\Matricule;
|
|
use Domain\Models\ValueObjects\OrderEmployee;
|
|
use Domain\Models\ValueObjects\OrderLine;
|
|
use Domain\Models\ValueObjects\OrderLineProduct;
|
|
use Domain\Models\ValueObjects\OrderReference;
|
|
use Domain\Models\ValueObjects\PickupTimeSlot;
|
|
use Domain\Models\ValueObjects\ProductWeight;
|
|
use Domain\Models\ValueObjects\ShopperEstablishmentCode;
|
|
use Domain\Models\ValueObjects\ShopperServiceCode;
|
|
use Domain\Models\ValueObjects\ShopperSiteCode;
|
|
use Domain\Ports\Repositories\OrderRepository;
|
|
use Domain\Services\OrderService;
|
|
use Mockery;
|
|
use Money\Money;
|
|
use Ramsey\Uuid\Uuid;
|
|
use Shared\Helpers\UuidHelper;
|
|
|
|
describe('Order Cancellation', function () {
|
|
it('cancels a validated order successfully', function () {
|
|
// GIVEN
|
|
$orderId = new OrderId(Uuid::uuid7());
|
|
|
|
$order = new Order(
|
|
id: $orderId,
|
|
shopId: new ShopId(Uuid::uuid7()),
|
|
reference: new OrderReference(Uuid::uuid7()),
|
|
employee: new OrderEmployee(
|
|
new Matricule('1234567'),
|
|
'Jean',
|
|
'Dupont',
|
|
new ShopperEstablishmentCode('ETAB01'),
|
|
new ShopperSiteCode('SITEA'),
|
|
new ShopperServiceCode('101')
|
|
),
|
|
deliveryAddress: new DeliveryAddress(
|
|
'Cocotte Lyon',
|
|
'12 rue de la Paix',
|
|
'69001',
|
|
'Lyon',
|
|
'0478123456'
|
|
),
|
|
pickupTimeSlot: new PickupTimeSlot(
|
|
new \DateTimeImmutable('2023-12-01 10:00:00'),
|
|
new \DateInterval('PT10M')
|
|
),
|
|
status: OrderStatus::VALIDATED,
|
|
total: Money::EUR(1899),
|
|
lines: [
|
|
new OrderLine(
|
|
product: new OrderLineProduct(
|
|
incoId: new IncoId(45),
|
|
originalId: new ProductId(Uuid::uuid7()),
|
|
name: 'Poulet Rôti',
|
|
weight: new ProductWeight(1200, false),
|
|
brand: ProductBrand::marie,
|
|
orderUnit: OrderUnit::PCE,
|
|
price: Money::EUR(1000),
|
|
discountedPrice: Money::EUR(900),
|
|
priceKgTtc: Money::EUR(1500),
|
|
taxRate: TaxRate::TVA_5_5,
|
|
),
|
|
quantity: 2,
|
|
unitPriceHt: Money::EUR(1000),
|
|
unitDiscount: Money::EUR(100),
|
|
unitTax: Money::EUR(50),
|
|
totalHt: Money::EUR(2000),
|
|
totalDiscount: Money::EUR(200),
|
|
totalTax: Money::EUR(99),
|
|
totalTtc: Money::EUR(1899)
|
|
),
|
|
]
|
|
);
|
|
|
|
/** @var OrderRepository|\Mockery\MockInterface $repository */
|
|
$repository = Mockery::mock(OrderRepository::class);
|
|
$repository->shouldReceive('find')->with($orderId)->andReturn($order);
|
|
$repository->shouldReceive('persist')->with($order)->once();
|
|
|
|
$service = new OrderService($repository);
|
|
|
|
// WHEN
|
|
$service->cancelOrder($orderId);
|
|
|
|
// THEN
|
|
expect($order->status)->toBe(OrderStatus::CANCELED);
|
|
});
|
|
|
|
it('throws exception if order not found', function () {
|
|
// GIVEN
|
|
$orderId = new OrderId(Uuid::uuid7());
|
|
|
|
/** @var OrderRepository|\Mockery\MockInterface $repository */
|
|
$repository = Mockery::mock(OrderRepository::class);
|
|
$repository->shouldReceive('find')->with($orderId)->andReturn(null);
|
|
|
|
$service = new OrderService($repository);
|
|
|
|
// WHEN
|
|
$action = fn () => $service->cancelOrder($orderId);
|
|
|
|
// THEN
|
|
expect($action)->toThrow(\DomainException::class, 'Order not found');
|
|
});
|
|
});
|
|
|
|
describe('Order Creation', function () {
|
|
it('creates an order from a basket correctly', function () {
|
|
// GIVEN
|
|
$basketId = new BasketId(Uuid::uuid7());
|
|
$basket = new Basket($basketId);
|
|
|
|
$basketProduct = new BasketProduct(
|
|
id: new ProductId(Uuid::uuid7()),
|
|
name: 'Poulet Rôti',
|
|
qty: 2,
|
|
unitPriceHt: Money::EUR(1000),
|
|
unitDiscount: Money::EUR(100),
|
|
taxRate: TaxRate::TVA_5_5,
|
|
weightGrams: 1200,
|
|
brand: ProductBrand::marie,
|
|
incoId: new IncoId(45),
|
|
orderUnit: OrderUnit::PCE,
|
|
slug: 'poulet-roti',
|
|
picture: 'images/poulet.jpg',
|
|
description: 'Un bon poulet',
|
|
weightVariable: false,
|
|
discountRate: DiscountRate::fromRatio(0.1),
|
|
priceKgTtc: Money::EUR(1500)
|
|
);
|
|
$basket->addOrUpdate($basketProduct);
|
|
|
|
$pickupTimeSlot = new PickupTimeSlot(
|
|
new \DateTimeImmutable('2023-12-01 10:00:00'),
|
|
new \DateInterval('PT10M')
|
|
);
|
|
|
|
$shopper = new Shopper(
|
|
id: new ShopperId(Uuid::uuid7()),
|
|
email: new Email('jean.dupont@example.com'),
|
|
matricule: new Matricule('1234567'),
|
|
nom: 'Dupont',
|
|
prenom: 'Jean',
|
|
startDate: CarbonImmutable::now(),
|
|
endDate: null,
|
|
etablissement: new ShopperEstablishmentCode('ETAB01'),
|
|
site: new ShopperSiteCode('SITEA'),
|
|
service: new ShopperServiceCode('101')
|
|
);
|
|
|
|
$shop = new Shop(
|
|
id: new ShopId(Uuid::uuid7()),
|
|
name: 'Cocotte Lyon',
|
|
address: '12 rue de la Paix',
|
|
postcode: '69001',
|
|
city: 'Lyon',
|
|
phone: '0478123456'
|
|
);
|
|
|
|
/** @var OrderRepository|\Mockery\MockInterface $repository */
|
|
$repository = Mockery::mock(OrderRepository::class);
|
|
|
|
$service = new OrderService($repository);
|
|
|
|
// WHEN
|
|
$order = $service->createOrderFromBasket($basket, $pickupTimeSlot, $shopper, $shop);
|
|
|
|
// THEN
|
|
expect($order)->toBeInstanceOf(Order::class);
|
|
expect($order->status)->toBe(OrderStatus::VALIDATED);
|
|
expect(UuidHelper::isV7($order->reference->value))->toBeTrue();
|
|
|
|
// * Employee
|
|
$employee = $order->employee;
|
|
expect($employee->matricule->value)->toBe('1234567');
|
|
expect($employee->firstname)->toBe('Jean');
|
|
expect($employee->lastname)->toBe('Dupont');
|
|
expect($employee->facility->value)->toBe('ETAB01');
|
|
expect($employee->site->value)->toBe('SITEA');
|
|
expect($employee->service->value)->toBe('101');
|
|
|
|
// * Delivery Address
|
|
$delivery = $order->deliveryAddress;
|
|
expect($delivery->name)->toBe('Cocotte Lyon');
|
|
expect($delivery->address)->toBe('12 rue de la Paix');
|
|
expect($delivery->postcode)->toBe('69001');
|
|
expect($delivery->city)->toBe('Lyon');
|
|
expect($delivery->phone)->toBe('0478123456');
|
|
|
|
// * Total
|
|
expect($order->getTotal()->getAmount())->toBe('1899');
|
|
|
|
// * Lines
|
|
$lines = $order->getLines();
|
|
expect($lines)->toHaveCount(1);
|
|
$line = $lines[0];
|
|
expect($line->quantity)->toBe(2);
|
|
|
|
expect($line->unitPriceHt->getAmount())->toBe('1000');
|
|
expect($line->unitDiscount->getAmount())->toBe('100');
|
|
expect($line->unitTtc->getAmount())->toBe('950');
|
|
expect($line->totalHt->getAmount())->toBe('1800');
|
|
expect($line->totalDiscount->getAmount())->toBe('200');
|
|
expect($line->totalTtc->getAmount())->toBe('1899');
|
|
|
|
// * product mapping
|
|
$product = $line->product;
|
|
expect($product->name)->toBe('Poulet Rôti');
|
|
expect($product->weight->toGrams())->toBe(1200);
|
|
expect($product->brand)->toBe(ProductBrand::marie);
|
|
expect($product->incoId->value)->toBe(45);
|
|
expect($product->orderUnit)->toBe(OrderUnit::PCE);
|
|
expect($product->picture)->toBe('images/poulet.jpg');
|
|
});
|
|
|
|
it('throws exception if basket product has no incoId', function () {
|
|
// GIVEN
|
|
$basketId = new BasketId(Uuid::uuid7());
|
|
$basket = new Basket($basketId);
|
|
|
|
$basketProduct = new BasketProduct(
|
|
id: new ProductId(Uuid::uuid7()),
|
|
name: 'Poulet Rôti',
|
|
qty: 2,
|
|
unitPriceHt: Money::EUR(1000),
|
|
unitDiscount: Money::EUR(100),
|
|
taxRate: TaxRate::TVA_5_5,
|
|
weightGrams: 1200,
|
|
brand: ProductBrand::marie,
|
|
incoId: null, // NO INCO ID
|
|
orderUnit: OrderUnit::PCE,
|
|
slug: 'poulet-roti',
|
|
picture: null,
|
|
description: null,
|
|
weightVariable: false,
|
|
discountRate: DiscountRate::fromRatio(0.1),
|
|
priceKgTtc: Money::EUR(1500)
|
|
);
|
|
$basket->addOrUpdate($basketProduct);
|
|
|
|
$pickupTimeSlot = new PickupTimeSlot(new \DateTimeImmutable(), new \DateInterval('PT10M'));
|
|
$shopper = new Shopper(
|
|
id: new ShopperId(Uuid::uuid7()),
|
|
email: new Email('a@a.com'),
|
|
matricule: new Matricule('1234567'),
|
|
nom: 'A',
|
|
prenom: 'B',
|
|
startDate: CarbonImmutable::now(),
|
|
endDate: null,
|
|
etablissement: new ShopperEstablishmentCode('ETAB01'),
|
|
site: new ShopperSiteCode('SITEA'),
|
|
service: new ShopperServiceCode('101')
|
|
);
|
|
$shop = new Shop(
|
|
id: new ShopId(Uuid::uuid7()),
|
|
name: 'S',
|
|
address: 'A',
|
|
postcode: 'P',
|
|
city: 'C'
|
|
);
|
|
|
|
$repository = Mockery::mock(OrderRepository::class);
|
|
|
|
$service = new OrderService($repository);
|
|
|
|
// WHEN
|
|
$action = fn () => $service->createOrderFromBasket($basket, $pickupTimeSlot, $shopper, $shop);
|
|
|
|
// THEN
|
|
expect($action)->toThrow(\DomainException::class, 'incoId cannot be null for an order');
|
|
});
|
|
});
|