2026-05-14 04:28:06 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace App\Domain\Article;
|
|
|
|
|
|
|
|
|
|
use Doctrine\Common\Collections\ArrayCollection;
|
|
|
|
|
use Doctrine\Common\Collections\Collection;
|
|
|
|
|
use Doctrine\ORM\Mapping as ORM;
|
|
|
|
|
use Symfony\Component\Uid\Uuid;
|
|
|
|
|
|
|
|
|
|
#[ORM\Entity]
|
|
|
|
|
#[ORM\Table(name: 'articles', schema: 'app')]
|
|
|
|
|
class Article
|
|
|
|
|
{
|
|
|
|
|
#[ORM\Id]
|
|
|
|
|
#[ORM\Column(type: 'uuid')]
|
|
|
|
|
private Uuid $id;
|
|
|
|
|
|
|
|
|
|
#[ORM\ManyToOne(targetEntity: ArticleType::class)]
|
|
|
|
|
#[ORM\JoinColumn(nullable: false)]
|
|
|
|
|
private ArticleType $articleType;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column(type: 'string', length: 255, unique: true)]
|
|
|
|
|
private string $sku;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column(type: 'string', length: 100, unique: true)]
|
|
|
|
|
private string $inventoryNumber;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column(type: 'string', enumType: ArticleStatus::class)]
|
|
|
|
|
private ArticleStatus $status;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column(type: 'integer')]
|
|
|
|
|
private int $stock;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column(type: 'string', enumType: ArticleCondition::class)]
|
|
|
|
|
private ArticleCondition $condition;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column(type: 'text', nullable: true)]
|
|
|
|
|
private ?string $conditionNotes = null;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column(type: 'decimal', precision: 10, scale: 2, nullable: true)]
|
|
|
|
|
private ?string $listingPrice = null;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column(type: 'string', length: 255, nullable: true)]
|
|
|
|
|
private ?string $serialNumber = null;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column(type: 'string', length: 255, nullable: true)]
|
|
|
|
|
private ?string $ebayListingId = null;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column(type: 'text', nullable: true)]
|
|
|
|
|
private ?string $ebayTitle = null;
|
|
|
|
|
|
|
|
|
|
#[ORM\Column(type: 'text', nullable: true)]
|
|
|
|
|
private ?string $ebayDescription = null;
|
|
|
|
|
|
|
|
|
|
/** @var Collection<int, AttributeValue> */
|
|
|
|
|
#[ORM\OneToMany(mappedBy: 'article', targetEntity: AttributeValue::class, cascade: ['persist', 'remove'])]
|
|
|
|
|
private Collection $attributeValues;
|
|
|
|
|
|
|
|
|
|
/** @var Collection<int, ArticlePhoto> */
|
2026-05-14 04:34:32 +00:00
|
|
|
#[ORM\OneToMany(mappedBy: 'article', targetEntity: ArticlePhoto::class, cascade: ['persist', 'remove'])]
|
|
|
|
|
#[ORM\OrderBy(['sortOrder' => 'ASC'])]
|
2026-05-14 04:28:06 +00:00
|
|
|
private Collection $photos;
|
|
|
|
|
|
|
|
|
|
public function __construct(
|
|
|
|
|
ArticleType $articleType,
|
|
|
|
|
string $sku,
|
|
|
|
|
string $inventoryNumber,
|
|
|
|
|
int $stock,
|
|
|
|
|
ArticleCondition $condition,
|
|
|
|
|
) {
|
|
|
|
|
$this->id = Uuid::v7();
|
|
|
|
|
$this->articleType = $articleType;
|
|
|
|
|
$this->sku = $sku;
|
|
|
|
|
$this->inventoryNumber = $inventoryNumber;
|
|
|
|
|
$this->status = ArticleStatus::Ingesting;
|
|
|
|
|
$this->stock = $stock;
|
|
|
|
|
$this->condition = $condition;
|
|
|
|
|
$this->attributeValues = new ArrayCollection();
|
|
|
|
|
$this->photos = new ArrayCollection();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function transitionTo(ArticleStatus $newStatus): void
|
|
|
|
|
{
|
|
|
|
|
if (!$this->status->canTransitionTo($newStatus)) {
|
|
|
|
|
throw new \DomainException(\sprintf(
|
|
|
|
|
'Cannot transition from %s to %s',
|
|
|
|
|
$this->status->value,
|
|
|
|
|
$newStatus->value,
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
$this->status = $newStatus;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function decrementStock(): void
|
|
|
|
|
{
|
|
|
|
|
if ($this->stock <= 0) {
|
|
|
|
|
throw new \DomainException('Stock cannot go below zero');
|
|
|
|
|
}
|
|
|
|
|
--$this->stock;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function isOutOfStock(): bool { return 0 === $this->stock; }
|
|
|
|
|
|
|
|
|
|
public function getMainPhoto(): ?ArticlePhoto
|
|
|
|
|
{
|
|
|
|
|
foreach ($this->photos as $photo) {
|
|
|
|
|
if ($photo->isMain()) {
|
|
|
|
|
return $photo;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function getId(): Uuid { return $this->id; }
|
|
|
|
|
public function getArticleType(): ArticleType { return $this->articleType; }
|
|
|
|
|
public function getSku(): string { return $this->sku; }
|
|
|
|
|
public function getInventoryNumber(): string { return $this->inventoryNumber; }
|
|
|
|
|
public function getStatus(): ArticleStatus { return $this->status; }
|
|
|
|
|
public function getStock(): int { return $this->stock; }
|
|
|
|
|
public function getCondition(): ArticleCondition { return $this->condition; }
|
|
|
|
|
public function getConditionNotes(): ?string { return $this->conditionNotes; }
|
|
|
|
|
public function getListingPrice(): ?string { return $this->listingPrice; }
|
|
|
|
|
public function getSerialNumber(): ?string { return $this->serialNumber; }
|
|
|
|
|
public function getEbayListingId(): ?string { return $this->ebayListingId; }
|
|
|
|
|
public function getEbayTitle(): ?string { return $this->ebayTitle; }
|
|
|
|
|
public function getEbayDescription(): ?string { return $this->ebayDescription; }
|
|
|
|
|
|
|
|
|
|
/** @return Collection<int, AttributeValue> */
|
|
|
|
|
public function getAttributeValues(): Collection { return $this->attributeValues; }
|
|
|
|
|
|
|
|
|
|
/** @return Collection<int, ArticlePhoto> */
|
|
|
|
|
public function getPhotos(): Collection { return $this->photos; }
|
|
|
|
|
|
|
|
|
|
public function setConditionNotes(?string $notes): void { $this->conditionNotes = $notes; }
|
|
|
|
|
public function setListingPrice(?string $price): void { $this->listingPrice = $price; }
|
|
|
|
|
public function setSerialNumber(?string $sn): void { $this->serialNumber = $sn; }
|
|
|
|
|
public function setEbayListingId(?string $id): void { $this->ebayListingId = $id; }
|
|
|
|
|
public function setEbayTitle(?string $title): void { $this->ebayTitle = $title; }
|
|
|
|
|
public function setEbayDescription(?string $desc): void { $this->ebayDescription = $desc; }
|
|
|
|
|
}
|