ldc-boutique-cnc-sable/domain/Models/Entities/Basket.php

175 lines
4.5 KiB
PHP

<?php
declare(strict_types=1);
namespace Domain\Models\Entities;
use Domain\Models\ValueObjects\BasketProduct;
use Domain\Models\ValueObjects\Entities\BasketId;
use Domain\Models\ValueObjects\Entities\ProductId;
use Money\Money;
use Shared\DDD\Entity;
/** @implements \Shared\DDD\Entity<\Domain\Models\ValueObjects\Entities\BasketId> */
final class Basket implements Entity
{
/**
* @var array<string,\Domain\Models\ValueObjects\BasketProduct>
*/
private array $items = [];
/**
* @param array<array-key,\Domain\Models\ValueObjects\BasketProduct> $items
*/
public function __construct(
public readonly BasketId $id,
array $items = []
) {
foreach ($items as $item) {
if (! $item instanceof BasketProduct) {
throw new \DomainException('All items shall be of type BasketProduct');
}
$this->items[(string) $item->id->value] = $item;
}
}
#[\Override]
public function id(): BasketId
{
return $this->id;
}
public function has(ProductId $itemId): bool
{
return array_key_exists((string) $itemId->value, $this->items);
}
public function addOrUpdate(BasketProduct $item): void
{
if ($this->has($item->id)) {
$existingItem = $this->items[(string) $item->id->value];
$updatedItem = $this->cloneWithQuantity(
$existingItem,
$existingItem->qty + $item->qty
);
$this->items[(string) $item->id->value] = $updatedItem;
return;
}
$this->items[(string) $item->id->value] = $item;
}
public function remove(ProductId $itemId, int $qty): void
{
if ($qty < 1) {
throw new \DomainException('Cannot remove quantity less than 1');
}
if (! $this->has($itemId)) {
throw new \DomainException('Basket item does not exists');
}
$existingItem = $this->items[(string) $itemId->value];
if ($existingItem->qty <= $qty) {
$this->removeLine($itemId);
return;
}
$updatedItem = $this->cloneWithQuantity(
$existingItem,
$existingItem->qty - $qty
);
$this->items[(string) $itemId->value] = $updatedItem;
}
public function removeLine(ProductId $itemId): void
{
if (! $this->has($itemId)) {
throw new \DomainException('Basket item does not exists');
}
unset($this->items[(string) $itemId->value]);
}
/**
* @return array<string,\Domain\Models\ValueObjects\BasketProduct>
*/
public function items(): array
{
return $this->items;
}
public function totalTaxIncluded(): Money
{
return array_reduce(
$this->items,
fn (Money $total, BasketProduct $item) => $total->add($item->totalTtc()),
Money::EUR(0)
);
}
public function totalVat(): Money
{
return array_reduce(
$this->items,
fn (Money $total, BasketProduct $item) => $total->add($item->taxTotal()),
Money::EUR(0)
);
}
public function totalRaw(): Money
{
return array_reduce(
$this->items,
fn (Money $total, BasketProduct $item) => $total->add($item->subtotalHt()),
Money::EUR(0)
);
}
public function totalDiscount(): Money
{
return array_reduce(
$this->items,
fn (Money $total, BasketProduct $item) => $total->add($item->discountTotal()),
Money::EUR(0)
);
}
public function totalWeight(): int
{
return (int) array_sum(array_map(fn (BasketProduct $item) => $item->weightTotal(), $this->items));
}
public function countItems(): int
{
return (int) array_sum(array_map(fn (BasketProduct $item) => $item->qty, $this->items));
}
private function cloneWithQuantity(BasketProduct $item, int $qty): BasketProduct
{
return new BasketProduct(
$item->id,
$item->name,
$qty,
$item->unitPriceHt,
$item->unitDiscount,
$item->taxRate,
$item->weightGrams,
$item->brand,
$item->incoId,
$item->orderUnit,
$item->slug,
$item->picture,
$item->description,
$item->weightVariable,
$item->discountRate,
$item->priceKgTtc,
[...$item->options]
);
}
}