diff --git a/migrations/Version20250609175837.php b/migrations/Version20250609175837.php new file mode 100644 index 0000000..51605c1 --- /dev/null +++ b/migrations/Version20250609175837.php @@ -0,0 +1,92 @@ +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); + } +} diff --git a/src/Controller/Index.php b/src/Controller/Index.php index 5c7ddd1..aaabdfd 100644 --- a/src/Controller/Index.php +++ b/src/Controller/Index.php @@ -14,9 +14,6 @@ use Symfony\Component\Routing\Attribute\Route; #[Route(path: '/', name: 'app_index')] final class Index extends AbstractController { - public function __construct( - private readonly InventoryService $inventoryService, - ) {} public function __invoke(): Response { diff --git a/src/Entity/DrinkType.php b/src/Entity/DrinkType.php index 12713b6..7d76553 100644 --- a/src/Entity/DrinkType.php +++ b/src/Entity/DrinkType.php @@ -9,7 +9,6 @@ use DateTimeImmutable; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; -use App\Entity\PropertyChangeLog; #[ORM\Entity(repositoryClass: DrinkTypeRepository::class)] #[ORM\Table(name: 'drink_type')] @@ -26,9 +25,6 @@ class DrinkType #[ORM\Column(type: 'datetime_immutable')] private DateTimeImmutable $updatedAt; - #[ORM\OneToMany(mappedBy: 'drinkType', targetEntity: InventoryRecord::class)] - private Collection $inventoryRecords; - #[ORM\OneToMany(mappedBy: 'drinkType', targetEntity: OrderItem::class)] private Collection $orderItems; @@ -37,16 +33,29 @@ class DrinkType #[ORM\Column(type: 'text', nullable: true)] private null|string $description = null; #[ORM\Column(type: 'integer')] - private int $desiredStock = 10; + private int $wantedStock = 10; + + #[ORM\Column(type: 'integer')] + private int $currentStock = 0; + public function __construct( ) { $this->createdAt = new DateTimeImmutable(); $this->updatedAt = new DateTimeImmutable(); - $this->inventoryRecords = 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 { return $this->id; @@ -60,7 +69,6 @@ class DrinkType public function setName(string $name): self { $this->name = $name; - $this->updateTimestamp(); return $this; } @@ -72,22 +80,21 @@ class DrinkType public function setDescription(null|string $description): self { $this->description = $description; - $this->updateTimestamp(); 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->updateTimestamp(); + $this->wantedStock = $wantedStock; return $this; } + public function getCreatedAt(): DateTimeImmutable { return $this->createdAt; @@ -98,16 +105,12 @@ class DrinkType return $this->updatedAt; } - public function getInventoryRecords(): Collection - { - return $this->inventoryRecords; - } - public function getOrderItems(): Collection { return $this->orderItems; } + #[ORM\PrePersist] private function updateTimestamp(): void { $this->updatedAt = new DateTimeImmutable(); diff --git a/src/Entity/InventoryRecord.php b/src/Entity/InventoryRecord.php deleted file mode 100644 index 2ffe0f6..0000000 --- a/src/Entity/InventoryRecord.php +++ /dev/null @@ -1,98 +0,0 @@ -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(); - } -} diff --git a/src/Entity/SystemConfig.php b/src/Entity/SystemConfig.php index a63e64f..0743c2f 100644 --- a/src/Entity/SystemConfig.php +++ b/src/Entity/SystemConfig.php @@ -14,7 +14,7 @@ use Doctrine\ORM\Mapping as ORM; class SystemConfig { 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_STOCK_INCREASE_AMOUNT = '1'; public const string DEFAULT_STOCK_DECREASE_AMOUNT = '1'; @@ -56,7 +56,6 @@ class SystemConfig public function setKey(SystemSettingKey $key): self { $this->key = $key; - $this->updateTimestamp(); return $this; } @@ -68,7 +67,6 @@ class SystemConfig public function setValue(string $value): self { $this->value = $value; - $this->updateTimestamp(); return $this; } @@ -82,7 +80,8 @@ class SystemConfig return $this->updatedAt; } - private function updateTimestamp(): void + #[ORM\PrePersist] + protected function updateTimestamp(): void { $this->updatedAt = new DateTimeImmutable(); } diff --git a/src/Enum/SystemSettingKey.php b/src/Enum/SystemSettingKey.php index a4f2ff3..e4149f7 100644 --- a/src/Enum/SystemSettingKey.php +++ b/src/Enum/SystemSettingKey.php @@ -12,17 +12,17 @@ use App\Entity\SystemConfig; enum SystemSettingKey: string { 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 STOCK_INCREASE_AMOUNT = 'stock_increase_amount'; case STOCK_DECREASE_AMOUNT = 'stock_decrease_amount'; 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::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::STOCK_INCREASE_AMOUNT => SystemConfig::DEFAULT_STOCK_INCREASE_AMOUNT, self::STOCK_DECREASE_AMOUNT => SystemConfig::DEFAULT_STOCK_DECREASE_AMOUNT, diff --git a/src/EventListener/PostPersistUpdateListener.php b/src/EventListener/PostPersistUpdateListener.php new file mode 100644 index 0000000..43965f8 --- /dev/null +++ b/src/EventListener/PostPersistUpdateListener.php @@ -0,0 +1,36 @@ +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, + }; + } +} diff --git a/src/Repository/InventoryRecordRepository.php b/src/Repository/InventoryRecordRepository.php deleted file mode 100644 index 800e38f..0000000 --- a/src/Repository/InventoryRecordRepository.php +++ /dev/null @@ -1,71 +0,0 @@ - - */ -class InventoryRecordRepository extends AbstractRepository -{ - public function __construct(EntityManagerInterface $entityManager) - { - parent::__construct($entityManager, $entityManager->getClassMetadata(InventoryRecord::class)); - } - - /** - * @return array - */ - 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 - */ - 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 $result */ - $result = $qb->getQuery()->getResult(); - return $result; - } -} diff --git a/src/Repository/SystemConfigRepository.php b/src/Repository/SystemConfigRepository.php index b8ffc57..4b813ed 100644 --- a/src/Repository/SystemConfigRepository.php +++ b/src/Repository/SystemConfigRepository.php @@ -26,29 +26,9 @@ class SystemConfigRepository extends AbstractRepository if (!($config instanceof SystemConfig)) { $config = new SystemConfig(); $config->setKey($key); - $config->setValue($key->getDefaultValue($key)); + $config->setValue($key->defaultValue($key)); $this->save($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); - } } diff --git a/src/Service/Config/AppName.php b/src/Service/Config/AppName.php index c8a5233..43cc62d 100644 --- a/src/Service/Config/AppName.php +++ b/src/Service/Config/AppName.php @@ -17,6 +17,6 @@ final readonly class AppName implements Stringable public function __toString(): string { - return $this->configService->getConfigValue(SystemSettingKey::SYSTEM_NAME, SystemConfig::DEFAULT_SYSTEM_NAME); + return $this->configService->get(SystemSettingKey::SYSTEM_NAME); } } diff --git a/src/Service/ConfigurationService.php b/src/Service/ConfigurationService.php index 7ff0ed3..57d1202 100644 --- a/src/Service/ConfigurationService.php +++ b/src/Service/ConfigurationService.php @@ -15,64 +15,30 @@ readonly class ConfigurationService private SystemConfigRepository $systemConfigRepository, ) {} - /** - * Get all configuration entries - * - * @return SystemConfig[] - */ - public function getAllConfigs(): array + public function get(SystemSettingKey $key): string { - 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); - } - - 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 = $this->systemConfigRepository->findByKey($key); + if ($config->getValue() === $value) { + return; } - $config = new SystemConfig(); - $config->setKey($key); $config->setValue($value); $this->systemConfigRepository->save($config); - - return $config; } - - public function updateConfig(SystemConfig $config, string $value): SystemConfig + public function reset(SystemSettingKey $key): void { - if ($value !== '') { - $config->setValue($value); - } - $this->systemConfigRepository->save($config); - - return $config; + $this->set($key, $key->defaultValue()); } - public function resetAllConfigs(): void + public function resetAll(): void { foreach (SystemSettingKey::cases() as $key) { - $this->setDefaultValue($key); + $this->reset($key); } } - - public function setDefaultValue(SystemSettingKey $key): void - { - $this->setConfigValue($key, SystemSettingKey::getDefaultValue($key)); - } } diff --git a/src/Service/DrinkTypeService.php b/src/Service/DrinkTypeService.php deleted file mode 100644 index ae46915..0000000 --- a/src/Service/DrinkTypeService.php +++ /dev/null @@ -1,144 +0,0 @@ -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); - } -} diff --git a/src/Service/DrinkTypeUpdate.php b/src/Service/DrinkTypeUpdate.php new file mode 100644 index 0000000..8b2b087 --- /dev/null +++ b/src/Service/DrinkTypeUpdate.php @@ -0,0 +1,61 @@ + $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); + } + } +} diff --git a/src/Service/InventoryService.php b/src/Service/InventoryService.php deleted file mode 100644 index 0697c0a..0000000 --- a/src/Service/InventoryService.php +++ /dev/null @@ -1,125 +0,0 @@ -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(), - ); - } -} diff --git a/src/Service/OrderService.php b/src/Service/OrderService.php index 0e3d7b9..733e905 100644 --- a/src/Service/OrderService.php +++ b/src/Service/OrderService.php @@ -20,7 +20,6 @@ readonly class OrderService private OrderRepository $orderRepository, private OrderItemRepository $orderItemRepository, private DrinkTypeRepository $drinkTypeRepository, - private InventoryService $inventoryService, ) {} /** diff --git a/src/Service/StockAdjustmentService.php b/src/Service/StockAdjustmentService.php deleted file mode 100644 index c9735e8..0000000 --- a/src/Service/StockAdjustmentService.php +++ /dev/null @@ -1,124 +0,0 @@ - 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); - } -} diff --git a/templates/drink_type/show.html.twig b/templates/drink_type/show.html.twig index 694300d..15a7084 100644 --- a/templates/drink_type/show.html.twig +++ b/templates/drink_type/show.html.twig @@ -124,7 +124,6 @@ Date - Old Value New Value @@ -132,7 +131,6 @@ {% for log in desired_stock_history %} {{ log.changeDate|date('Y-m-d H:i:s') }} - {{ log.oldValue }} {{ log.newValue }} {% endfor %} diff --git a/tests/DbTestCase.php b/tests/DbTestCase.php index 475f610..af2a475 100644 --- a/tests/DbTestCase.php +++ b/tests/DbTestCase.php @@ -11,6 +11,16 @@ use Doctrine\ORM\Tools\SchemaTool; abstract class DbTestCase extends TestCase { + /** + * @template T of object + * @param class-string $id + * @return object + */ + protected function get(string $id): object + { + return $this->getContainer()->get($id); + } + protected function setUp(): void { $em = $this->getContainer()->get(EntityManagerInterface::class); diff --git a/tests/Feature/Entity/DrinkTypePropertyChangeLogTest.php b/tests/Feature/Entity/DrinkTypePropertyChangeLogTest.php deleted file mode 100644 index 862cd6f..0000000 --- a/tests/Feature/Entity/DrinkTypePropertyChangeLogTest.php +++ /dev/null @@ -1,48 +0,0 @@ -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'); -}); diff --git a/tests/Feature/Entity/DrinkTypeTest.php b/tests/Feature/Entity/DrinkTypeTest.php new file mode 100644 index 0000000..9c3236c --- /dev/null +++ b/tests/Feature/Entity/DrinkTypeTest.php @@ -0,0 +1,41 @@ +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); +}); diff --git a/tests/Feature/FeatureTestBase.php b/tests/Feature/FeatureTestBase.php deleted file mode 100644 index f8fbb5c..0000000 --- a/tests/Feature/FeatureTestBase.php +++ /dev/null @@ -1,19 +0,0 @@ -in(__DIR__); - -beforeEach(function (): void { - $em = self::getContainer()->get(EntityManagerInterface::class); - createDatabaseSchema($em); -}); - -afterEach(function (): void { - $em = self::getContainer()->get(EntityManagerInterface::class); - deleteDatabaseFile($em); -}); diff --git a/tests/Feature/Service/Config/AppNameTest.php b/tests/Feature/Service/Config/AppNameTest.php new file mode 100644 index 0000000..bd4cb89 --- /dev/null +++ b/tests/Feature/Service/Config/AppNameTest.php @@ -0,0 +1,18 @@ +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); + +}); diff --git a/tests/Feature/Service/Config/ConfigServicesTest.php b/tests/Feature/Service/Config/ConfigServicesTest.php deleted file mode 100644 index b1005fa..0000000 --- a/tests/Feature/Service/Config/ConfigServicesTest.php +++ /dev/null @@ -1,88 +0,0 @@ -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(); -}); diff --git a/tests/Feature/Service/ConfigurationServiceTest.php b/tests/Feature/Service/ConfigurationServiceTest.php index 20e5ab2..f7af002 100644 --- a/tests/Feature/Service/ConfigurationServiceTest.php +++ b/tests/Feature/Service/ConfigurationServiceTest.php @@ -6,142 +6,52 @@ use App\Entity\SystemConfig; use App\Enum\SystemSettingKey; use App\Service\ConfigurationService; -test('getAllConfigs returns all configurations', function (): void { - // Arrange - $configService = $this->getContainer()->get(ConfigurationService::class); - - // Act - $configs = $configService->getAllConfigs(); - - // Assert - expect($configs)->toBeArray(); -}); - -test('getConfigValue returns correct value', function (): void { + +test('get returns correct value', function (): void { // Arrange + /** @var ConfigurationService $configService */ $configService = $this->getContainer()->get(ConfigurationService::class); $key = SystemSettingKey::SYSTEM_NAME; - $expectedValue = SystemSettingKey::getDefaultValue($key); // Act - $value = $configService->getConfigValue($key); + $value = $configService->get($key); // Assert - expect($value)->toBe($expectedValue); + expect($value)->toBe($key->defaultValue()); }); test('setConfigValue updates configuration value', function (): void { // Arrange + /** @var ConfigurationService $configService */ $configService = $this->getContainer()->get(ConfigurationService::class); $key = SystemSettingKey::SYSTEM_NAME; $newValue = 'Test System Name'; // Act - $configService->setConfigValue($key, $newValue); - $value = $configService->getConfigValue($key); + $configService->set($key, $newValue); + $value = $configService->get($key); // Assert 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 { // Arrange + /** @var ConfigurationService $configService */ $configService = $this->getContainer()->get(ConfigurationService::class); // Set non-default values for all configs foreach (SystemSettingKey::cases() as $key) { - $configService->setConfigValue($key, 'non-default-value'); + $configService->set($key, 'non-default value'); } // Act - $configService->resetAllConfigs(); + $configService->resetAll(); // Assert foreach (SystemSettingKey::cases() as $key) { - $expectedValue = SystemSettingKey::getDefaultValue($key); - $actualValue = $configService->getConfigValue($key); - expect($actualValue)->toBe($expectedValue); + + expect($configService->get($key))->toBe($key->defaultValue()); } }); - -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); -}); diff --git a/tests/Feature/Service/DrinkTypeServiceTest.php b/tests/Feature/Service/DrinkTypeServiceTest.php deleted file mode 100644 index c3fdf8d..0000000 --- a/tests/Feature/Service/DrinkTypeServiceTest.php +++ /dev/null @@ -1,243 +0,0 @@ -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(); -}); diff --git a/tests/Feature/Service/InventoryServiceTest.php b/tests/Feature/Service/InventoryServiceTest.php deleted file mode 100644 index 8569ddd..0000000 --- a/tests/Feature/Service/InventoryServiceTest.php +++ /dev/null @@ -1,366 +0,0 @@ -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); -}); diff --git a/tests/Feature/Service/OrderServiceTest.php b/tests/Feature/Service/OrderServiceTest.php deleted file mode 100644 index 26e3777..0000000 --- a/tests/Feature/Service/OrderServiceTest.php +++ /dev/null @@ -1,461 +0,0 @@ -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(); -});