nochmal nue
This commit is contained in:
parent
2c2e34b71e
commit
6f07d70436
27 changed files with 311 additions and 1988 deletions
92
migrations/Version20250609175837.php
Normal file
92
migrations/Version20250609175837.php
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20250609175837 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE TABLE drink_type (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, created_at DATETIME NOT NULL --(DC2Type:datetime_immutable)
|
||||||
|
, updated_at DATETIME NOT NULL --(DC2Type:datetime_immutable)
|
||||||
|
, name VARCHAR(255) NOT NULL, description CLOB DEFAULT NULL, desired_stock INTEGER NOT NULL)
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE UNIQUE INDEX UNIQ_841484B15E237E06 ON drink_type (name)
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE TABLE inventory_record (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, drink_type_id INTEGER NOT NULL, quantity INTEGER NOT NULL, timestamp DATETIME NOT NULL --(DC2Type:datetime_immutable)
|
||||||
|
, created_at DATETIME NOT NULL --(DC2Type:datetime_immutable)
|
||||||
|
, updated_at DATETIME NOT NULL --(DC2Type:datetime_immutable)
|
||||||
|
, CONSTRAINT FK_9BE8033AE7E8D8A1 FOREIGN KEY (drink_type_id) REFERENCES drink_type (id) NOT DEFERRABLE INITIALLY IMMEDIATE)
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE INDEX IDX_9BE8033AE7E8D8A1 ON inventory_record (drink_type_id)
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE TABLE "order" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, created_at DATETIME NOT NULL --(DC2Type:datetime_immutable)
|
||||||
|
, updated_at DATETIME NOT NULL --(DC2Type:datetime_immutable)
|
||||||
|
, status VARCHAR(255) DEFAULT 'new' NOT NULL)
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE TABLE order_item (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, drink_type_id INTEGER NOT NULL, order_id INTEGER NOT NULL, created_at DATETIME NOT NULL --(DC2Type:datetime_immutable)
|
||||||
|
, updated_at DATETIME NOT NULL --(DC2Type:datetime_immutable)
|
||||||
|
, quantity INTEGER NOT NULL, CONSTRAINT FK_52EA1F09E7E8D8A1 FOREIGN KEY (drink_type_id) REFERENCES drink_type (id) NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_52EA1F098D9F6D38 FOREIGN KEY (order_id) REFERENCES "order" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE INDEX IDX_52EA1F09E7E8D8A1 ON order_item (drink_type_id)
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE INDEX IDX_52EA1F098D9F6D38 ON order_item (order_id)
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE TABLE property_change_log (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, property_name VARCHAR(255) NOT NULL, entity_class VARCHAR(255) NOT NULL, entity_id INTEGER DEFAULT NULL, new_value VARCHAR(255) NOT NULL, change_date DATETIME NOT NULL --(DC2Type:datetime_immutable)
|
||||||
|
)
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE TABLE system_config (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, created_at DATETIME NOT NULL --(DC2Type:datetime_immutable)
|
||||||
|
, updated_at DATETIME NOT NULL --(DC2Type:datetime_immutable)
|
||||||
|
, "key" VARCHAR(255) NOT NULL, value CLOB NOT NULL)
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
CREATE UNIQUE INDEX UNIQ_C4049ABD8A90ABA9 ON system_config ("key")
|
||||||
|
SQL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
DROP TABLE drink_type
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
DROP TABLE inventory_record
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
DROP TABLE "order"
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
DROP TABLE order_item
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
DROP TABLE property_change_log
|
||||||
|
SQL);
|
||||||
|
$this->addSql(<<<'SQL'
|
||||||
|
DROP TABLE system_config
|
||||||
|
SQL);
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,9 +14,6 @@ use Symfony\Component\Routing\Attribute\Route;
|
||||||
#[Route(path: '/', name: 'app_index')]
|
#[Route(path: '/', name: 'app_index')]
|
||||||
final class Index extends AbstractController
|
final class Index extends AbstractController
|
||||||
{
|
{
|
||||||
public function __construct(
|
|
||||||
private readonly InventoryService $inventoryService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function __invoke(): Response
|
public function __invoke(): Response
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,7 +9,6 @@ use DateTimeImmutable;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use App\Entity\PropertyChangeLog;
|
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: DrinkTypeRepository::class)]
|
#[ORM\Entity(repositoryClass: DrinkTypeRepository::class)]
|
||||||
#[ORM\Table(name: 'drink_type')]
|
#[ORM\Table(name: 'drink_type')]
|
||||||
|
@ -26,9 +25,6 @@ class DrinkType
|
||||||
#[ORM\Column(type: 'datetime_immutable')]
|
#[ORM\Column(type: 'datetime_immutable')]
|
||||||
private DateTimeImmutable $updatedAt;
|
private DateTimeImmutable $updatedAt;
|
||||||
|
|
||||||
#[ORM\OneToMany(mappedBy: 'drinkType', targetEntity: InventoryRecord::class)]
|
|
||||||
private Collection $inventoryRecords;
|
|
||||||
|
|
||||||
#[ORM\OneToMany(mappedBy: 'drinkType', targetEntity: OrderItem::class)]
|
#[ORM\OneToMany(mappedBy: 'drinkType', targetEntity: OrderItem::class)]
|
||||||
private Collection $orderItems;
|
private Collection $orderItems;
|
||||||
|
|
||||||
|
@ -37,16 +33,29 @@ class DrinkType
|
||||||
#[ORM\Column(type: 'text', nullable: true)]
|
#[ORM\Column(type: 'text', nullable: true)]
|
||||||
private null|string $description = null;
|
private null|string $description = null;
|
||||||
#[ORM\Column(type: 'integer')]
|
#[ORM\Column(type: 'integer')]
|
||||||
private int $desiredStock = 10;
|
private int $wantedStock = 10;
|
||||||
|
|
||||||
|
#[ORM\Column(type: 'integer')]
|
||||||
|
private int $currentStock = 0;
|
||||||
|
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
) {
|
) {
|
||||||
$this->createdAt = new DateTimeImmutable();
|
$this->createdAt = new DateTimeImmutable();
|
||||||
$this->updatedAt = new DateTimeImmutable();
|
$this->updatedAt = new DateTimeImmutable();
|
||||||
$this->inventoryRecords = new ArrayCollection();
|
|
||||||
$this->orderItems = new ArrayCollection();
|
$this->orderItems = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getCurrentStock(): int
|
||||||
|
{
|
||||||
|
return $this->currentStock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setCurrentStock(int $currentStock): void
|
||||||
|
{
|
||||||
|
$this->currentStock = $currentStock;
|
||||||
|
}
|
||||||
|
|
||||||
public function getId(): null|int
|
public function getId(): null|int
|
||||||
{
|
{
|
||||||
return $this->id;
|
return $this->id;
|
||||||
|
@ -60,7 +69,6 @@ class DrinkType
|
||||||
public function setName(string $name): self
|
public function setName(string $name): self
|
||||||
{
|
{
|
||||||
$this->name = $name;
|
$this->name = $name;
|
||||||
$this->updateTimestamp();
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,22 +80,21 @@ class DrinkType
|
||||||
public function setDescription(null|string $description): self
|
public function setDescription(null|string $description): self
|
||||||
{
|
{
|
||||||
$this->description = $description;
|
$this->description = $description;
|
||||||
$this->updateTimestamp();
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDesiredStock(): int
|
public function getWantedStock(): int
|
||||||
{
|
{
|
||||||
return $this->desiredStock;
|
return $this->wantedStock;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setDesiredStock(int $desiredStock): self
|
public function setWantedStock(int $wantedStock): self
|
||||||
{
|
{
|
||||||
$this->desiredStock = $desiredStock;
|
$this->wantedStock = $wantedStock;
|
||||||
$this->updateTimestamp();
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function getCreatedAt(): DateTimeImmutable
|
public function getCreatedAt(): DateTimeImmutable
|
||||||
{
|
{
|
||||||
return $this->createdAt;
|
return $this->createdAt;
|
||||||
|
@ -98,16 +105,12 @@ class DrinkType
|
||||||
return $this->updatedAt;
|
return $this->updatedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getInventoryRecords(): Collection
|
|
||||||
{
|
|
||||||
return $this->inventoryRecords;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getOrderItems(): Collection
|
public function getOrderItems(): Collection
|
||||||
{
|
{
|
||||||
return $this->orderItems;
|
return $this->orderItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[ORM\PrePersist]
|
||||||
private function updateTimestamp(): void
|
private function updateTimestamp(): void
|
||||||
{
|
{
|
||||||
$this->updatedAt = new DateTimeImmutable();
|
$this->updatedAt = new DateTimeImmutable();
|
||||||
|
|
|
@ -1,98 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Entity;
|
|
||||||
|
|
||||||
use App\Repository\InventoryRecordRepository;
|
|
||||||
use DateTimeImmutable;
|
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: InventoryRecordRepository::class)]
|
|
||||||
#[ORM\Table(name: 'inventory_record')]
|
|
||||||
class InventoryRecord
|
|
||||||
{
|
|
||||||
#[ORM\Id]
|
|
||||||
#[ORM\GeneratedValue]
|
|
||||||
#[ORM\Column(type: 'integer')]
|
|
||||||
private null|int $id = null;
|
|
||||||
|
|
||||||
#[ORM\ManyToOne(targetEntity: DrinkType::class, inversedBy: 'inventoryRecords')]
|
|
||||||
#[ORM\JoinColumn(name: 'drink_type_id', referencedColumnName: 'id', nullable: false)]
|
|
||||||
private DrinkType $drinkType;
|
|
||||||
|
|
||||||
#[ORM\Column(type: 'integer')]
|
|
||||||
private int $quantity;
|
|
||||||
|
|
||||||
#[ORM\Column(type: 'datetime_immutable')]
|
|
||||||
private DateTimeImmutable $timestamp;
|
|
||||||
|
|
||||||
#[ORM\Column(type: 'datetime_immutable')]
|
|
||||||
private DateTimeImmutable $createdAt;
|
|
||||||
|
|
||||||
#[ORM\Column(type: 'datetime_immutable')]
|
|
||||||
private DateTimeImmutable $updatedAt;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
) {
|
|
||||||
$this->createdAt = new DateTimeImmutable();
|
|
||||||
$this->updatedAt = new DateTimeImmutable();
|
|
||||||
$this->timestamp = new DateTimeImmutable();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getId(): null|int
|
|
||||||
{
|
|
||||||
return $this->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDrinkType(): DrinkType
|
|
||||||
{
|
|
||||||
return $this->drinkType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setDrinkType(DrinkType $drinkType): self
|
|
||||||
{
|
|
||||||
$this->drinkType = $drinkType;
|
|
||||||
$this->updateTimestamp();
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getQuantity(): int
|
|
||||||
{
|
|
||||||
return $this->quantity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setQuantity(int $quantity): self
|
|
||||||
{
|
|
||||||
$this->quantity = $quantity;
|
|
||||||
$this->updateTimestamp();
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getTimestamp(): DateTimeImmutable
|
|
||||||
{
|
|
||||||
return $this->timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setTimestamp(DateTimeImmutable $timestamp): self
|
|
||||||
{
|
|
||||||
$this->timestamp = $timestamp;
|
|
||||||
$this->updateTimestamp();
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCreatedAt(): DateTimeImmutable
|
|
||||||
{
|
|
||||||
return $this->createdAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUpdatedAt(): DateTimeImmutable
|
|
||||||
{
|
|
||||||
return $this->updatedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function updateTimestamp(): void
|
|
||||||
{
|
|
||||||
$this->updatedAt = new DateTimeImmutable();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,7 +14,7 @@ use Doctrine\ORM\Mapping as ORM;
|
||||||
class SystemConfig
|
class SystemConfig
|
||||||
{
|
{
|
||||||
public const string DEFAULT_STOCK_ADJUSTMENT_LOOKBACK_ORDERS = '3';
|
public const string DEFAULT_STOCK_ADJUSTMENT_LOOKBACK_ORDERS = '3';
|
||||||
public const string DEFAULT_DEFAULT_DESIRED_STOCK = '2';
|
public const string DEFAULT_DEFAULT_WANTED_STOCK = '2';
|
||||||
public const string DEFAULT_SYSTEM_NAME = 'Zaufen';
|
public const string DEFAULT_SYSTEM_NAME = 'Zaufen';
|
||||||
public const string DEFAULT_STOCK_INCREASE_AMOUNT = '1';
|
public const string DEFAULT_STOCK_INCREASE_AMOUNT = '1';
|
||||||
public const string DEFAULT_STOCK_DECREASE_AMOUNT = '1';
|
public const string DEFAULT_STOCK_DECREASE_AMOUNT = '1';
|
||||||
|
@ -56,7 +56,6 @@ class SystemConfig
|
||||||
public function setKey(SystemSettingKey $key): self
|
public function setKey(SystemSettingKey $key): self
|
||||||
{
|
{
|
||||||
$this->key = $key;
|
$this->key = $key;
|
||||||
$this->updateTimestamp();
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +67,6 @@ class SystemConfig
|
||||||
public function setValue(string $value): self
|
public function setValue(string $value): self
|
||||||
{
|
{
|
||||||
$this->value = $value;
|
$this->value = $value;
|
||||||
$this->updateTimestamp();
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +80,8 @@ class SystemConfig
|
||||||
return $this->updatedAt;
|
return $this->updatedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function updateTimestamp(): void
|
#[ORM\PrePersist]
|
||||||
|
protected function updateTimestamp(): void
|
||||||
{
|
{
|
||||||
$this->updatedAt = new DateTimeImmutable();
|
$this->updatedAt = new DateTimeImmutable();
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,17 +12,17 @@ use App\Entity\SystemConfig;
|
||||||
enum SystemSettingKey: string
|
enum SystemSettingKey: string
|
||||||
{
|
{
|
||||||
case STOCK_ADJUSTMENT_LOOKBACK_ORDERS = 'stock_adjustment_lookback_orders';
|
case STOCK_ADJUSTMENT_LOOKBACK_ORDERS = 'stock_adjustment_lookback_orders';
|
||||||
case DEFAULT_DESIRED_STOCK = 'default_desired_stock';
|
case DEFAULT_WANTED_STOCK = 'default_wanted_stock';
|
||||||
case SYSTEM_NAME = 'system_name';
|
case SYSTEM_NAME = 'system_name';
|
||||||
case STOCK_INCREASE_AMOUNT = 'stock_increase_amount';
|
case STOCK_INCREASE_AMOUNT = 'stock_increase_amount';
|
||||||
case STOCK_DECREASE_AMOUNT = 'stock_decrease_amount';
|
case STOCK_DECREASE_AMOUNT = 'stock_decrease_amount';
|
||||||
case STOCK_LOW_MULTIPLIER = 'stock_low_multiplier';
|
case STOCK_LOW_MULTIPLIER = 'stock_low_multiplier';
|
||||||
|
|
||||||
public static function getDefaultValue(self $key): string
|
public function defaultValue(): string
|
||||||
{
|
{
|
||||||
return match ($key) {
|
return match ($this) {
|
||||||
self::STOCK_ADJUSTMENT_LOOKBACK_ORDERS => SystemConfig::DEFAULT_STOCK_ADJUSTMENT_LOOKBACK_ORDERS,
|
self::STOCK_ADJUSTMENT_LOOKBACK_ORDERS => SystemConfig::DEFAULT_STOCK_ADJUSTMENT_LOOKBACK_ORDERS,
|
||||||
self::DEFAULT_DESIRED_STOCK => SystemConfig::DEFAULT_DEFAULT_DESIRED_STOCK,
|
self::DEFAULT_WANTED_STOCK => SystemConfig::DEFAULT_DEFAULT_WANTED_STOCK,
|
||||||
self::SYSTEM_NAME => SystemConfig::DEFAULT_SYSTEM_NAME,
|
self::SYSTEM_NAME => SystemConfig::DEFAULT_SYSTEM_NAME,
|
||||||
self::STOCK_INCREASE_AMOUNT => SystemConfig::DEFAULT_STOCK_INCREASE_AMOUNT,
|
self::STOCK_INCREASE_AMOUNT => SystemConfig::DEFAULT_STOCK_INCREASE_AMOUNT,
|
||||||
self::STOCK_DECREASE_AMOUNT => SystemConfig::DEFAULT_STOCK_DECREASE_AMOUNT,
|
self::STOCK_DECREASE_AMOUNT => SystemConfig::DEFAULT_STOCK_DECREASE_AMOUNT,
|
||||||
|
|
36
src/EventListener/PostPersistUpdateListener.php
Normal file
36
src/EventListener/PostPersistUpdateListener.php
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\EventListener;
|
||||||
|
|
||||||
|
|
||||||
|
use App\Entity\DrinkType;
|
||||||
|
use App\Entity\PropertyChangeLog;
|
||||||
|
use App\Service\DrinkTypeUpdate;
|
||||||
|
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\ORM\Events;
|
||||||
|
use Doctrine\Persistence\Event\LifecycleEventArgs;
|
||||||
|
|
||||||
|
#[AsDoctrineListener(event: Events::postPersist)]
|
||||||
|
#[AsDoctrineListener(event: Events::postUpdate)]
|
||||||
|
final class PostPersistUpdateListener
|
||||||
|
{
|
||||||
|
public function __construct(private DrinkTypeUpdate $drinkTypeUpdate) {}
|
||||||
|
public function postPersist(LifecycleEventArgs $args): void
|
||||||
|
{
|
||||||
|
$this->log(Events::postPersist, $args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function postUpdate(LifecycleEventArgs $args): void
|
||||||
|
{
|
||||||
|
$this->log(Events::postUpdate, $args);
|
||||||
|
}
|
||||||
|
private function log(string $action, LifecycleEventArgs $args): void
|
||||||
|
{
|
||||||
|
$entity = $args->getObject();
|
||||||
|
match($entity::class) {
|
||||||
|
DrinkType::class => $this->drinkTypeUpdate->handleLog($action, $entity, $args->getObjectManager()),
|
||||||
|
default => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,71 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Repository;
|
|
||||||
|
|
||||||
use App\Entity\DrinkType;
|
|
||||||
use App\Entity\InventoryRecord;
|
|
||||||
use DateTimeImmutable;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @extends AbstractRepository<InventoryRecord>
|
|
||||||
*/
|
|
||||||
class InventoryRecordRepository extends AbstractRepository
|
|
||||||
{
|
|
||||||
public function __construct(EntityManagerInterface $entityManager)
|
|
||||||
{
|
|
||||||
parent::__construct($entityManager, $entityManager->getClassMetadata(InventoryRecord::class));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<int, InventoryRecord>
|
|
||||||
*/
|
|
||||||
public function findByDrinkType(DrinkType $drinkType): array
|
|
||||||
{
|
|
||||||
return $this->findBy(
|
|
||||||
[
|
|
||||||
'drinkType' => $drinkType,
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'timestamp' => 'DESC',
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function findLatestByDrinkType(DrinkType $drinkType): null|InventoryRecord
|
|
||||||
{
|
|
||||||
$records = $this->findBy(
|
|
||||||
[
|
|
||||||
'drinkType' => $drinkType,
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'timestamp' => 'DESC',
|
|
||||||
],
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
|
|
||||||
return $records[0] ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<int, InventoryRecord>
|
|
||||||
*/
|
|
||||||
public function findByTimestampRange(DateTimeImmutable $start, DateTimeImmutable $end): array
|
|
||||||
{
|
|
||||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
|
||||||
$qb
|
|
||||||
->select('ir')
|
|
||||||
->from(InventoryRecord::class, 'ir')
|
|
||||||
->where('ir.timestamp >= :start')
|
|
||||||
->andWhere('ir.timestamp <= :end')
|
|
||||||
->setParameter('start', $start)
|
|
||||||
->setParameter('end', $end)
|
|
||||||
->orderBy('ir.timestamp', 'DESC');
|
|
||||||
|
|
||||||
/** @var array<int, InventoryRecord> $result */
|
|
||||||
$result = $qb->getQuery()->getResult();
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -26,29 +26,9 @@ class SystemConfigRepository extends AbstractRepository
|
||||||
if (!($config instanceof SystemConfig)) {
|
if (!($config instanceof SystemConfig)) {
|
||||||
$config = new SystemConfig();
|
$config = new SystemConfig();
|
||||||
$config->setKey($key);
|
$config->setKey($key);
|
||||||
$config->setValue($key->getDefaultValue($key));
|
$config->setValue($key->defaultValue($key));
|
||||||
$this->save($config);
|
$this->save($config);
|
||||||
}
|
}
|
||||||
return $config;
|
return $config;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getValue(SystemSettingKey $key, string $default = ''): string
|
|
||||||
{
|
|
||||||
$config = $this->findByKey($key);
|
|
||||||
return ($config instanceof SystemConfig) ? $config->getValue() : $default;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setValue(SystemSettingKey $key, string $value): void
|
|
||||||
{
|
|
||||||
$config = $this->findByKey($key);
|
|
||||||
|
|
||||||
if ($config instanceof SystemConfig) {
|
|
||||||
$config->setValue($value);
|
|
||||||
} else {
|
|
||||||
$config = new SystemConfig();
|
|
||||||
$config->setKey($key);
|
|
||||||
$config->setValue($value);
|
|
||||||
}
|
|
||||||
$this->save($config);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,6 @@ final readonly class AppName implements Stringable
|
||||||
|
|
||||||
public function __toString(): string
|
public function __toString(): string
|
||||||
{
|
{
|
||||||
return $this->configService->getConfigValue(SystemSettingKey::SYSTEM_NAME, SystemConfig::DEFAULT_SYSTEM_NAME);
|
return $this->configService->get(SystemSettingKey::SYSTEM_NAME);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,64 +15,30 @@ readonly class ConfigurationService
|
||||||
private SystemConfigRepository $systemConfigRepository,
|
private SystemConfigRepository $systemConfigRepository,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
public function get(SystemSettingKey $key): string
|
||||||
* Get all configuration entries
|
|
||||||
*
|
|
||||||
* @return SystemConfig[]
|
|
||||||
*/
|
|
||||||
public function getAllConfigs(): array
|
|
||||||
{
|
{
|
||||||
return $this->systemConfigRepository->findAll();
|
return $this->systemConfigRepository->findByKey($key)->getValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getConfigValue(SystemSettingKey $key, string $default = ''): string
|
public function set(SystemSettingKey $key, string $value): void
|
||||||
{
|
{
|
||||||
return $this->systemConfigRepository->getValue($key, $default);
|
$config = $this->systemConfigRepository->findByKey($key);
|
||||||
}
|
if ($config->getValue() === $value) {
|
||||||
|
return;
|
||||||
public function setConfigValue(SystemSettingKey $key, string $value): void
|
|
||||||
{
|
|
||||||
$this->systemConfigRepository->setValue($key, $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getConfigByKey(SystemSettingKey $key): SystemConfig
|
|
||||||
{
|
|
||||||
return $this->systemConfigRepository->findByKey($key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function createConfig(SystemSettingKey $key, string $value): SystemConfig
|
|
||||||
{
|
|
||||||
if ($this->systemConfigRepository->findByKey($key) instanceof SystemConfig) {
|
|
||||||
throw new InvalidArgumentException("A configuration with the key '{$key->value}' already exists");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$config = new SystemConfig();
|
|
||||||
$config->setKey($key);
|
|
||||||
$config->setValue($value);
|
$config->setValue($value);
|
||||||
$this->systemConfigRepository->save($config);
|
$this->systemConfigRepository->save($config);
|
||||||
|
|
||||||
return $config;
|
|
||||||
}
|
}
|
||||||
|
public function reset(SystemSettingKey $key): void
|
||||||
public function updateConfig(SystemConfig $config, string $value): SystemConfig
|
|
||||||
{
|
{
|
||||||
if ($value !== '') {
|
$this->set($key, $key->defaultValue());
|
||||||
$config->setValue($value);
|
|
||||||
}
|
|
||||||
$this->systemConfigRepository->save($config);
|
|
||||||
|
|
||||||
return $config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function resetAllConfigs(): void
|
public function resetAll(): void
|
||||||
{
|
{
|
||||||
foreach (SystemSettingKey::cases() as $key) {
|
foreach (SystemSettingKey::cases() as $key) {
|
||||||
$this->setDefaultValue($key);
|
$this->reset($key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setDefaultValue(SystemSettingKey $key): void
|
|
||||||
{
|
|
||||||
$this->setConfigValue($key, SystemSettingKey::getDefaultValue($key));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,144 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Service;
|
|
||||||
|
|
||||||
use App\Entity\DrinkType;
|
|
||||||
use App\Enum\SystemSettingKey;
|
|
||||||
use App\Repository\DrinkTypeRepository;
|
|
||||||
use InvalidArgumentException;
|
|
||||||
|
|
||||||
readonly class DrinkTypeService
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private DrinkTypeRepository $drinkTypeRepository,
|
|
||||||
private ConfigurationService $configService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all drink types
|
|
||||||
*
|
|
||||||
* @return DrinkType[]
|
|
||||||
*/
|
|
||||||
public function getAllDrinkTypes(): array
|
|
||||||
{
|
|
||||||
return $this->drinkTypeRepository->findAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a drink type by ID
|
|
||||||
*
|
|
||||||
* @param int $id
|
|
||||||
* @return DrinkType|null
|
|
||||||
*/
|
|
||||||
public function getDrinkTypeById(int $id): null|DrinkType
|
|
||||||
{
|
|
||||||
return $this->drinkTypeRepository->find($id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a drink type by name
|
|
||||||
*
|
|
||||||
* @param string $name
|
|
||||||
* @return DrinkType|null
|
|
||||||
*/
|
|
||||||
public function getDrinkTypeByName(string $name): null|DrinkType
|
|
||||||
{
|
|
||||||
return $this->drinkTypeRepository->findOneBy([
|
|
||||||
'name' => $name,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new drink type
|
|
||||||
*
|
|
||||||
* @param string $name
|
|
||||||
* @param string|null $description
|
|
||||||
* @param int|null $desiredStock
|
|
||||||
* @return DrinkType
|
|
||||||
* @throws InvalidArgumentException If a drink type with the same name already exists
|
|
||||||
*/
|
|
||||||
public function createDrinkType(
|
|
||||||
string $name,
|
|
||||||
null|string $description = null,
|
|
||||||
null|int $desiredStock = null,
|
|
||||||
): DrinkType {
|
|
||||||
// Check if a drink type with the same name already exists
|
|
||||||
if (
|
|
||||||
$this->drinkTypeRepository->findOneBy([
|
|
||||||
'name' => $name,
|
|
||||||
]) !== null
|
|
||||||
) {
|
|
||||||
throw new InvalidArgumentException("A drink type with the name '{$name}' already exists");
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no desired stock is provided, use the default from configuration
|
|
||||||
if ($desiredStock === null) {
|
|
||||||
$desiredStock = (int) $this->configService->getConfigByKey(SystemSettingKey::DEFAULT_DESIRED_STOCK)->getValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
$drinkType = new DrinkType();
|
|
||||||
$drinkType->setName($name);
|
|
||||||
$drinkType->setDescription($description);
|
|
||||||
$drinkType->setDesiredStock($desiredStock);
|
|
||||||
$this->drinkTypeRepository->save($drinkType);
|
|
||||||
|
|
||||||
return $drinkType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update an existing drink type
|
|
||||||
*
|
|
||||||
* @param DrinkType $drinkType
|
|
||||||
* @param string|null $name
|
|
||||||
* @param string|null $description
|
|
||||||
* @param int|null $desiredStock
|
|
||||||
* @return DrinkType
|
|
||||||
* @throws InvalidArgumentException If a drink type with the same name already exists
|
|
||||||
*/
|
|
||||||
public function updateDrinkType(
|
|
||||||
DrinkType $drinkType,
|
|
||||||
null|string $name = null,
|
|
||||||
null|string $description = null,
|
|
||||||
null|int $desiredStock = null,
|
|
||||||
): DrinkType {
|
|
||||||
// Update name if provided
|
|
||||||
if ($name !== null && $name !== $drinkType->getName()) {
|
|
||||||
// Check if a drink type with the same name already exists
|
|
||||||
if (
|
|
||||||
$this->drinkTypeRepository->findOneBy([
|
|
||||||
'name' => $name,
|
|
||||||
]) !== null
|
|
||||||
) {
|
|
||||||
throw new InvalidArgumentException("A drink type with the name '{$name}' already exists");
|
|
||||||
}
|
|
||||||
$drinkType->setName($name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update description if provided
|
|
||||||
if ($description !== null) {
|
|
||||||
$drinkType->setDescription($description);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update desired stock if provided
|
|
||||||
if ($desiredStock !== null) {
|
|
||||||
$drinkType->setDesiredStock($desiredStock);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->drinkTypeRepository->save($drinkType);
|
|
||||||
|
|
||||||
return $drinkType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a drink type
|
|
||||||
*
|
|
||||||
* @param DrinkType $drinkType
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function deleteDrinkType(DrinkType $drinkType): void
|
|
||||||
{
|
|
||||||
$this->drinkTypeRepository->remove($drinkType);
|
|
||||||
}
|
|
||||||
}
|
|
61
src/Service/DrinkTypeUpdate.php
Normal file
61
src/Service/DrinkTypeUpdate.php
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Service;
|
||||||
|
|
||||||
|
use App\Entity\DrinkType;
|
||||||
|
use App\Entity\PropertyChangeLog;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\ORM\Events;
|
||||||
|
|
||||||
|
final class DrinkTypeUpdate
|
||||||
|
{
|
||||||
|
public function handleLog(string $action, DrinkType $entity, EntityManagerInterface $em): void
|
||||||
|
{
|
||||||
|
match ($action) {
|
||||||
|
Events::postPersist => $this->handleCreate($entity, $em),
|
||||||
|
Events::postUpdate => $this->handleUpdate($entity, $em),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private function handleCreate(DrinkType $entity, EntityManagerInterface $em): void
|
||||||
|
{
|
||||||
|
$this->logWanted($entity, $em);
|
||||||
|
$this->logCurrent($entity, $em);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function logWanted(DrinkType $entity, EntityManagerInterface $em): void
|
||||||
|
{
|
||||||
|
$logWanted = new PropertyChangeLog();
|
||||||
|
$logWanted->setNewValue((string) $entity->getWantedStock());
|
||||||
|
$logWanted->setEntityClass(DrinkType::class);
|
||||||
|
$logWanted->setEntityId($entity->getId());
|
||||||
|
$logWanted->setPropertyName('wantedStock');
|
||||||
|
|
||||||
|
$em->persist($logWanted);
|
||||||
|
$em->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function logCurrent(DrinkType $entity, EntityManagerInterface $em): void
|
||||||
|
{
|
||||||
|
$logCurrent = new PropertyChangeLog();
|
||||||
|
$logCurrent->setNewValue((string) $entity->getCurrentStock());
|
||||||
|
$logCurrent->setEntityClass(DrinkType::class);
|
||||||
|
$logCurrent->setEntityId($entity->getId());
|
||||||
|
$logCurrent->setPropertyName('currentStock');
|
||||||
|
|
||||||
|
$em->persist($logCurrent);
|
||||||
|
$em->flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function handleUpdate(DrinkType $entity, EntityManagerInterface $em): void
|
||||||
|
{
|
||||||
|
$uow = $em->getUnitOfWork();
|
||||||
|
$changeSet = $uow->getEntityChangeSet($entity);
|
||||||
|
if (isset($changeSet['wantedStock'])) {
|
||||||
|
$this->logWanted($entity, $em);;
|
||||||
|
}
|
||||||
|
if (isset($changeSet['currentStock'])) {
|
||||||
|
$this->logCurrent($entity,$em);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,125 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Service;
|
|
||||||
|
|
||||||
use App\Entity\DrinkType;
|
|
||||||
use App\Entity\InventoryRecord;
|
|
||||||
use App\Repository\DrinkTypeRepository;
|
|
||||||
use App\Repository\InventoryRecordRepository;
|
|
||||||
use App\Service\Config\LowStockMultiplier;
|
|
||||||
use App\ValueObject\DrinkStock;
|
|
||||||
use DateTimeImmutable;
|
|
||||||
|
|
||||||
readonly class InventoryService
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private InventoryRecordRepository $inventoryRecordRepository,
|
|
||||||
private DrinkTypeRepository $drinkTypeRepository,
|
|
||||||
private LowStockMultiplier $lowStockMultiplier,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all inventory records
|
|
||||||
*
|
|
||||||
* @return InventoryRecord[]
|
|
||||||
*/
|
|
||||||
public function getAllInventoryRecords(): array
|
|
||||||
{
|
|
||||||
return $this->inventoryRecordRepository->findAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get inventory records for a specific drink type
|
|
||||||
*
|
|
||||||
* @param DrinkType $drinkType
|
|
||||||
* @return InventoryRecord[]
|
|
||||||
*/
|
|
||||||
public function getInventoryRecordsByDrinkType(DrinkType $drinkType): array
|
|
||||||
{
|
|
||||||
return $this->inventoryRecordRepository->findByDrinkType($drinkType);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the latest inventory record for a specific drink type
|
|
||||||
*
|
|
||||||
* @param DrinkType $drinkType
|
|
||||||
* @return InventoryRecord
|
|
||||||
*/
|
|
||||||
public function getLatestInventoryRecord(DrinkType $drinkType): InventoryRecord
|
|
||||||
{
|
|
||||||
$record = $this->inventoryRecordRepository->findLatestByDrinkType($drinkType);
|
|
||||||
if (!($record instanceof InventoryRecord)) {
|
|
||||||
$record = new InventoryRecord();
|
|
||||||
$record->setDrinkType($drinkType);
|
|
||||||
$record->setQuantity(0);
|
|
||||||
$this->inventoryRecordRepository->save($record);
|
|
||||||
}
|
|
||||||
return $record;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the current stock level for a specific drink type
|
|
||||||
*
|
|
||||||
* @param DrinkType $drinkType
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
public function getCurrentStockLevel(DrinkType $drinkType): int
|
|
||||||
{
|
|
||||||
$latestRecord = $this->getLatestInventoryRecord($drinkType);
|
|
||||||
return ($latestRecord instanceof InventoryRecord) ? $latestRecord->getQuantity() : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the stock level for a specific drink type
|
|
||||||
*
|
|
||||||
* @param DrinkType $drinkType
|
|
||||||
* @param int $quantity
|
|
||||||
* @param DateTimeImmutable|null $timestamp
|
|
||||||
* @return InventoryRecord
|
|
||||||
*/
|
|
||||||
public function updateStockLevel(
|
|
||||||
DrinkType $drinkType,
|
|
||||||
int $quantity,
|
|
||||||
DateTimeImmutable $timestamp = new DateTimeImmutable(),
|
|
||||||
): InventoryRecord {
|
|
||||||
$inventoryRecord = new InventoryRecord();
|
|
||||||
$inventoryRecord->setDrinkType($drinkType);
|
|
||||||
$inventoryRecord->setQuantity($quantity);
|
|
||||||
$inventoryRecord->setTimestamp($timestamp);
|
|
||||||
|
|
||||||
$this->inventoryRecordRepository->save($inventoryRecord);
|
|
||||||
|
|
||||||
return $inventoryRecord;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return DrinkStock[]
|
|
||||||
*/
|
|
||||||
public function getAllDrinkTypesWithStockLevels(bool $includeZeroDesiredStock = false): array
|
|
||||||
{
|
|
||||||
if ($includeZeroDesiredStock) {
|
|
||||||
$drinkTypes = $this->drinkTypeRepository->findAll();
|
|
||||||
} else {
|
|
||||||
$drinkTypes = $this->drinkTypeRepository->findDesired();
|
|
||||||
}
|
|
||||||
$result = [];
|
|
||||||
|
|
||||||
foreach ($drinkTypes as $drinkType) {
|
|
||||||
$result[] = $this->getDrinkStock($drinkType);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDrinkStock(DrinkType $drinkType): DrinkStock
|
|
||||||
{
|
|
||||||
return DrinkStock::fromInventoryRecord(
|
|
||||||
$this->getLatestInventoryRecord($drinkType),
|
|
||||||
$this->lowStockMultiplier->getValue(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -20,7 +20,6 @@ readonly class OrderService
|
||||||
private OrderRepository $orderRepository,
|
private OrderRepository $orderRepository,
|
||||||
private OrderItemRepository $orderItemRepository,
|
private OrderItemRepository $orderItemRepository,
|
||||||
private DrinkTypeRepository $drinkTypeRepository,
|
private DrinkTypeRepository $drinkTypeRepository,
|
||||||
private InventoryService $inventoryService,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,124 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\Service;
|
|
||||||
|
|
||||||
use App\Entity\DrinkType;
|
|
||||||
use App\Enum\SystemSettingKey;
|
|
||||||
use App\Repository\DrinkTypeRepository;
|
|
||||||
use App\Repository\InventoryRecordRepository;
|
|
||||||
use App\Repository\OrderRepository;
|
|
||||||
use App\Repository\SystemConfigRepository;
|
|
||||||
use App\ValueObject\StockAdjustmentProposal;
|
|
||||||
|
|
||||||
readonly class StockAdjustmentService
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
private DrinkTypeRepository $drinkTypeRepository,
|
|
||||||
private InventoryRecordRepository $inventoryRecordRepository,
|
|
||||||
private OrderRepository $orderRepository,
|
|
||||||
private SystemConfigRepository $systemConfigRepository,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Proposes adjusted stock levels for all drink types
|
|
||||||
*
|
|
||||||
* @return array<int, StockAdjustmentProposal> Array of stock adjustment proposals
|
|
||||||
*/
|
|
||||||
public function proposeStockAdjustments(): array
|
|
||||||
{
|
|
||||||
$drinkTypes = $this->drinkTypeRepository->findAll();
|
|
||||||
$proposals = [];
|
|
||||||
|
|
||||||
foreach ($drinkTypes as $drinkType) {
|
|
||||||
$proposals[] = $this->proposeStockAdjustmentForDrinkType($drinkType);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $proposals;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Proposes an adjusted stock level for a specific drink type
|
|
||||||
*/
|
|
||||||
public function proposeStockAdjustmentForDrinkType(DrinkType $drinkType): StockAdjustmentProposal
|
|
||||||
{
|
|
||||||
$currentDesiredStock = $drinkType->getDesiredStock();
|
|
||||||
$lookbackOrders =
|
|
||||||
(int) $this->systemConfigRepository->getValue(SystemSettingKey::STOCK_ADJUSTMENT_LOOKBACK_ORDERS);
|
|
||||||
$increaseAmount = (int) $this->systemConfigRepository->getValue(SystemSettingKey::STOCK_INCREASE_AMOUNT);
|
|
||||||
$decreaseAmount = (int) $this->systemConfigRepository->getValue(SystemSettingKey::STOCK_DECREASE_AMOUNT);
|
|
||||||
|
|
||||||
// Get the last N orders for this drink type
|
|
||||||
$lastOrders = $this->orderRepository->findLastOrdersForDrinkType($drinkType, $lookbackOrders);
|
|
||||||
|
|
||||||
// If there are no orders, return the current desired stock
|
|
||||||
if ($lastOrders === []) {
|
|
||||||
return new StockAdjustmentProposal($drinkType, $currentDesiredStock);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if stock was 0 in the last order
|
|
||||||
$lastOrder = $lastOrders[0];
|
|
||||||
$lastOrderItems = $lastOrder->getOrderItems();
|
|
||||||
$stockWasZeroInLastOrder = false;
|
|
||||||
|
|
||||||
foreach ($lastOrderItems as $orderItem) {
|
|
||||||
if ($orderItem->getDrinkType()->getId() === $drinkType->getId()) {
|
|
||||||
// Find the inventory record closest to the order creation date
|
|
||||||
$inventoryRecords = $this->inventoryRecordRepository->findByDrinkType($drinkType);
|
|
||||||
foreach ($inventoryRecords as $record) {
|
|
||||||
if ($record->getTimestamp() <= $lastOrder->getCreatedAt()) {
|
|
||||||
if ($record->getQuantity() === 0) {
|
|
||||||
$stockWasZeroInLastOrder = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If stock was 0 in the last order, increase desired stock
|
|
||||||
if ($stockWasZeroInLastOrder) {
|
|
||||||
return new StockAdjustmentProposal($drinkType, $currentDesiredStock + $increaseAmount);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if stock was above zero in all lookback orders
|
|
||||||
$stockWasAboveZeroInAllOrders = true;
|
|
||||||
$ordersToCheck = min(count($lastOrders), $lookbackOrders);
|
|
||||||
|
|
||||||
for ($i = 0; $i < $ordersToCheck; $i++) {
|
|
||||||
$order = $lastOrders[$i];
|
|
||||||
$orderItems = $order->getOrderItems();
|
|
||||||
|
|
||||||
foreach ($orderItems as $orderItem) {
|
|
||||||
if ($orderItem->getDrinkType()->getId() === $drinkType->getId()) {
|
|
||||||
// Find the inventory record closest to the order creation date
|
|
||||||
$inventoryRecords = $this->inventoryRecordRepository->findByDrinkType($drinkType);
|
|
||||||
foreach ($inventoryRecords as $record) {
|
|
||||||
if ($record->getTimestamp() <= $order->getCreatedAt()) {
|
|
||||||
if ($record->getQuantity() === 0) {
|
|
||||||
$stockWasAboveZeroInAllOrders = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!$stockWasAboveZeroInAllOrders) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If stock was above zero in all lookback orders, decrease desired stock
|
|
||||||
if ($stockWasAboveZeroInAllOrders && $ordersToCheck === $lookbackOrders) {
|
|
||||||
$proposedStock = max(0, $currentDesiredStock - $decreaseAmount);
|
|
||||||
return new StockAdjustmentProposal($drinkType, $proposedStock);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, keep the current desired stock
|
|
||||||
return new StockAdjustmentProposal($drinkType, $currentDesiredStock);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -124,7 +124,6 @@
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Date</th>
|
<th>Date</th>
|
||||||
<th>Old Value</th>
|
|
||||||
<th>New Value</th>
|
<th>New Value</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
@ -132,7 +131,6 @@
|
||||||
{% for log in desired_stock_history %}
|
{% for log in desired_stock_history %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ log.changeDate|date('Y-m-d H:i:s') }}</td>
|
<td>{{ log.changeDate|date('Y-m-d H:i:s') }}</td>
|
||||||
<td>{{ log.oldValue }}</td>
|
|
||||||
<td>{{ log.newValue }}</td>
|
<td>{{ log.newValue }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -11,6 +11,16 @@ use Doctrine\ORM\Tools\SchemaTool;
|
||||||
|
|
||||||
abstract class DbTestCase extends TestCase
|
abstract class DbTestCase extends TestCase
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @template T of object
|
||||||
|
* @param class-string<T> $id
|
||||||
|
* @return object<T>
|
||||||
|
*/
|
||||||
|
protected function get(string $id): object
|
||||||
|
{
|
||||||
|
return $this->getContainer()->get($id);
|
||||||
|
}
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use App\Entity\DrinkType;
|
|
||||||
use App\Entity\PropertyChangeLog;
|
|
||||||
use App\Repository\PropertyChangeLogRepository;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
|
|
||||||
test('property change log is created when drink type desired stock is updated', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
$propertyChangeLogRepository = $this->getContainer()->get(PropertyChangeLogRepository::class);
|
|
||||||
|
|
||||||
// Create a drink type
|
|
||||||
$drinkType = new DrinkType();
|
|
||||||
$drinkType->setName('Test Drink Type');
|
|
||||||
$drinkType->setDescription('Test Description');
|
|
||||||
$drinkType->setDesiredStock(5);
|
|
||||||
$em->persist($drinkType);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
$drinkTypeId = $drinkType->getId();
|
|
||||||
|
|
||||||
// Act - Update the desired stock
|
|
||||||
$drinkType->setDesiredStock(10);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Manually create a PropertyChangeLog entry since the event listener might not work in tests
|
|
||||||
$log = new PropertyChangeLog();
|
|
||||||
$log->setEntityClass(DrinkType::class);
|
|
||||||
$log->setEntityId($drinkTypeId);
|
|
||||||
$log->setPropertyName('desiredStock');
|
|
||||||
$log->setNewValue('10');
|
|
||||||
$em->persist($log);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Assert - Check that a PropertyChangeLog entry was created
|
|
||||||
$logs = $propertyChangeLogRepository->findBy([
|
|
||||||
'entityClass' => DrinkType::class,
|
|
||||||
'propertyName' => 'desiredStock',
|
|
||||||
'entityId' => $drinkTypeId
|
|
||||||
], ['changeDate' => 'DESC']);
|
|
||||||
|
|
||||||
expect($logs)->toHaveCount(1);
|
|
||||||
expect($logs[0])->toBeInstanceOf(PropertyChangeLog::class);
|
|
||||||
expect($logs[0]->getNewValue())->toBe('10');
|
|
||||||
});
|
|
41
tests/Feature/Entity/DrinkTypeTest.php
Normal file
41
tests/Feature/Entity/DrinkTypeTest.php
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Entity\DrinkType;
|
||||||
|
use App\Entity\PropertyChangeLog;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
|
test('Update Listener', function () {
|
||||||
|
$drinkType = new DrinkType();
|
||||||
|
$drinkType->setName('test');
|
||||||
|
$drinkType->setWantedStock(10);
|
||||||
|
$drinkType->setCurrentStock(10);
|
||||||
|
|
||||||
|
$em = $this->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
$propertyLogRepository = $em->getRepository(PropertyChangeLog::class);
|
||||||
|
$logs = $propertyLogRepository->findBy(['entityClass' => DrinkType::class]);
|
||||||
|
expect($logs)->toHaveCount(2);
|
||||||
|
|
||||||
|
$drinkType->setWantedStock(15);
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
$logs = $propertyLogRepository->findBy(['entityClass' => DrinkType::class]);
|
||||||
|
expect($logs)->toHaveCount(3);
|
||||||
|
|
||||||
|
$drinkType->setCurrentStock(15);
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
$logs = $propertyLogRepository->findBy(['entityClass' => DrinkType::class]);
|
||||||
|
expect($logs)->toHaveCount(4);
|
||||||
|
|
||||||
|
$drinkType->setDescription('test');
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
$logs = $propertyLogRepository->findBy(['entityClass' => DrinkType::class]);
|
||||||
|
expect($logs)->toHaveCount(4);
|
||||||
|
});
|
|
@ -1,19 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
// tests/Feature/FeatureTestBootstrap.php
|
|
||||||
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
|
||||||
|
|
||||||
uses(KernelTestCase::class)->in(__DIR__);
|
|
||||||
|
|
||||||
beforeEach(function (): void {
|
|
||||||
$em = self::getContainer()->get(EntityManagerInterface::class);
|
|
||||||
createDatabaseSchema($em);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(function (): void {
|
|
||||||
$em = self::getContainer()->get(EntityManagerInterface::class);
|
|
||||||
deleteDatabaseFile($em);
|
|
||||||
});
|
|
18
tests/Feature/Service/Config/AppNameTest.php
Normal file
18
tests/Feature/Service/Config/AppNameTest.php
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Enum\SystemSettingKey;
|
||||||
|
use App\Service\Config\AppName;
|
||||||
|
use App\Service\ConfigurationService;
|
||||||
|
|
||||||
|
test('it returns the system name from configuration service', function () {
|
||||||
|
|
||||||
|
/** @var ConfigurationService $configService */
|
||||||
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
$appName = new AppName($configService);
|
||||||
|
expect((string)$appName)->toBe(SystemSettingKey::SYSTEM_NAME->defaultValue());
|
||||||
|
|
||||||
|
$expected = 'Test System Name';
|
||||||
|
$configService->set(SystemSettingKey::SYSTEM_NAME, $expected);
|
||||||
|
expect((string)$appName)->toBe($expected);
|
||||||
|
|
||||||
|
});
|
|
@ -1,88 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use App\Entity\SystemConfig;
|
|
||||||
use App\Enum\SystemSettingKey;
|
|
||||||
use App\Service\Config\AppName;
|
|
||||||
use App\Service\Config\LowStockMultiplier;
|
|
||||||
use App\Service\ConfigurationService;
|
|
||||||
|
|
||||||
test('AppName returns system name from configuration', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$appName = $this->getContainer()->get(AppName::class);
|
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
|
||||||
$testSystemName = 'Test System Name';
|
|
||||||
|
|
||||||
// Set a custom system name
|
|
||||||
$configService->setConfigValue(SystemSettingKey::SYSTEM_NAME, $testSystemName);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$result = (string) $appName;
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($result)->toBe($testSystemName);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('AppName returns default system name when not configured', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$appName = $this->getContainer()->get(AppName::class);
|
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
|
||||||
|
|
||||||
// Reset to default value
|
|
||||||
$configService->setDefaultValue(SystemSettingKey::SYSTEM_NAME);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$result = (string) $appName;
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($result)->toBe(SystemConfig::DEFAULT_SYSTEM_NAME);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('LowStockMultiplier returns multiplier from configuration', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$lowStockMultiplier = $this->getContainer()->get(LowStockMultiplier::class);
|
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
|
||||||
$testMultiplier = '0.5';
|
|
||||||
|
|
||||||
// Set a custom multiplier
|
|
||||||
$configService->setConfigValue(SystemSettingKey::STOCK_LOW_MULTIPLIER, $testMultiplier);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$result = $lowStockMultiplier->getValue();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($result)->toBe((float) $testMultiplier);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('LowStockMultiplier returns default multiplier when not configured', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$lowStockMultiplier = $this->getContainer()->get(LowStockMultiplier::class);
|
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
|
||||||
|
|
||||||
// Reset to default value
|
|
||||||
$configService->setDefaultValue(SystemSettingKey::STOCK_LOW_MULTIPLIER);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$result = $lowStockMultiplier->getValue();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($result)->toBe((float) SystemConfig::DEFAULT_STOCK_LOW_MULTIPLIER);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('LowStockMultiplier converts string value to float', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$lowStockMultiplier = $this->getContainer()->get(LowStockMultiplier::class);
|
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
|
||||||
$testMultiplier = '0.75';
|
|
||||||
|
|
||||||
// Set a custom multiplier
|
|
||||||
$configService->setConfigValue(SystemSettingKey::STOCK_LOW_MULTIPLIER, $testMultiplier);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$result = $lowStockMultiplier->getValue();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($result)->toBe(0.75);
|
|
||||||
expect($result)->toBeFloat();
|
|
||||||
});
|
|
|
@ -6,142 +6,52 @@ use App\Entity\SystemConfig;
|
||||||
use App\Enum\SystemSettingKey;
|
use App\Enum\SystemSettingKey;
|
||||||
use App\Service\ConfigurationService;
|
use App\Service\ConfigurationService;
|
||||||
|
|
||||||
test('getAllConfigs returns all configurations', function (): void {
|
|
||||||
// Arrange
|
test('get returns correct value', function (): void {
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$configs = $configService->getAllConfigs();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($configs)->toBeArray();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getConfigValue returns correct value', function (): void {
|
|
||||||
// Arrange
|
// Arrange
|
||||||
|
/** @var ConfigurationService $configService */
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
$key = SystemSettingKey::SYSTEM_NAME;
|
$key = SystemSettingKey::SYSTEM_NAME;
|
||||||
$expectedValue = SystemSettingKey::getDefaultValue($key);
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
$value = $configService->getConfigValue($key);
|
$value = $configService->get($key);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect($value)->toBe($expectedValue);
|
expect($value)->toBe($key->defaultValue());
|
||||||
});
|
});
|
||||||
|
|
||||||
test('setConfigValue updates configuration value', function (): void {
|
test('setConfigValue updates configuration value', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
|
/** @var ConfigurationService $configService */
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
$key = SystemSettingKey::SYSTEM_NAME;
|
$key = SystemSettingKey::SYSTEM_NAME;
|
||||||
$newValue = 'Test System Name';
|
$newValue = 'Test System Name';
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
$configService->setConfigValue($key, $newValue);
|
$configService->set($key, $newValue);
|
||||||
$value = $configService->getConfigValue($key);
|
$value = $configService->get($key);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect($value)->toBe($newValue);
|
expect($value)->toBe($newValue);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getConfigByKey returns correct config', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
|
||||||
$key = SystemSettingKey::SYSTEM_NAME;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$config = $configService->getConfigByKey($key);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($config)->toBeInstanceOf(SystemConfig::class)
|
|
||||||
->and($config->getKey())->toBe($key);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('createConfig throws exception when config already exists', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
|
||||||
$key = SystemSettingKey::SYSTEM_NAME;
|
|
||||||
$value = 'Test System Name';
|
|
||||||
|
|
||||||
// Ensure config exists
|
|
||||||
$configService->setConfigValue($key, $value);
|
|
||||||
|
|
||||||
// Act & Assert
|
|
||||||
expect(fn() => $configService->createConfig($key, $value))
|
|
||||||
->toThrow(InvalidArgumentException::class);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('updateConfig updates configuration value', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
|
||||||
$key = SystemSettingKey::SYSTEM_NAME;
|
|
||||||
$initialValue = 'Initial System Name';
|
|
||||||
$newValue = 'Updated System Name';
|
|
||||||
|
|
||||||
// Create or update config with initial value
|
|
||||||
$configService->setConfigValue($key, $initialValue);
|
|
||||||
$config = $configService->getConfigByKey($key);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$updatedConfig = $configService->updateConfig($config, $newValue);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($updatedConfig->getValue())->toBe($newValue)
|
|
||||||
->and($configService->getConfigValue($key))->toBe($newValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('updateConfig does not update when value is empty', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
|
||||||
$key = SystemSettingKey::SYSTEM_NAME;
|
|
||||||
$initialValue = 'Initial System Name';
|
|
||||||
|
|
||||||
// Create or update config with initial value
|
|
||||||
$configService->setConfigValue($key, $initialValue);
|
|
||||||
$config = $configService->getConfigByKey($key);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$updatedConfig = $configService->updateConfig($config, '');
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($updatedConfig->getValue())->toBe($initialValue);
|
|
||||||
expect($configService->getConfigValue($key))->toBe($initialValue);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('resetAllConfigs resets all configurations to default values', function (): void {
|
test('resetAllConfigs resets all configurations to default values', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
|
/** @var ConfigurationService $configService */
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
|
|
||||||
// Set non-default values for all configs
|
// Set non-default values for all configs
|
||||||
foreach (SystemSettingKey::cases() as $key) {
|
foreach (SystemSettingKey::cases() as $key) {
|
||||||
$configService->setConfigValue($key, 'non-default-value');
|
$configService->set($key, 'non-default value');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
$configService->resetAllConfigs();
|
$configService->resetAll();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
foreach (SystemSettingKey::cases() as $key) {
|
foreach (SystemSettingKey::cases() as $key) {
|
||||||
$expectedValue = SystemSettingKey::getDefaultValue($key);
|
|
||||||
$actualValue = $configService->getConfigValue($key);
|
expect($configService->get($key))->toBe($key->defaultValue());
|
||||||
expect($actualValue)->toBe($expectedValue);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('setDefaultValue sets default value for specific key', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
|
||||||
$key = SystemSettingKey::SYSTEM_NAME;
|
|
||||||
$nonDefaultValue = 'Non-Default System Name';
|
|
||||||
|
|
||||||
// Set non-default value
|
|
||||||
$configService->setConfigValue($key, $nonDefaultValue);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$configService->setDefaultValue($key);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
$expectedValue = SystemSettingKey::getDefaultValue($key);
|
|
||||||
$actualValue = $configService->getConfigValue($key);
|
|
||||||
expect($actualValue)->toBe($expectedValue);
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,243 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use App\Entity\DrinkType;
|
|
||||||
use App\Enum\SystemSettingKey;
|
|
||||||
use App\Service\ConfigurationService;
|
|
||||||
use App\Service\DrinkTypeService;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
|
|
||||||
test('getAllDrinkTypes returns all drink types', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$drinkTypes = $drinkTypeService->getAllDrinkTypes();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($drinkTypes)->toBeArray();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getDrinkTypeById returns correct drink type', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Create a drink type
|
|
||||||
$drinkType = new DrinkType();
|
|
||||||
$drinkType->setName('Test Drink Type');
|
|
||||||
$drinkType->setDescription('Test Description');
|
|
||||||
$drinkType->setDesiredStock(5);
|
|
||||||
$em->persist($drinkType);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
$id = $drinkType->getId();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$retrievedDrinkType = $drinkTypeService->getDrinkTypeById($id);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($retrievedDrinkType)->toBeInstanceOf(DrinkType::class);
|
|
||||||
expect($retrievedDrinkType->getId())->toBe($id);
|
|
||||||
expect($retrievedDrinkType->getName())->toBe('Test Drink Type');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getDrinkTypeById returns null for non-existent id', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
|
||||||
$nonExistentId = 9999;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$drinkType = $drinkTypeService->getDrinkTypeById($nonExistentId);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($drinkType)->toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getDrinkTypeByName returns correct drink type', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Create a drink type
|
|
||||||
$drinkType = new DrinkType();
|
|
||||||
$drinkType->setName('Test Drink Type By Name');
|
|
||||||
$drinkType->setDescription('Test Description');
|
|
||||||
$drinkType->setDesiredStock(5);
|
|
||||||
$em->persist($drinkType);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$retrievedDrinkType = $drinkTypeService->getDrinkTypeByName('Test Drink Type By Name');
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($retrievedDrinkType)->toBeInstanceOf(DrinkType::class);
|
|
||||||
expect($retrievedDrinkType->getName())->toBe('Test Drink Type By Name');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getDrinkTypeByName returns null for non-existent name', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
|
||||||
$nonExistentName = 'Non-Existent Drink Type';
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$drinkType = $drinkTypeService->getDrinkTypeByName($nonExistentName);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($drinkType)->toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('createDrinkType creates new drink type with provided values', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
|
||||||
$name = 'New Drink Type';
|
|
||||||
$description = 'New Description';
|
|
||||||
$desiredStock = 10;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$drinkType = $drinkTypeService->createDrinkType($name, $description, $desiredStock);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($drinkType)->toBeInstanceOf(DrinkType::class);
|
|
||||||
expect($drinkType->getName())->toBe($name);
|
|
||||||
expect($drinkType->getDescription())->toBe($description);
|
|
||||||
expect($drinkType->getDesiredStock())->toBe($desiredStock);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('createDrinkType creates new drink type with default desired stock', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
|
||||||
$name = 'New Drink Type Default Stock';
|
|
||||||
$description = 'New Description';
|
|
||||||
|
|
||||||
// Set default desired stock in configuration
|
|
||||||
$defaultDesiredStock = '15';
|
|
||||||
$configService->setConfigValue(SystemSettingKey::DEFAULT_DESIRED_STOCK, $defaultDesiredStock);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$drinkType = $drinkTypeService->createDrinkType($name, $description);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($drinkType)->toBeInstanceOf(DrinkType::class);
|
|
||||||
expect($drinkType->getName())->toBe($name);
|
|
||||||
expect($drinkType->getDescription())->toBe($description);
|
|
||||||
expect($drinkType->getDesiredStock())->toBe((int) $defaultDesiredStock);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('createDrinkType throws exception when drink type with same name exists', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
|
||||||
$name = 'Duplicate Drink Type';
|
|
||||||
|
|
||||||
// Create a drink type with the same name
|
|
||||||
$drinkTypeService->createDrinkType($name);
|
|
||||||
|
|
||||||
// Act & Assert
|
|
||||||
expect(fn() => $drinkTypeService->createDrinkType($name))
|
|
||||||
->toThrow(InvalidArgumentException::class);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('updateDrinkType updates drink type properties', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Create a drink type
|
|
||||||
$drinkType = new DrinkType();
|
|
||||||
$drinkType->setName('Original Drink Type');
|
|
||||||
$drinkType->setDescription('Original Description');
|
|
||||||
$drinkType->setDesiredStock(5);
|
|
||||||
$em->persist($drinkType);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
$newName = 'Updated Drink Type';
|
|
||||||
$newDescription = 'Updated Description';
|
|
||||||
$newDesiredStock = 15;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$updatedDrinkType = $drinkTypeService->updateDrinkType(
|
|
||||||
$drinkType,
|
|
||||||
$newName,
|
|
||||||
$newDescription,
|
|
||||||
$newDesiredStock
|
|
||||||
);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($updatedDrinkType)->toBeInstanceOf(DrinkType::class);
|
|
||||||
expect($updatedDrinkType->getName())->toBe($newName);
|
|
||||||
expect($updatedDrinkType->getDescription())->toBe($newDescription);
|
|
||||||
expect($updatedDrinkType->getDesiredStock())->toBe($newDesiredStock);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('updateDrinkType throws exception when updating to existing name', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Create two drink types
|
|
||||||
$drinkType1 = new DrinkType();
|
|
||||||
$drinkType1->setName('First Drink Type');
|
|
||||||
$em->persist($drinkType1);
|
|
||||||
|
|
||||||
$drinkType2 = new DrinkType();
|
|
||||||
$drinkType2->setName('Second Drink Type');
|
|
||||||
$em->persist($drinkType2);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Act & Assert
|
|
||||||
expect(fn() => $drinkTypeService->updateDrinkType($drinkType2, 'First Drink Type'))
|
|
||||||
->toThrow(InvalidArgumentException::class);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('updateDrinkType only updates provided properties', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Create a drink type
|
|
||||||
$drinkType = new DrinkType();
|
|
||||||
$drinkType->setName('Partial Update Drink Type');
|
|
||||||
$drinkType->setDescription('Original Description');
|
|
||||||
$drinkType->setDesiredStock(5);
|
|
||||||
$em->persist($drinkType);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
$newDescription = 'Updated Description';
|
|
||||||
|
|
||||||
// Act - only update description
|
|
||||||
$updatedDrinkType = $drinkTypeService->updateDrinkType(
|
|
||||||
$drinkType,
|
|
||||||
null,
|
|
||||||
$newDescription,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($updatedDrinkType)->toBeInstanceOf(DrinkType::class);
|
|
||||||
expect($updatedDrinkType->getName())->toBe('Partial Update Drink Type');
|
|
||||||
expect($updatedDrinkType->getDescription())->toBe($newDescription);
|
|
||||||
expect($updatedDrinkType->getDesiredStock())->toBe(5);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('deleteDrinkType removes drink type', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$drinkTypeService = $this->getContainer()->get(DrinkTypeService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Create a drink type
|
|
||||||
$drinkType = new DrinkType();
|
|
||||||
$drinkType->setName('Drink Type To Delete');
|
|
||||||
$em->persist($drinkType);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
$id = $drinkType->getId();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$drinkTypeService->deleteDrinkType($drinkType);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
$deletedDrinkType = $drinkTypeService->getDrinkTypeById($id);
|
|
||||||
expect($deletedDrinkType)->toBeNull();
|
|
||||||
});
|
|
|
@ -1,366 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use App\Entity\DrinkType;
|
|
||||||
use App\Entity\InventoryRecord;
|
|
||||||
use App\Enum\StockState;
|
|
||||||
use App\Enum\SystemSettingKey;
|
|
||||||
use App\Repository\DrinkTypeRepository;
|
|
||||||
use App\Repository\InventoryRecordRepository;
|
|
||||||
use App\Service\ConfigurationService;
|
|
||||||
use App\Service\InventoryService;
|
|
||||||
use App\ValueObject\DrinkStock;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
|
|
||||||
test('getAllInventoryRecords returns all inventory records', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$records = $inventoryService->getAllInventoryRecords();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($records)->toBeArray();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getInventoryRecordsByDrinkType returns records for specific drink type', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Create a drink type
|
|
||||||
$drinkType = new DrinkType();
|
|
||||||
$drinkType->setName('Test Drink Type for Inventory');
|
|
||||||
$drinkType->setDesiredStock(10);
|
|
||||||
$em->persist($drinkType);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Create inventory records
|
|
||||||
$record1 = new InventoryRecord();
|
|
||||||
$record1->setDrinkType($drinkType);
|
|
||||||
$record1->setQuantity(5);
|
|
||||||
$em->persist($record1);
|
|
||||||
|
|
||||||
$record2 = new InventoryRecord();
|
|
||||||
$record2->setDrinkType($drinkType);
|
|
||||||
$record2->setQuantity(8);
|
|
||||||
$em->persist($record2);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$records = $inventoryService->getInventoryRecordsByDrinkType($drinkType);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($records)->toBeArray();
|
|
||||||
expect(count($records))->toBeGreaterThanOrEqual(2);
|
|
||||||
expect($records[0])->toBeInstanceOf(InventoryRecord::class);
|
|
||||||
expect($records[0]->getDrinkType()->getId())->toBe($drinkType->getId());
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getLatestInventoryRecord returns latest record for drink type', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Create a drink type
|
|
||||||
$drinkType = new DrinkType();
|
|
||||||
$drinkType->setName('Test Drink Type for Latest Record');
|
|
||||||
$drinkType->setDesiredStock(10);
|
|
||||||
$em->persist($drinkType);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Create inventory records with different timestamps
|
|
||||||
$record1 = new InventoryRecord();
|
|
||||||
$record1->setDrinkType($drinkType);
|
|
||||||
$record1->setQuantity(5);
|
|
||||||
$record1->setTimestamp(new DateTimeImmutable('-2 days'));
|
|
||||||
$em->persist($record1);
|
|
||||||
|
|
||||||
$record2 = new InventoryRecord();
|
|
||||||
$record2->setDrinkType($drinkType);
|
|
||||||
$record2->setQuantity(8);
|
|
||||||
$record2->setTimestamp(new DateTimeImmutable('-1 day'));
|
|
||||||
$em->persist($record2);
|
|
||||||
|
|
||||||
$record3 = new InventoryRecord();
|
|
||||||
$record3->setDrinkType($drinkType);
|
|
||||||
$record3->setQuantity(12);
|
|
||||||
$record3->setTimestamp(new DateTimeImmutable());
|
|
||||||
$em->persist($record3);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$latestRecord = $inventoryService->getLatestInventoryRecord($drinkType);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($latestRecord)->toBeInstanceOf(InventoryRecord::class);
|
|
||||||
expect($latestRecord->getDrinkType()->getId())->toBe($drinkType->getId());
|
|
||||||
expect($latestRecord->getQuantity())->toBe(12);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getLatestInventoryRecord creates new record if none exists', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
$repository = $this->getContainer()->get(InventoryRecordRepository::class);
|
|
||||||
|
|
||||||
// Create a drink type with no inventory records
|
|
||||||
$drinkType = new DrinkType();
|
|
||||||
$drinkType->setName('Test Drink Type with No Records');
|
|
||||||
$drinkType->setDesiredStock(10);
|
|
||||||
$em->persist($drinkType);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Delete any existing records for this drink type
|
|
||||||
foreach ($repository->findByDrinkType($drinkType) as $record) {
|
|
||||||
$em->remove($record);
|
|
||||||
}
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$latestRecord = $inventoryService->getLatestInventoryRecord($drinkType);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($latestRecord)->toBeInstanceOf(InventoryRecord::class);
|
|
||||||
expect($latestRecord->getDrinkType()->getId())->toBe($drinkType->getId());
|
|
||||||
expect($latestRecord->getQuantity())->toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getCurrentStockLevel returns correct stock level', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Create a drink type
|
|
||||||
$drinkType = new DrinkType();
|
|
||||||
$drinkType->setName('Test Drink Type for Stock Level');
|
|
||||||
$drinkType->setDesiredStock(10);
|
|
||||||
$em->persist($drinkType);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Create inventory record
|
|
||||||
$record = new InventoryRecord();
|
|
||||||
$record->setDrinkType($drinkType);
|
|
||||||
$record->setQuantity(15);
|
|
||||||
$em->persist($record);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$stockLevel = $inventoryService->getCurrentStockLevel($drinkType);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($stockLevel)->toBe(15);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('updateStockLevel creates new inventory record', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Create a drink type
|
|
||||||
$drinkType = new DrinkType();
|
|
||||||
$drinkType->setName('Test Drink Type for Update');
|
|
||||||
$drinkType->setDesiredStock(10);
|
|
||||||
$em->persist($drinkType);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
$newQuantity = 25;
|
|
||||||
$timestamp = new DateTimeImmutable();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$record = $inventoryService->updateStockLevel($drinkType, $newQuantity, $timestamp);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($record)->toBeInstanceOf(InventoryRecord::class);
|
|
||||||
expect($record->getDrinkType()->getId())->toBe($drinkType->getId());
|
|
||||||
expect($record->getQuantity())->toBe($newQuantity);
|
|
||||||
expect($record->getTimestamp()->getTimestamp())->toBe($timestamp->getTimestamp());
|
|
||||||
|
|
||||||
// Verify the stock level was updated
|
|
||||||
$currentLevel = $inventoryService->getCurrentStockLevel($drinkType);
|
|
||||||
expect($currentLevel)->toBe($newQuantity);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getAllDrinkTypesWithStockLevels returns all drink types with stock', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
$drinkTypeRepo = $this->getContainer()->get(DrinkTypeRepository::class);
|
|
||||||
|
|
||||||
// Create drink types and inventory records
|
|
||||||
$drinkType1 = new DrinkType();
|
|
||||||
$drinkType1->setName('Drink Type 1 for Stock Levels');
|
|
||||||
$drinkType1->setDesiredStock(10);
|
|
||||||
$em->persist($drinkType1);
|
|
||||||
|
|
||||||
$drinkType2 = new DrinkType();
|
|
||||||
$drinkType2->setName('Drink Type 2 for Stock Levels');
|
|
||||||
$drinkType2->setDesiredStock(0); // Zero desired stock
|
|
||||||
$em->persist($drinkType2);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Create inventory records
|
|
||||||
$record1 = new InventoryRecord();
|
|
||||||
$record1->setDrinkType($drinkType1);
|
|
||||||
$record1->setQuantity(5);
|
|
||||||
$em->persist($record1);
|
|
||||||
|
|
||||||
$record2 = new InventoryRecord();
|
|
||||||
$record2->setDrinkType($drinkType2);
|
|
||||||
$record2->setQuantity(8);
|
|
||||||
$em->persist($record2);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Act - without zero desired stock
|
|
||||||
$stockLevels1 = $inventoryService->getAllDrinkTypesWithStockLevels(false);
|
|
||||||
|
|
||||||
// Act - with zero desired stock
|
|
||||||
$stockLevels2 = $inventoryService->getAllDrinkTypesWithStockLevels(true);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($stockLevels1)->toBeArray();
|
|
||||||
expect($stockLevels2)->toBeArray();
|
|
||||||
expect(count($stockLevels2))->toBeGreaterThanOrEqual(count($stockLevels1));
|
|
||||||
|
|
||||||
foreach ($stockLevels2 as $stockLevel) {
|
|
||||||
expect($stockLevel)->toBeInstanceOf(DrinkStock::class);
|
|
||||||
expect($stockLevel->record)->toBeInstanceOf(InventoryRecord::class);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getDrinkStock returns correct DrinkStock object with CRITICAL state', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Set low stock multiplier
|
|
||||||
$configService->setConfigValue(SystemSettingKey::STOCK_LOW_MULTIPLIER, '0.3');
|
|
||||||
|
|
||||||
// Create a drink type with zero quantity (CRITICAL)
|
|
||||||
$drinkType = new DrinkType();
|
|
||||||
$drinkType->setName('Critical Stock Drink Type');
|
|
||||||
$drinkType->setDesiredStock(10);
|
|
||||||
$em->persist($drinkType);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Create inventory record with zero quantity
|
|
||||||
$record = new InventoryRecord();
|
|
||||||
$record->setDrinkType($drinkType);
|
|
||||||
$record->setQuantity(0);
|
|
||||||
$em->persist($record);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$drinkStock = $inventoryService->getDrinkStock($drinkType);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($drinkStock)->toBeInstanceOf(DrinkStock::class);
|
|
||||||
expect($drinkStock->record->getDrinkType()->getId())->toBe($drinkType->getId());
|
|
||||||
expect($drinkStock->stock)->toBe(StockState::CRITICAL);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getDrinkStock returns correct DrinkStock object with LOW state', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Set low stock multiplier
|
|
||||||
$lowStockMultiplier = 0.3;
|
|
||||||
$configService->setConfigValue(SystemSettingKey::STOCK_LOW_MULTIPLIER, (string) $lowStockMultiplier);
|
|
||||||
|
|
||||||
// Create a drink type with low quantity
|
|
||||||
$desiredStock = 10;
|
|
||||||
$drinkType = new DrinkType();
|
|
||||||
$drinkType->setName('Low Stock Drink Type');
|
|
||||||
$drinkType->setDesiredStock($desiredStock);
|
|
||||||
$em->persist($drinkType);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Create inventory record with low quantity (between 0 and lowStockMultiplier * desiredStock)
|
|
||||||
$lowQuantity = (int) ($desiredStock * $lowStockMultiplier) - 1;
|
|
||||||
$record = new InventoryRecord();
|
|
||||||
$record->setDrinkType($drinkType);
|
|
||||||
$record->setQuantity($lowQuantity);
|
|
||||||
$em->persist($record);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$drinkStock = $inventoryService->getDrinkStock($drinkType);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($drinkStock)->toBeInstanceOf(DrinkStock::class);
|
|
||||||
expect($drinkStock->record->getDrinkType()->getId())->toBe($drinkType->getId());
|
|
||||||
expect($drinkStock->stock)->toBe(StockState::LOW);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getDrinkStock returns correct DrinkStock object with NORMAL state', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Set low stock multiplier
|
|
||||||
$lowStockMultiplier = 0.3;
|
|
||||||
$configService->setConfigValue(SystemSettingKey::STOCK_LOW_MULTIPLIER, (string) $lowStockMultiplier);
|
|
||||||
|
|
||||||
// Create a drink type with normal quantity
|
|
||||||
$desiredStock = 10;
|
|
||||||
$drinkType = new DrinkType();
|
|
||||||
$drinkType->setName('Normal Stock Drink Type');
|
|
||||||
$drinkType->setDesiredStock($desiredStock);
|
|
||||||
$em->persist($drinkType);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Create inventory record with normal quantity (between lowStockMultiplier * desiredStock and desiredStock)
|
|
||||||
$normalQuantity = (int) ($desiredStock * $lowStockMultiplier) + 1;
|
|
||||||
$record = new InventoryRecord();
|
|
||||||
$record->setDrinkType($drinkType);
|
|
||||||
$record->setQuantity($normalQuantity);
|
|
||||||
$em->persist($record);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$drinkStock = $inventoryService->getDrinkStock($drinkType);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($drinkStock)->toBeInstanceOf(DrinkStock::class);
|
|
||||||
expect($drinkStock->record->getDrinkType()->getId())->toBe($drinkType->getId());
|
|
||||||
expect($drinkStock->stock)->toBe(StockState::NORMAL);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getDrinkStock returns correct DrinkStock object with HIGH state', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Set low stock multiplier
|
|
||||||
$configService->setConfigValue(SystemSettingKey::STOCK_LOW_MULTIPLIER, '0.3');
|
|
||||||
|
|
||||||
// Create a drink type with high quantity
|
|
||||||
$desiredStock = 10;
|
|
||||||
$drinkType = new DrinkType();
|
|
||||||
$drinkType->setName('High Stock Drink Type');
|
|
||||||
$drinkType->setDesiredStock($desiredStock);
|
|
||||||
$em->persist($drinkType);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Create inventory record with high quantity (greater than desiredStock)
|
|
||||||
$highQuantity = $desiredStock + 1;
|
|
||||||
$record = new InventoryRecord();
|
|
||||||
$record->setDrinkType($drinkType);
|
|
||||||
$record->setQuantity($highQuantity);
|
|
||||||
$em->persist($record);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$drinkStock = $inventoryService->getDrinkStock($drinkType);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($drinkStock)->toBeInstanceOf(DrinkStock::class);
|
|
||||||
expect($drinkStock->record->getDrinkType()->getId())->toBe($drinkType->getId());
|
|
||||||
expect($drinkStock->stock)->toBe(StockState::HIGH);
|
|
||||||
});
|
|
|
@ -1,461 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use App\Entity\DrinkType;
|
|
||||||
use App\Entity\Order;
|
|
||||||
use App\Entity\OrderItem;
|
|
||||||
use App\Enum\OrderStatus;
|
|
||||||
use App\Repository\OrderItemRepository;
|
|
||||||
use App\Service\InventoryService;
|
|
||||||
use App\Service\OrderService;
|
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
|
|
||||||
test('getAllOrders returns all orders', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$orders = $orderService->getAllOrders();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($orders)->toBeArray();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getOrderById returns correct order', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Create an order
|
|
||||||
$order = new Order();
|
|
||||||
$order->setStatus(OrderStatus::NEW);
|
|
||||||
$em->persist($order);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
$id = $order->getId();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$retrievedOrder = $orderService->getOrderById($id);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($retrievedOrder)->toBeInstanceOf(Order::class);
|
|
||||||
expect($retrievedOrder->getId())->toBe($id);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getOrderById returns null for non-existent id', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
|
||||||
$nonExistentId = 9999;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$order = $orderService->getOrderById($nonExistentId);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($order)->toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getOrdersByStatus returns orders with specific status', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Create orders with different statuses
|
|
||||||
$order1 = new Order();
|
|
||||||
$order1->setStatus(OrderStatus::NEW);
|
|
||||||
$em->persist($order1);
|
|
||||||
|
|
||||||
$order2 = new Order();
|
|
||||||
$order2->setStatus(OrderStatus::ORDERED);
|
|
||||||
$em->persist($order2);
|
|
||||||
|
|
||||||
$order3 = new Order();
|
|
||||||
$order3->setStatus(OrderStatus::NEW);
|
|
||||||
$em->persist($order3);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$newOrders = $orderService->getOrdersByStatus(OrderStatus::NEW);
|
|
||||||
$orderedOrders = $orderService->getOrdersByStatus(OrderStatus::ORDERED);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($newOrders)->toBeArray();
|
|
||||||
expect(count($newOrders))->toBeGreaterThanOrEqual(2);
|
|
||||||
expect($orderedOrders)->toBeArray();
|
|
||||||
expect(count($orderedOrders))->toBeGreaterThanOrEqual(1);
|
|
||||||
|
|
||||||
foreach ($newOrders as $order) {
|
|
||||||
expect($order)->toBeInstanceOf(Order::class);
|
|
||||||
expect($order->getStatus())->toBe(OrderStatus::NEW);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($orderedOrders as $order) {
|
|
||||||
expect($order)->toBeInstanceOf(Order::class);
|
|
||||||
expect($order->getStatus())->toBe(OrderStatus::ORDERED);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getActiveOrders returns orders with active statuses', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Create orders with different statuses
|
|
||||||
$order1 = new Order();
|
|
||||||
$order1->setStatus(OrderStatus::NEW);
|
|
||||||
$em->persist($order1);
|
|
||||||
|
|
||||||
$order2 = new Order();
|
|
||||||
$order2->setStatus(OrderStatus::ORDERED);
|
|
||||||
$em->persist($order2);
|
|
||||||
|
|
||||||
$order3 = new Order();
|
|
||||||
$order3->setStatus(OrderStatus::FULFILLED);
|
|
||||||
$em->persist($order3);
|
|
||||||
|
|
||||||
$order4 = new Order();
|
|
||||||
$order4->setStatus(OrderStatus::CANCELLED);
|
|
||||||
$em->persist($order4);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$activeOrders = $orderService->getActiveOrders();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($activeOrders)->toBeArray();
|
|
||||||
|
|
||||||
foreach ($activeOrders as $order) {
|
|
||||||
expect($order)->toBeInstanceOf(Order::class);
|
|
||||||
expect($order->getStatus())->toBeIn([OrderStatus::NEW, OrderStatus::ORDERED, OrderStatus::IN_WORK]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getMostRecentActiveOrder returns most recent active order', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Create orders with different statuses and timestamps
|
|
||||||
$order1 = new Order();
|
|
||||||
$order1->setStatus(OrderStatus::NEW);
|
|
||||||
$order1->setUpdatedAt(new DateTimeImmutable('-2 days'));
|
|
||||||
$em->persist($order1);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Sleep to ensure different timestamps
|
|
||||||
|
|
||||||
$order2 = new Order();
|
|
||||||
$order2->setStatus(OrderStatus::IN_WORK);
|
|
||||||
$order2->setUpdatedAt(new DateTimeImmutable('-1 day'));
|
|
||||||
$em->persist($order2);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$recentOrder = $orderService->getMostRecentActiveOrder();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($recentOrder)->toBeInstanceOf(Order::class);
|
|
||||||
expect($recentOrder->getId())->toBe($order2->getId());
|
|
||||||
});
|
|
||||||
|
|
||||||
test('hasActiveOrders returns true when active orders exist', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Create an active order
|
|
||||||
$order = new Order();
|
|
||||||
$order->setStatus(OrderStatus::NEW);
|
|
||||||
$em->persist($order);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$hasActiveOrders = $orderService->hasActiveOrders();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($hasActiveOrders)->toBeTrue();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getOrdersByDateRange returns orders within date range', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Create orders
|
|
||||||
$order1 = new Order();
|
|
||||||
$order1->setStatus(OrderStatus::NEW);
|
|
||||||
$em->persist($order1);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
$start = new DateTimeImmutable('-1 day');
|
|
||||||
$end = new DateTimeImmutable('+1 day');
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$orders = $orderService->getOrdersByDateRange($start, $end);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($orders)->toBeArray();
|
|
||||||
expect(count($orders))->toBeGreaterThanOrEqual(1);
|
|
||||||
|
|
||||||
foreach ($orders as $order) {
|
|
||||||
expect($order)->toBeInstanceOf(Order::class);
|
|
||||||
expect($order->getCreatedAt()->getTimestamp())->toBeGreaterThanOrEqual($start->getTimestamp());
|
|
||||||
expect($order->getCreatedAt()->getTimestamp())->toBeLessThanOrEqual($end->getTimestamp());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('createOrder creates new order with items', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Create drink types
|
|
||||||
$drinkType1 = new DrinkType();
|
|
||||||
$drinkType1->setName('Drink Type 1 for Order');
|
|
||||||
$drinkType1->setDesiredStock(10);
|
|
||||||
$em->persist($drinkType1);
|
|
||||||
|
|
||||||
$drinkType2 = new DrinkType();
|
|
||||||
$drinkType2->setName('Drink Type 2 for Order');
|
|
||||||
$drinkType2->setDesiredStock(5);
|
|
||||||
$em->persist($drinkType2);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
$items = [
|
|
||||||
[
|
|
||||||
'drinkTypeId' => $drinkType1->getId(),
|
|
||||||
'quantity' => 3,
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'drinkTypeId' => $drinkType2->getId(),
|
|
||||||
'quantity' => 2,
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$order = $orderService->createOrder($items);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($order)->toBeInstanceOf(Order::class);
|
|
||||||
expect($order->getStatus())->toBe(OrderStatus::NEW);
|
|
||||||
expect($order->getOrderItems()->count())->toBe(2);
|
|
||||||
|
|
||||||
$orderItems = $order->getOrderItems()->toArray();
|
|
||||||
expect($orderItems[0]->getDrinkType()->getId())->toBe($drinkType1->getId());
|
|
||||||
expect($orderItems[0]->getQuantity())->toBe(3);
|
|
||||||
expect($orderItems[1]->getDrinkType()->getId())->toBe($drinkType2->getId());
|
|
||||||
expect($orderItems[1]->getQuantity())->toBe(2);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('createOrderFromStockLevels creates order based on stock levels', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
|
||||||
$inventoryService = $this->getContainer()->get(InventoryService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Create drink types with stock levels below desired stock
|
|
||||||
$drinkType1 = new DrinkType();
|
|
||||||
$drinkType1->setName('Low Stock Drink Type 1');
|
|
||||||
$drinkType1->setDesiredStock(10);
|
|
||||||
$em->persist($drinkType1);
|
|
||||||
|
|
||||||
$drinkType2 = new DrinkType();
|
|
||||||
$drinkType2->setName('Low Stock Drink Type 2');
|
|
||||||
$drinkType2->setDesiredStock(8);
|
|
||||||
$em->persist($drinkType2);
|
|
||||||
|
|
||||||
$drinkType3 = new DrinkType();
|
|
||||||
$drinkType3->setName('Sufficient Stock Drink Type');
|
|
||||||
$drinkType3->setDesiredStock(5);
|
|
||||||
$em->persist($drinkType3);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Set stock levels
|
|
||||||
$inventoryService->updateStockLevel($drinkType1, 2); // Low stock
|
|
||||||
$inventoryService->updateStockLevel($drinkType2, 3); // Low stock
|
|
||||||
$inventoryService->updateStockLevel($drinkType3, 10); // Sufficient stock
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$order = $orderService->createOrderFromStockLevels();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($order)->toBeInstanceOf(Order::class);
|
|
||||||
expect($order->getStatus())->toBe(OrderStatus::NEW);
|
|
||||||
|
|
||||||
// Should only include items for drink types with low stock
|
|
||||||
$orderItems = $order->getOrderItems()->toArray();
|
|
||||||
$drinkTypeIds = array_map(fn($item) => $item->getDrinkType()->getId(), $orderItems);
|
|
||||||
|
|
||||||
expect($drinkTypeIds)->toContain($drinkType1->getId());
|
|
||||||
expect($drinkTypeIds)->toContain($drinkType2->getId());
|
|
||||||
expect($drinkTypeIds)->not->toContain($drinkType3->getId());
|
|
||||||
|
|
||||||
// Check quantities
|
|
||||||
foreach ($orderItems as $item) {
|
|
||||||
$drinkType = $item->getDrinkType();
|
|
||||||
$currentStock = $inventoryService->getCurrentStockLevel($drinkType);
|
|
||||||
$desiredStock = $drinkType->getDesiredStock();
|
|
||||||
$expectedQuantity = $desiredStock - $currentStock;
|
|
||||||
|
|
||||||
expect($item->getQuantity())->toBe($expectedQuantity);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('updateOrderStatus updates order status', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Create an order
|
|
||||||
$order = new Order();
|
|
||||||
$order->setStatus(OrderStatus::NEW);
|
|
||||||
$em->persist($order);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$updatedOrder = $orderService->updateOrderStatus($order, OrderStatus::ORDERED);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($updatedOrder)->toBeInstanceOf(Order::class);
|
|
||||||
expect($updatedOrder->getStatus())->toBe(OrderStatus::ORDERED);
|
|
||||||
|
|
||||||
// Verify the status was updated in the database
|
|
||||||
});
|
|
||||||
|
|
||||||
test('addOrderItem adds item to order', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Create an order
|
|
||||||
$order = new Order();
|
|
||||||
$order->setStatus(OrderStatus::NEW);
|
|
||||||
$em->persist($order);
|
|
||||||
|
|
||||||
// Create a drink type
|
|
||||||
$drinkType = new DrinkType();
|
|
||||||
$drinkType->setName('Drink Type for Order Item');
|
|
||||||
$drinkType->setDesiredStock(10);
|
|
||||||
$em->persist($drinkType);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
$quantity = 5;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$orderItem = $orderService->addOrderItem($order, $drinkType, $quantity);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($orderItem)->toBeInstanceOf(OrderItem::class);
|
|
||||||
expect($orderItem->getOrder()->getId())->toBe($order->getId());
|
|
||||||
expect($orderItem->getDrinkType()->getId())->toBe($drinkType->getId());
|
|
||||||
expect($orderItem->getQuantity())->toBe($quantity);
|
|
||||||
|
|
||||||
// Verify the item was added to the order
|
|
||||||
expect($order->getOrderItems()->contains($orderItem))->toBeTrue();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('addOrderItem updates quantity if item already exists', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Create an order
|
|
||||||
$order = new Order();
|
|
||||||
$order->setStatus(OrderStatus::NEW);
|
|
||||||
$em->persist($order);
|
|
||||||
|
|
||||||
// Create a drink type
|
|
||||||
$drinkType = new DrinkType();
|
|
||||||
$drinkType->setName('Drink Type for Existing Order Item');
|
|
||||||
$drinkType->setDesiredStock(10);
|
|
||||||
$em->persist($drinkType);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Add an item
|
|
||||||
$initialQuantity = 3;
|
|
||||||
$orderItem = $orderService->addOrderItem($order, $drinkType, $initialQuantity);
|
|
||||||
|
|
||||||
// Act - add another item with the same drink type
|
|
||||||
$additionalQuantity = 2;
|
|
||||||
$updatedOrderItem = $orderService->addOrderItem($order, $drinkType, $additionalQuantity);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($updatedOrderItem)->toBeInstanceOf(OrderItem::class);
|
|
||||||
expect($updatedOrderItem->getId())->toBe($orderItem->getId());
|
|
||||||
expect($updatedOrderItem->getQuantity())->toBe($initialQuantity + $additionalQuantity);
|
|
||||||
|
|
||||||
// Verify the order still has only one item for this drink type
|
|
||||||
$matchingItems = $order->getOrderItems()->filter(
|
|
||||||
fn($item): bool => $item->getDrinkType()->getId() === $drinkType->getId()
|
|
||||||
);
|
|
||||||
expect($matchingItems->count())->toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('removeOrderItem removes item from order', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Create an order
|
|
||||||
$order = new Order();
|
|
||||||
$order->setStatus(OrderStatus::NEW);
|
|
||||||
$em->persist($order);
|
|
||||||
|
|
||||||
// Create a drink type
|
|
||||||
$drinkType = new DrinkType();
|
|
||||||
$drinkType->setName('Drink Type for Order Item Removal');
|
|
||||||
$drinkType->setDesiredStock(10);
|
|
||||||
$em->persist($drinkType);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Add an item
|
|
||||||
$orderItem = $orderService->addOrderItem($order, $drinkType, 5);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$orderService->removeOrderItem($order, $orderItem);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect($order->getOrderItems()->contains($orderItem))->toBeFalse();
|
|
||||||
|
|
||||||
// Verify the item was removed from the database
|
|
||||||
$em->refresh($order);
|
|
||||||
$matchingItems = $order->getOrderItems()->filter(
|
|
||||||
fn($item): bool => $item->getDrinkType()->getId() === $drinkType->getId()
|
|
||||||
);
|
|
||||||
expect($matchingItems->count())->toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('deleteOrder removes order and its items', function (): void {
|
|
||||||
// Arrange
|
|
||||||
$orderService = $this->getContainer()->get(OrderService::class);
|
|
||||||
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
|
||||||
|
|
||||||
// Create an order
|
|
||||||
$order = new Order();
|
|
||||||
$order->setStatus(OrderStatus::NEW);
|
|
||||||
$em->persist($order);
|
|
||||||
|
|
||||||
// Create a drink type
|
|
||||||
$drinkType = new DrinkType();
|
|
||||||
$drinkType->setName('Drink Type for Order Deletion');
|
|
||||||
$drinkType->setDesiredStock(10);
|
|
||||||
$em->persist($drinkType);
|
|
||||||
$em->flush();
|
|
||||||
|
|
||||||
// Add an item
|
|
||||||
$orderItem = $orderService->addOrderItem($order, $drinkType, 5);
|
|
||||||
$orderItemId = $orderItem->getId();
|
|
||||||
$orderId = $order->getId();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
$orderService->deleteOrder($order);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
$deletedOrder = $orderService->getOrderById($orderId);
|
|
||||||
expect($deletedOrder)->toBeNull();
|
|
||||||
|
|
||||||
// Verify the order items were also deleted
|
|
||||||
$orderItemRepo = $this->getContainer()->get(OrderItemRepository::class);
|
|
||||||
$deletedOrderItem = $orderItemRepo->find($orderItemId);
|
|
||||||
expect($deletedOrderItem)->toBeNull();
|
|
||||||
});
|
|
Loading…
Add table
Add a link
Reference in a new issue