<?php
declare(strict_types=1);
namespace App\Order\Doctrine\Entity;
use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Order\Controller\Order\CreateController;
use App\Order\Doctrine\Repository\OrderRepository;
use App\Order\Filter\OrderFilter;
use App\Point\Doctrine\Entity\Account;
use App\Shop\Doctrine\Entity\Shop;
use DateTimeImmutable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Embedded;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\JoinColumn;
use Doctrine\ORM\Mapping\ManyToOne;
use Doctrine\ORM\Mapping\OneToMany;
use Doctrine\ORM\Mapping\Table;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints\Callback;
use Symfony\Component\Validator\Constraints\Count;
use Symfony\Component\Validator\Constraints\NotNull;
use Symfony\Component\Validator\Constraints\Valid;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
#[ApiResource(
collectionOperations: ['get', 'post' => ['controller' => CreateController::class]],
itemOperations: ['get' => ['security' => 'is_granted("item", object)']],
normalizationContext: ['groups' => ['read', 'product']],
denormalizationContext: ['groups' => ['write']],
attributes: [
'pagination_enabled' => false,
'order' => ['orderedAt' => 'DESC'],
],
)]
#[Entity(repositoryClass: OrderRepository::class)]
#[ApiFilter(OrderFilter::class)]
#[Table(name: '`order`')]
class Order
{
#[Id]
#[Column(type: 'integer')]
#[GeneratedValue]
#[Groups(['read'])]
private ?int $id = null;
#[Column(type: 'datetime_immutable')]
#[Groups(['read'])]
private DateTimeImmutable $orderedAt;
#[Column(type: 'datetime_immutable')]
#[Groups(['read'])]
private DateTimeImmutable $updatedAt;
#[ManyToOne(targetEntity: Account::class)]
#[JoinColumn(nullable: false, onDelete: 'CASCADE')]
#[ApiProperty(readableLink: false, writableLink: false)]
#[Groups(['read', 'write'])]
#[NotNull]
private Account $account;
#[ManyToOne(targetEntity: Shop::class)]
#[JoinColumn(nullable: false, onDelete: 'CASCADE')]
private Shop $shop;
#[Column]
#[Groups(['read'])]
private string $state = 'pending';
#[Column(nullable: true)]
#[Groups(['read'])]
private ?string $orderForm = null;
/**
* @var array<array-key, array{state: string, date: string}>
*/
#[Column(type: 'array')]
private array $history = [];
/**
* @var Collection<int, Line>
*/
#[OneToMany(mappedBy: 'order', targetEntity: Line::class, cascade: ['persist'])]
#[Count(min: 1)]
#[Valid]
#[Groups(['read', 'write'])]
private Collection $lines;
/**
* @var Collection<int, Detail>
*/
#[OneToMany(mappedBy: 'order', targetEntity: Detail::class, cascade: ['persist'])]
private Collection $details;
#[Embedded(class: Address::class)]
#[Valid]
#[Groups(['read', 'write'])]
private Address $deliveryAddress;
#[Embedded(class: Address::class)]
#[Valid]
#[Groups(['read', 'write'])]
private Address $billingAddress;
#[Embedded(class: Address::class)]
#[Valid]
#[Groups(['read', 'write'])]
private Address $mainAddress;
#[Column(nullable: true)]
#[Groups(['read', 'write'])]
private ?string $comment = null;
public function __construct()
{
$this->orderedAt = new DateTimeImmutable();
$this->updatedAt = new DateTimeImmutable();
$this->lines = new ArrayCollection();
$this->details = new ArrayCollection();
$this->deliveryAddress = new Address();
$this->billingAddress = new Address();
$this->mainAddress = new Address();
$this->addStateHistory('pending');
}
public function getId(): ?int
{
return $this->id;
}
public function getOrderedAt(): DateTimeImmutable
{
return $this->orderedAt;
}
public function getUpdatedAt(): DateTimeImmutable
{
return $this->updatedAt;
}
public function setUpdatedAt(DateTimeImmutable $updatedAt): void
{
$this->updatedAt = $updatedAt;
}
public function getAccount(): Account
{
return $this->account;
}
public function setAccount(Account $account): void
{
$this->account = $account;
}
public function getShop(): Shop
{
return $this->shop;
}
public function setShop(Shop $shop): void
{
$this->shop = $shop;
}
public function getState(): string
{
return $this->state;
}
public function setState(string $state): void
{
$this->state = $state;
}
/**
* @return array<array-key, array{state: string, date: string}>
*/
public function getHistory(): array
{
return $this->history;
}
public function addStateHistory(string $state): void
{
$this->history[] = ['date' => date('Y-m-d H:i:s'), 'state' => $state];
}
/**
* @return Collection<int, Line>
*/
public function getLines(): Collection
{
return $this->lines;
}
public function addLine(Line $line): void
{
$line->setOrder($this);
if (null === $line->getProduct()->getCurrentPrice()) {
throw new \Exception('Thisproduct has no current price.');
}
$line->setPrice($line->getProduct()->getCurrentPrice());
$this->lines->add($line);
}
public function removeLine(Line $line): void
{
$this->lines->removeElement($line);
}
/**
* @return Collection<int, Detail>
*/
public function getDetails(): Collection
{
return $this->details;
}
public function getTotalPrice(): int
{
return intval(array_sum($this->getLines()->map(static fn (Line $line) => $line->getTotalPrice())->toArray()));
}
public function getTotalPoints(): int
{
return intval(array_sum($this->getLines()->map(static fn (Line $line) => $line->getTotalPoints())->toArray()));
}
public function getNumberOfProducts(): int
{
return intval(array_sum($this->getLines()->map(static fn (Line $line) => $line->getQuantity())->toArray()));
}
public function getReference(): string
{
return sprintf('BCB%02d-1%04d-%d',$this->account->getProfile()->getId(), $this->id, $this->account->getId());
}
#[Callback]
public function validate(ExecutionContextInterface $context): void
{
if ($this->account->getBalance() < $this->getTotalPoints()) {
$context->buildViolation('Le montant de la commande est trop élevé.')->addViolation();
}
}
/**
* @return Collection<int, Detail>
*/
public function getDetailsToCancel(): Collection
{
return $this
->getDetails()
->filter(static fn (Detail $detail) => $detail->getState() === 'in_progress' || $detail->getState() === 'waiting_for_shipment' || $detail->getState() === 'shipped' || $detail->getState() === 'delivered');
}
/**
* @return Collection<int, Detail>
*/
public function getDetailsToProcess(): Collection
{
return $this
->getDetails()
->filter(static fn (Detail $detail) => 'in_progress' === $detail->getState());
}
/**
* @return Collection<int, Detail>
*/
public function getDetailsToShip(): Collection
{
return $this
->getDetails()
->filter(static fn (Detail $detail) => 'waiting_for_shipment' === $detail->getState());
}
/**
* @return Collection<int, Detail>
*/
public function getDetailsToDeliver(): Collection
{
return $this
->getDetails()
->filter(static fn (Detail $detail) => 'waiting_for_shipment' === $detail->getState()
|| 'shipped' === $detail->getState());
}
public function getDeliveryAddress(): Address
{
return $this->deliveryAddress;
}
public function setDeliveryAddress(Address $deliveryAddress): void
{
$this->deliveryAddress = $deliveryAddress;
}
public function getBillingAddress(): Address
{
return $this->billingAddress;
}
public function setBillingAddress(Address $billingAddress): void
{
$this->billingAddress = $billingAddress;
}
public function getMainAddress(): Address
{
return $this->mainAddress;
}
public function setMainAddress(Address $mainAddress): void
{
$this->mainAddress = $mainAddress;
}
public function getTotalQuantity(): int
{
return array_sum($this->getLines()->map(static fn (Line $line) => $line->getQuantity())->toArray());
}
public function getOrderForm(): ?string
{
return $this->orderForm;
}
public function setOrderForm(?string $orderForm): void
{
$this->orderForm = $orderForm;
}
public function getComment(): ?string
{
return $this->comment;
}
public function setComment(?string $comment): void
{
$this->comment = $comment;
}
}