tetssssss
This commit is contained in:
parent
6f07d70436
commit
8063e7bec9
28 changed files with 771 additions and 273 deletions
|
@ -25,6 +25,9 @@
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"pestphp/pest": "^3.8",
|
"pestphp/pest": "^3.8",
|
||||||
"rector/rector": "^2.0",
|
"rector/rector": "^2.0",
|
||||||
|
"symfony/browser-kit": "^7.3",
|
||||||
|
"symfony/css-selector": "7.3.*",
|
||||||
|
"symfony/dom-crawler": "7.3.*",
|
||||||
"symfony/maker-bundle": "^1.63",
|
"symfony/maker-bundle": "^1.63",
|
||||||
"symfony/stopwatch": "7.3.*",
|
"symfony/stopwatch": "7.3.*",
|
||||||
"symfony/web-profiler-bundle": "7.3.*",
|
"symfony/web-profiler-bundle": "7.3.*",
|
||||||
|
|
269
composer.lock
generated
269
composer.lock
generated
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "139ab419de1a7d3014e5595dbe864775",
|
"content-hash": "7dbc573cc9b3d6ab3fb532611e469c70",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "doctrine/cache",
|
"name": "doctrine/cache",
|
||||||
|
@ -5196,6 +5196,73 @@
|
||||||
},
|
},
|
||||||
"time": "2025-03-19T14:43:43+00:00"
|
"time": "2025-03-19T14:43:43+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "masterminds/html5",
|
||||||
|
"version": "2.9.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/Masterminds/html5-php.git",
|
||||||
|
"reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f5ac2c0b0a2eefca70b2ce32a5809992227e75a6",
|
||||||
|
"reference": "f5ac2c0b0a2eefca70b2ce32a5809992227e75a6",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-dom": "*",
|
||||||
|
"php": ">=5.3.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "2.7-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Masterminds\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Matt Butcher",
|
||||||
|
"email": "technosophos@gmail.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Matt Farina",
|
||||||
|
"email": "matt@mattfarina.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Asmir Mustafic",
|
||||||
|
"email": "goetas@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "An HTML5 parser and serializer.",
|
||||||
|
"homepage": "http://masterminds.github.io/html5-php",
|
||||||
|
"keywords": [
|
||||||
|
"HTML5",
|
||||||
|
"dom",
|
||||||
|
"html",
|
||||||
|
"parser",
|
||||||
|
"querypath",
|
||||||
|
"serializer",
|
||||||
|
"xml"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/Masterminds/html5-php/issues",
|
||||||
|
"source": "https://github.com/Masterminds/html5-php/tree/2.9.0"
|
||||||
|
},
|
||||||
|
"time": "2024-03-31T07:05:07+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "myclabs/deep-copy",
|
"name": "myclabs/deep-copy",
|
||||||
"version": "1.13.1",
|
"version": "1.13.1",
|
||||||
|
@ -7746,6 +7813,206 @@
|
||||||
],
|
],
|
||||||
"time": "2024-10-20T05:08:20+00:00"
|
"time": "2024-10-20T05:08:20+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/browser-kit",
|
||||||
|
"version": "v7.3.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/browser-kit.git",
|
||||||
|
"reference": "5384291845e74fd7d54f3d925c4a86ce12336593"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/browser-kit/zipball/5384291845e74fd7d54f3d925c4a86ce12336593",
|
||||||
|
"reference": "5384291845e74fd7d54f3d925c4a86ce12336593",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.2",
|
||||||
|
"symfony/dom-crawler": "^6.4|^7.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/css-selector": "^6.4|^7.0",
|
||||||
|
"symfony/http-client": "^6.4|^7.0",
|
||||||
|
"symfony/mime": "^6.4|^7.0",
|
||||||
|
"symfony/process": "^6.4|^7.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\BrowserKit\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Simulates the behavior of a web browser, allowing you to make requests, click on links and submit forms programmatically",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/browser-kit/tree/v7.3.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-03-05T10:15:41+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/css-selector",
|
||||||
|
"version": "v7.3.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/css-selector.git",
|
||||||
|
"reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/css-selector/zipball/601a5ce9aaad7bf10797e3663faefce9e26c24e2",
|
||||||
|
"reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=8.2"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\CssSelector\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jean-François Simon",
|
||||||
|
"email": "jeanfrancois.simon@sensiolabs.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Converts CSS selectors to XPath expressions",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/css-selector/tree/v7.3.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2024-09-25T14:21:43+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/dom-crawler",
|
||||||
|
"version": "v7.3.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/dom-crawler.git",
|
||||||
|
"reference": "0fabbc3d6a9c473b716a93fc8e7a537adb396166"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/dom-crawler/zipball/0fabbc3d6a9c473b716a93fc8e7a537adb396166",
|
||||||
|
"reference": "0fabbc3d6a9c473b716a93fc8e7a537adb396166",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"masterminds/html5": "^2.6",
|
||||||
|
"php": ">=8.2",
|
||||||
|
"symfony/polyfill-ctype": "~1.8",
|
||||||
|
"symfony/polyfill-mbstring": "~1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/css-selector": "^6.4|^7.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\DomCrawler\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Eases DOM navigation for HTML and XML documents",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/dom-crawler/tree/v7.3.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-03-05T10:15:41+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/maker-bundle",
|
"name": "symfony/maker-bundle",
|
||||||
"version": "v1.63.0",
|
"version": "v1.63.0",
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Doctrine\ORM\Events;
|
|
||||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
||||||
|
|
||||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
return static function (ContainerConfigurator $containerConfigurator): void {
|
||||||
|
|
|
@ -58,8 +58,10 @@ final class DrinkTypeController extends AbstractController
|
||||||
$desiredStockHistory = $propertyChangeLogRepository->findBy([
|
$desiredStockHistory = $propertyChangeLogRepository->findBy([
|
||||||
'entityClass' => DrinkType::class,
|
'entityClass' => DrinkType::class,
|
||||||
'propertyName' => 'desiredStock',
|
'propertyName' => 'desiredStock',
|
||||||
'entityId' => $drinkType->getId()
|
'entityId' => $drinkType->getId(),
|
||||||
], ['changeDate' => 'DESC']);
|
], [
|
||||||
|
'changeDate' => 'DESC',
|
||||||
|
]);
|
||||||
|
|
||||||
return $this->render('drink_type/show.html.twig', [
|
return $this->render('drink_type/show.html.twig', [
|
||||||
'drink_type' => $drinkType,
|
'drink_type' => $drinkType,
|
||||||
|
|
|
@ -4,9 +4,6 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Controller;
|
namespace App\Controller;
|
||||||
|
|
||||||
use App\Enum\StockState;
|
|
||||||
use App\Service\InventoryService;
|
|
||||||
use App\ValueObject\DrinkStock;
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Attribute\Route;
|
use Symfony\Component\Routing\Attribute\Route;
|
||||||
|
@ -14,19 +11,8 @@ 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 __invoke(): Response
|
public function __invoke(): Response
|
||||||
{
|
{
|
||||||
$drinkStocks = $this->inventoryService->getAllDrinkTypesWithStockLevels();
|
return new Response('<h1>Hello World!</h1>');
|
||||||
|
|
||||||
$low = array_filter(
|
|
||||||
$drinkStocks,
|
|
||||||
fn(DrinkStock $stock): bool => $stock->stock === StockState::LOW || $stock->stock === StockState::CRITICAL,
|
|
||||||
);
|
|
||||||
|
|
||||||
return $this->render('index.html.twig', [
|
|
||||||
'drinkStocks' => $drinkStocks,
|
|
||||||
'low' => $low,
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,14 @@ namespace App\Entity;
|
||||||
|
|
||||||
use App\Repository\PropertyChangeLogRepository;
|
use App\Repository\PropertyChangeLogRepository;
|
||||||
use Doctrine\ORM\Mapping\Column;
|
use Doctrine\ORM\Mapping\Column;
|
||||||
|
use Doctrine\ORM\Mapping\Entity;
|
||||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Doctrine\ORM\Mapping\Id;
|
use Doctrine\ORM\Mapping\Id;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping\Table;
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: PropertyChangeLogRepository::class)]
|
#[Entity(repositoryClass: PropertyChangeLogRepository::class)]
|
||||||
#[ORM\Table(name: 'property_change_log')]
|
#[Table(name: 'property_change_log')]
|
||||||
final class PropertyChangeLog
|
final class PropertyChangeLog
|
||||||
{
|
{
|
||||||
#[Id]
|
#[Id]
|
||||||
|
|
|
@ -1,21 +1,20 @@
|
||||||
<?php declare(strict_types=1);
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\EventListener;
|
namespace App\EventListener;
|
||||||
|
|
||||||
|
|
||||||
use App\Entity\DrinkType;
|
use App\Entity\DrinkType;
|
||||||
use App\Entity\PropertyChangeLog;
|
use App\Service\DrinkType\DrinkTypeUpdateLog;
|
||||||
use App\Service\DrinkTypeUpdate;
|
|
||||||
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
|
||||||
use Doctrine\ORM\Events;
|
use Doctrine\ORM\Events;
|
||||||
use Doctrine\Persistence\Event\LifecycleEventArgs;
|
use Doctrine\Persistence\Event\LifecycleEventArgs;
|
||||||
|
|
||||||
#[AsDoctrineListener(event: Events::postPersist)]
|
#[AsDoctrineListener(event: Events::postPersist)]
|
||||||
#[AsDoctrineListener(event: Events::postUpdate)]
|
#[AsDoctrineListener(event: Events::postUpdate)]
|
||||||
final class PostPersistUpdateListener
|
final readonly class PostPersistUpdateListener
|
||||||
{
|
{
|
||||||
public function __construct(private DrinkTypeUpdate $drinkTypeUpdate) {}
|
public function __construct(private DrinkTypeUpdateLog $drinkTypeUpdate) {}
|
||||||
public function postPersist(LifecycleEventArgs $args): void
|
public function postPersist(LifecycleEventArgs $args): void
|
||||||
{
|
{
|
||||||
$this->log(Events::postPersist, $args);
|
$this->log(Events::postPersist, $args);
|
||||||
|
@ -28,7 +27,7 @@ final class PostPersistUpdateListener
|
||||||
private function log(string $action, LifecycleEventArgs $args): void
|
private function log(string $action, LifecycleEventArgs $args): void
|
||||||
{
|
{
|
||||||
$entity = $args->getObject();
|
$entity = $args->getObject();
|
||||||
match($entity::class) {
|
match ($entity::class) {
|
||||||
DrinkType::class => $this->drinkTypeUpdate->handleLog($action, $entity, $args->getObjectManager()),
|
DrinkType::class => $this->drinkTypeUpdate->handleLog($action, $entity, $args->getObjectManager()),
|
||||||
default => null,
|
default => null,
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,20 +25,20 @@ class DrinkTypeRepository extends AbstractRepository
|
||||||
return parent::findBy(
|
return parent::findBy(
|
||||||
criteria: [],
|
criteria: [],
|
||||||
orderBy: [
|
orderBy: [
|
||||||
'desiredStock' => 'DESC',
|
'wantedStock' => 'DESC',
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return DrinkType[] */
|
/** @return DrinkType[] */
|
||||||
public function findDesired(): array
|
public function findWanted(): array
|
||||||
{
|
{
|
||||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||||
$qb
|
$qb
|
||||||
->select('d')
|
->select('d')
|
||||||
->from(DrinkType::class, 'd')
|
->from(DrinkType::class, 'd')
|
||||||
->where('d.desiredStock > 0')
|
->where('d.wantedStock > 0')
|
||||||
->orderBy('d.desiredStock', 'DESC')
|
->orderBy('d.wantedStock', 'DESC')
|
||||||
->addOrderBy('d.name', 'ASC');
|
->addOrderBy('d.name', 'ASC');
|
||||||
|
|
||||||
/** @var array<int, DrinkType> $result */
|
/** @var array<int, DrinkType> $result */
|
||||||
|
|
|
@ -26,7 +26,7 @@ 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->defaultValue($key));
|
$config->setValue($key->defaultValue());
|
||||||
$this->save($config);
|
$this->save($config);
|
||||||
}
|
}
|
||||||
return $config;
|
return $config;
|
||||||
|
|
|
@ -4,7 +4,6 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Service\Config;
|
namespace App\Service\Config;
|
||||||
|
|
||||||
use App\Entity\SystemConfig;
|
|
||||||
use App\Enum\SystemSettingKey;
|
use App\Enum\SystemSettingKey;
|
||||||
use App\Service\ConfigurationService;
|
use App\Service\ConfigurationService;
|
||||||
use Stringable;
|
use Stringable;
|
||||||
|
|
|
@ -4,7 +4,6 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Service\Config;
|
namespace App\Service\Config;
|
||||||
|
|
||||||
use App\Entity\SystemConfig;
|
|
||||||
use App\Enum\SystemSettingKey;
|
use App\Enum\SystemSettingKey;
|
||||||
use App\Service\ConfigurationService;
|
use App\Service\ConfigurationService;
|
||||||
|
|
||||||
|
@ -16,11 +15,6 @@ final readonly class LowStockMultiplier
|
||||||
|
|
||||||
public function getValue(): float
|
public function getValue(): float
|
||||||
{
|
{
|
||||||
$value = $this->configService->getConfigValue(
|
return (float) $this->configService->get(SystemSettingKey::STOCK_LOW_MULTIPLIER);
|
||||||
SystemSettingKey::STOCK_LOW_MULTIPLIER,
|
|
||||||
SystemConfig::DEFAULT_STOCK_LOW_MULTIPLIER,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (float) $value;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,8 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Service;
|
namespace App\Service;
|
||||||
|
|
||||||
use App\Entity\SystemConfig;
|
|
||||||
use App\Enum\SystemSettingKey;
|
use App\Enum\SystemSettingKey;
|
||||||
use App\Repository\SystemConfigRepository;
|
use App\Repository\SystemConfigRepository;
|
||||||
use InvalidArgumentException;
|
|
||||||
|
|
||||||
readonly class ConfigurationService
|
readonly class ConfigurationService
|
||||||
{
|
{
|
||||||
|
|
30
src/Service/DrinkType/CreateNewOrderItems.php
Normal file
30
src/Service/DrinkType/CreateNewOrderItems.php
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Service\DrinkType;
|
||||||
|
|
||||||
|
use App\Entity\DrinkType;
|
||||||
|
use App\Entity\Order;
|
||||||
|
use App\Entity\OrderItem;
|
||||||
|
use App\Repository\DrinkTypeRepository;
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
|
||||||
|
final readonly class CreateNewOrderItems
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private DrinkTypeRepository $drinkTypeRepository,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function __invoke(Order $order): void
|
||||||
|
{
|
||||||
|
new ArrayCollection($this->drinkTypeRepository->findWanted())->forAll(
|
||||||
|
fn (DrinkType $drinkType) => $order
|
||||||
|
->addOrderItem(
|
||||||
|
new OrderItem()
|
||||||
|
->setDrinkType($drinkType)
|
||||||
|
->setQuantity($drinkType->getWantedStock() - $drinkType->getCurrentStock())
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
63
src/Service/DrinkType/DrinkTypeUpdateLog.php
Normal file
63
src/Service/DrinkType/DrinkTypeUpdateLog.php
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Service\DrinkType;
|
||||||
|
|
||||||
|
use App\Entity\DrinkType;
|
||||||
|
use App\Entity\PropertyChangeLog;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
use Doctrine\ORM\Events;
|
||||||
|
|
||||||
|
final class DrinkTypeUpdateLog
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
src/Service/DrinkType/GetStockHistory.php
Normal file
32
src/Service/DrinkType/GetStockHistory.php
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Service\DrinkType;
|
||||||
|
|
||||||
|
use App\Entity\DrinkType;
|
||||||
|
use App\Entity\PropertyChangeLog;
|
||||||
|
use App\Repository\PropertyChangeLogRepository;
|
||||||
|
|
||||||
|
final readonly class GetStockHistory
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private PropertyChangeLogRepository $propertyChangeLogRepository,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param DrinkType $drinkType
|
||||||
|
* @return PropertyChangeLog[]
|
||||||
|
*/
|
||||||
|
public function __invoke(DrinkType $drinkType): array
|
||||||
|
{
|
||||||
|
if ($drinkType->getId() === null) {
|
||||||
|
return[];
|
||||||
|
}
|
||||||
|
return $this->propertyChangeLogRepository->findBy([
|
||||||
|
'entityClass' => DrinkType::class,
|
||||||
|
'propertyName' => 'currentStock',
|
||||||
|
'entityId' => $drinkType->getId(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
27
src/Service/DrinkType/GetWantedHistory.php
Normal file
27
src/Service/DrinkType/GetWantedHistory.php
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Service\DrinkType;
|
||||||
|
|
||||||
|
use App\Entity\DrinkType;
|
||||||
|
use App\Entity\PropertyChangeLog;
|
||||||
|
use App\Repository\PropertyChangeLogRepository;
|
||||||
|
|
||||||
|
final readonly class GetWantedHistory
|
||||||
|
{
|
||||||
|
public function __construct(private PropertyChangeLogRepository $propertyChangeLogRepository) {}
|
||||||
|
/** @return PropertyChangeLog[] */
|
||||||
|
public function __invoke(DrinkType $drinkType): array
|
||||||
|
{
|
||||||
|
if ($drinkType->getId() === null) {
|
||||||
|
return[];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->propertyChangeLogRepository->findBy([
|
||||||
|
'entityClass' => DrinkType::class,
|
||||||
|
'propertyName' => 'wantedStock',
|
||||||
|
'entityId' => $drinkType->getId(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,61 +0,0 @@
|
||||||
<?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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,7 +4,6 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace App\Service;
|
namespace App\Service;
|
||||||
|
|
||||||
use App\Entity\DrinkType;
|
|
||||||
use App\Entity\Order;
|
use App\Entity\Order;
|
||||||
use App\Entity\OrderItem;
|
use App\Entity\OrderItem;
|
||||||
use App\Enum\OrderStatus;
|
use App\Enum\OrderStatus;
|
||||||
|
@ -132,111 +131,4 @@ readonly class OrderService
|
||||||
|
|
||||||
return $order;
|
return $order;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an order based on current stock levels
|
|
||||||
*
|
|
||||||
* @return Order
|
|
||||||
*/
|
|
||||||
public function createOrderFromStockLevels(): Order
|
|
||||||
{
|
|
||||||
$lowStockItems = $this->inventoryService->getAllDrinkTypesWithStockLevels();
|
|
||||||
$orderItems = [];
|
|
||||||
|
|
||||||
foreach ($lowStockItems as $item) {
|
|
||||||
if ($item->record->getQuantity() < $item->record->getDrinkType()->getDesiredStock()) {
|
|
||||||
$orderItems[] = [
|
|
||||||
'drinkTypeId' => $item->record->getDrinkType()->getId(),
|
|
||||||
'quantity' => $item->record->getDrinkType()->getDesiredStock() - $item->record->getQuantity(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->createOrder($orderItems);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update an order's status
|
|
||||||
*
|
|
||||||
* @return Order
|
|
||||||
* @throws InvalidArgumentException If the status is invalid
|
|
||||||
*/
|
|
||||||
public function updateOrderStatus(Order $order, OrderStatus $status): Order
|
|
||||||
{
|
|
||||||
$order->setStatus($status);
|
|
||||||
$this->orderRepository->save($order);
|
|
||||||
|
|
||||||
return $order;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an item to an order
|
|
||||||
*
|
|
||||||
* @param Order $order
|
|
||||||
* @param DrinkType $drinkType
|
|
||||||
* @param int $quantity
|
|
||||||
* @return OrderItem
|
|
||||||
* @throws InvalidArgumentException If the order is not in 'new' or 'in_work' status
|
|
||||||
*/
|
|
||||||
public function addOrderItem(Order $order, DrinkType $drinkType, int $quantity): OrderItem
|
|
||||||
{
|
|
||||||
if (!in_array($order->getStatus(), [OrderStatus::NEW, OrderStatus::IN_WORK], true)) {
|
|
||||||
throw new InvalidArgumentException(
|
|
||||||
"Cannot add items to an order with status '{$order->getStatus()->value}'",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the order already has an item for this drink type
|
|
||||||
$existingItem = $this->orderItemRepository->findByOrderAndDrinkType($order, $drinkType);
|
|
||||||
|
|
||||||
if ($existingItem instanceof OrderItem) {
|
|
||||||
// Update the existing item
|
|
||||||
$existingItem->setQuantity($existingItem->getQuantity() + $quantity);
|
|
||||||
$this->orderItemRepository->save($existingItem);
|
|
||||||
return $existingItem;
|
|
||||||
}
|
|
||||||
// Create a new item
|
|
||||||
$orderItem = new OrderItem();
|
|
||||||
$orderItem->setQuantity($quantity);
|
|
||||||
$orderItem->setOrder($order);
|
|
||||||
$orderItem->setDrinkType($drinkType);
|
|
||||||
$this->orderItemRepository->save($orderItem);
|
|
||||||
return $orderItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove an item from an order
|
|
||||||
*
|
|
||||||
* @param Order $order
|
|
||||||
* @param OrderItem $orderItem
|
|
||||||
* @return void
|
|
||||||
* @throws InvalidArgumentException If the order is not in 'new' or 'in_work' status
|
|
||||||
*/
|
|
||||||
public function removeOrderItem(Order $order, OrderItem $orderItem): void
|
|
||||||
{
|
|
||||||
if (!in_array($order->getStatus(), [OrderStatus::NEW, OrderStatus::IN_WORK], true)) {
|
|
||||||
throw new InvalidArgumentException(
|
|
||||||
"Cannot remove items from an order with status '{$order->getStatus()->value}'",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$order->removeOrderItem($orderItem);
|
|
||||||
$this->orderItemRepository->remove($orderItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete an order
|
|
||||||
*
|
|
||||||
* @param Order $order
|
|
||||||
* @return void
|
|
||||||
* @throws InvalidArgumentException If the order is not in 'new' status
|
|
||||||
*/
|
|
||||||
public function deleteOrder(Order $order): void
|
|
||||||
{
|
|
||||||
if ($order->getStatus() !== OrderStatus::NEW && $order->getStatus() !== OrderStatus::IN_WORK) {
|
|
||||||
throw new InvalidArgumentException("Cannot delete an order with status '{$order->getStatus()->value}'");
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->orderRepository->remove($order);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace App\ValueObject;
|
|
||||||
|
|
||||||
use App\Entity\InventoryRecord;
|
|
||||||
use App\Enum\StockState;
|
|
||||||
|
|
||||||
final readonly class DrinkStock
|
|
||||||
{
|
|
||||||
public function __construct(
|
|
||||||
public InventoryRecord $record,
|
|
||||||
public StockState $stock,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public static function fromInventoryRecord(InventoryRecord $record, float $lowStockMultiplier): self
|
|
||||||
{
|
|
||||||
if ($record->getQuantity() === 0 && $record->getDrinkType()->getDesiredStock() > 0) {
|
|
||||||
return new self($record, StockState::CRITICAL);
|
|
||||||
}
|
|
||||||
if ($record->getQuantity() < ($record->getDrinkType()->getDesiredStock() * $lowStockMultiplier)) {
|
|
||||||
return new self($record, StockState::LOW);
|
|
||||||
}
|
|
||||||
if ($record->getQuantity() > $record->getDrinkType()->getDesiredStock()) {
|
|
||||||
return new self($record, StockState::HIGH);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new self($record, StockState::NORMAL);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +1,12 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use App\Entity\DrinkType;
|
use App\Entity\DrinkType;
|
||||||
use App\Entity\PropertyChangeLog;
|
use App\Entity\PropertyChangeLog;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
test('Update Listener', function () {
|
test('Update Listener', function (): void {
|
||||||
$drinkType = new DrinkType();
|
$drinkType = new DrinkType();
|
||||||
$drinkType->setName('test');
|
$drinkType->setName('test');
|
||||||
$drinkType->setWantedStock(10);
|
$drinkType->setWantedStock(10);
|
||||||
|
@ -16,26 +18,34 @@ test('Update Listener', function () {
|
||||||
$em->flush();
|
$em->flush();
|
||||||
|
|
||||||
$propertyLogRepository = $em->getRepository(PropertyChangeLog::class);
|
$propertyLogRepository = $em->getRepository(PropertyChangeLog::class);
|
||||||
$logs = $propertyLogRepository->findBy(['entityClass' => DrinkType::class]);
|
$logs = $propertyLogRepository->findBy([
|
||||||
|
'entityClass' => DrinkType::class,
|
||||||
|
]);
|
||||||
expect($logs)->toHaveCount(2);
|
expect($logs)->toHaveCount(2);
|
||||||
|
|
||||||
$drinkType->setWantedStock(15);
|
$drinkType->setWantedStock(15);
|
||||||
$em->persist($drinkType);
|
$em->persist($drinkType);
|
||||||
$em->flush();
|
$em->flush();
|
||||||
|
|
||||||
$logs = $propertyLogRepository->findBy(['entityClass' => DrinkType::class]);
|
$logs = $propertyLogRepository->findBy([
|
||||||
|
'entityClass' => DrinkType::class,
|
||||||
|
]);
|
||||||
expect($logs)->toHaveCount(3);
|
expect($logs)->toHaveCount(3);
|
||||||
|
|
||||||
$drinkType->setCurrentStock(15);
|
$drinkType->setCurrentStock(15);
|
||||||
$em->persist($drinkType);
|
$em->persist($drinkType);
|
||||||
$em->flush();
|
$em->flush();
|
||||||
|
|
||||||
$logs = $propertyLogRepository->findBy(['entityClass' => DrinkType::class]);
|
$logs = $propertyLogRepository->findBy([
|
||||||
|
'entityClass' => DrinkType::class,
|
||||||
|
]);
|
||||||
expect($logs)->toHaveCount(4);
|
expect($logs)->toHaveCount(4);
|
||||||
|
|
||||||
$drinkType->setDescription('test');
|
$drinkType->setDescription('test');
|
||||||
$em->persist($drinkType);
|
$em->persist($drinkType);
|
||||||
$em->flush();
|
$em->flush();
|
||||||
$logs = $propertyLogRepository->findBy(['entityClass' => DrinkType::class]);
|
$logs = $propertyLogRepository->findBy([
|
||||||
|
'entityClass' => DrinkType::class,
|
||||||
|
]);
|
||||||
expect($logs)->toHaveCount(4);
|
expect($logs)->toHaveCount(4);
|
||||||
});
|
});
|
||||||
|
|
105
tests/Feature/Repository/DrinkTypeRepositoryTest.php
Normal file
105
tests/Feature/Repository/DrinkTypeRepositoryTest.php
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use App\Entity\DrinkType;
|
||||||
|
use App\Repository\DrinkTypeRepository;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
|
test('findAll returns all drink types ordered by wantedStock DESC', function (): void {
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
$repository = $this->getContainer()->get(DrinkTypeRepository::class);
|
||||||
|
|
||||||
|
// Clear existing drink types
|
||||||
|
$existingDrinkTypes = $repository->findAll();
|
||||||
|
foreach ($existingDrinkTypes as $drinkType) {
|
||||||
|
$em->remove($drinkType);
|
||||||
|
}
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Create test drink types with different wantedStock values
|
||||||
|
$drinkType1 = new DrinkType();
|
||||||
|
$drinkType1->setName('Drink Type 1');
|
||||||
|
$drinkType1->setWantedStock(5);
|
||||||
|
|
||||||
|
$drinkType2 = new DrinkType();
|
||||||
|
$drinkType2->setName('Drink Type 2');
|
||||||
|
$drinkType2->setWantedStock(10);
|
||||||
|
|
||||||
|
$drinkType3 = new DrinkType();
|
||||||
|
$drinkType3->setName('Drink Type 3');
|
||||||
|
$drinkType3->setWantedStock(0);
|
||||||
|
|
||||||
|
// Persist the drink types
|
||||||
|
$em->persist($drinkType1);
|
||||||
|
$em->persist($drinkType2);
|
||||||
|
$em->persist($drinkType3);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Test findAll method
|
||||||
|
$result = $repository->findAll();
|
||||||
|
|
||||||
|
// Verify the result
|
||||||
|
expect($result)->toHaveCount(3);
|
||||||
|
expect($result[0]->getName())->toBe('Drink Type 2');
|
||||||
|
expect($result[1]->getName())->toBe('Drink Type 1');
|
||||||
|
expect($result[2]->getName())->toBe('Drink Type 3');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('findWanted returns only drink types with wantedStock > 0 ordered by wantedStock DESC and name ASC', function (): void {
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
$repository = $this->getContainer()->get(DrinkTypeRepository::class);
|
||||||
|
|
||||||
|
// Clear existing drink types
|
||||||
|
$existingDrinkTypes = $repository->findAll();
|
||||||
|
foreach ($existingDrinkTypes as $drinkType) {
|
||||||
|
$em->remove($drinkType);
|
||||||
|
}
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Create test drink types with different wantedStock values
|
||||||
|
$drinkType1 = new DrinkType();
|
||||||
|
$drinkType1->setName('Cola');
|
||||||
|
$drinkType1->setWantedStock(10);
|
||||||
|
|
||||||
|
$drinkType2 = new DrinkType();
|
||||||
|
$drinkType2->setName('Beer');
|
||||||
|
$drinkType2->setWantedStock(10);
|
||||||
|
|
||||||
|
$drinkType3 = new DrinkType();
|
||||||
|
$drinkType3->setName('Water');
|
||||||
|
$drinkType3->setWantedStock(5);
|
||||||
|
|
||||||
|
$drinkType4 = new DrinkType();
|
||||||
|
$drinkType4->setName('Juice');
|
||||||
|
$drinkType4->setWantedStock(0);
|
||||||
|
|
||||||
|
// Persist the drink types
|
||||||
|
$em->persist($drinkType1);
|
||||||
|
$em->persist($drinkType2);
|
||||||
|
$em->persist($drinkType3);
|
||||||
|
$em->persist($drinkType4);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Test findWanted method
|
||||||
|
$result = $repository->findWanted();
|
||||||
|
|
||||||
|
// Verify the result
|
||||||
|
expect($result)->toHaveCount(3);
|
||||||
|
|
||||||
|
// First should be Beer (wantedStock 10, name starts with B)
|
||||||
|
expect($result[0]->getName())->toBe('Beer');
|
||||||
|
expect($result[0]->getWantedStock())->toBe(10);
|
||||||
|
|
||||||
|
// Second should be Cola (wantedStock 10, name starts with C)
|
||||||
|
expect($result[1]->getName())->toBe('Cola');
|
||||||
|
expect($result[1]->getWantedStock())->toBe(10);
|
||||||
|
|
||||||
|
// Third should be Water (wantedStock 5)
|
||||||
|
expect($result[2]->getName())->toBe('Water');
|
||||||
|
expect($result[2]->getWantedStock())->toBe(5);
|
||||||
|
|
||||||
|
// Juice should not be in the result (wantedStock 0)
|
||||||
|
$names = array_map(fn($dt) => $dt->getName(), $result);
|
||||||
|
expect($names)->not->toContain('Juice');
|
||||||
|
});
|
|
@ -1,18 +1,20 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use App\Enum\SystemSettingKey;
|
use App\Enum\SystemSettingKey;
|
||||||
use App\Service\Config\AppName;
|
use App\Service\Config\AppName;
|
||||||
use App\Service\ConfigurationService;
|
use App\Service\ConfigurationService;
|
||||||
|
|
||||||
test('it returns the system name from configuration service', function () {
|
test('it returns the system name from configuration service', function (): void {
|
||||||
|
|
||||||
/** @var ConfigurationService $configService */
|
/** @var ConfigurationService $configService */
|
||||||
$configService = $this->getContainer()->get(ConfigurationService::class);
|
$configService = $this->getContainer()->get(ConfigurationService::class);
|
||||||
$appName = new AppName($configService);
|
$appName = new AppName($configService);
|
||||||
expect((string)$appName)->toBe(SystemSettingKey::SYSTEM_NAME->defaultValue());
|
expect((string) $appName)->toBe(SystemSettingKey::SYSTEM_NAME->defaultValue());
|
||||||
|
|
||||||
$expected = 'Test System Name';
|
$expected = 'Test System Name';
|
||||||
$configService->set(SystemSettingKey::SYSTEM_NAME, $expected);
|
$configService->set(SystemSettingKey::SYSTEM_NAME, $expected);
|
||||||
expect((string)$appName)->toBe($expected);
|
expect((string) $appName)->toBe($expected);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
use App\Entity\SystemConfig;
|
|
||||||
use App\Enum\SystemSettingKey;
|
use App\Enum\SystemSettingKey;
|
||||||
use App\Service\ConfigurationService;
|
use App\Service\ConfigurationService;
|
||||||
|
|
||||||
|
|
||||||
test('get returns correct value', function (): void {
|
test('get returns correct value', function (): void {
|
||||||
// Arrange
|
// Arrange
|
||||||
/** @var ConfigurationService $configService */
|
/** @var ConfigurationService $configService */
|
||||||
|
|
87
tests/Feature/Service/DrinkType/GetStockHistoryTest.php
Normal file
87
tests/Feature/Service/DrinkType/GetStockHistoryTest.php
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use App\Entity\DrinkType;
|
||||||
|
use App\Entity\PropertyChangeLog;
|
||||||
|
use App\Service\DrinkType\GetStockHistory;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
|
test('it returns empty array for unsaved drink type', function (): void {
|
||||||
|
$drinkType = new DrinkType();
|
||||||
|
$drinkType->setName('Test Drink');
|
||||||
|
|
||||||
|
$getStockHistory = $this->getContainer()->get(GetStockHistory::class);
|
||||||
|
$result = $getStockHistory($drinkType);
|
||||||
|
|
||||||
|
expect($result)->toBeArray();
|
||||||
|
expect($result)->toBeEmpty();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it returns stock history for a drink type', function (): void {
|
||||||
|
// Create a drink type
|
||||||
|
$drinkType = new DrinkType();
|
||||||
|
$drinkType->setName('Test Drink');
|
||||||
|
$drinkType->setCurrentStock(10);
|
||||||
|
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Change the current stock to create logs
|
||||||
|
$drinkType->setCurrentStock(15);
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
$drinkType->setCurrentStock(5);
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Get the stock history
|
||||||
|
$getStockHistory = $this->getContainer()->get(GetStockHistory::class);
|
||||||
|
$result = $getStockHistory($drinkType);
|
||||||
|
|
||||||
|
// Verify the result
|
||||||
|
expect($result)->toBeArray();
|
||||||
|
expect($result)->toHaveCount(3);
|
||||||
|
|
||||||
|
// Verify that all logs are for currentStock
|
||||||
|
foreach ($result as $log) {
|
||||||
|
expect($log)->toBeInstanceOf(PropertyChangeLog::class);
|
||||||
|
expect($log->getPropertyName())->toBe('currentStock');
|
||||||
|
expect($log->getEntityClass())->toBe(DrinkType::class);
|
||||||
|
expect($log->getEntityId())->toBe($drinkType->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the values in order (oldest first)
|
||||||
|
expect($result[0]->getNewValue())->toBe('10');
|
||||||
|
expect($result[1]->getNewValue())->toBe('15');
|
||||||
|
expect($result[2]->getNewValue())->toBe('5');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it only returns logs for the specified drink type', function (): void {
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Create first drink type
|
||||||
|
$drinkType1 = new DrinkType();
|
||||||
|
$drinkType1->setName('Drink 1');
|
||||||
|
$drinkType1->setCurrentStock(10);
|
||||||
|
$em->persist($drinkType1);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Create second drink type
|
||||||
|
$drinkType2 = new DrinkType();
|
||||||
|
$drinkType2->setName('Drink 2');
|
||||||
|
$drinkType2->setCurrentStock(20);
|
||||||
|
$em->persist($drinkType2);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Get history for first drink type
|
||||||
|
$getStockHistory = $this->getContainer()->get(GetStockHistory::class);
|
||||||
|
$result = $getStockHistory($drinkType1);
|
||||||
|
|
||||||
|
// Verify we only get logs for the first drink type
|
||||||
|
expect($result)->toHaveCount(1);
|
||||||
|
expect($result[0]->getEntityId())->toBe($drinkType1->getId());
|
||||||
|
expect($result[0]->getNewValue())->toBe('10');
|
||||||
|
});
|
87
tests/Feature/Service/DrinkType/GetWantedHistoryTest.php
Normal file
87
tests/Feature/Service/DrinkType/GetWantedHistoryTest.php
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use App\Entity\DrinkType;
|
||||||
|
use App\Entity\PropertyChangeLog;
|
||||||
|
use App\Service\DrinkType\GetWantedHistory;
|
||||||
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
|
||||||
|
test('it returns empty array for unsaved drink type', function (): void {
|
||||||
|
$drinkType = new DrinkType();
|
||||||
|
$drinkType->setName('Test Drink');
|
||||||
|
|
||||||
|
$getWantedHistory = $this->getContainer()->get(GetWantedHistory::class);
|
||||||
|
$result = $getWantedHistory($drinkType);
|
||||||
|
|
||||||
|
expect($result)->toBeArray();
|
||||||
|
expect($result)->toBeEmpty();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it returns wanted stock history for a drink type', function (): void {
|
||||||
|
// Create a drink type
|
||||||
|
$drinkType = new DrinkType();
|
||||||
|
$drinkType->setName('Test Drink');
|
||||||
|
$drinkType->setWantedStock(10);
|
||||||
|
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Change the wanted stock to create logs
|
||||||
|
$drinkType->setWantedStock(15);
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
$drinkType->setWantedStock(5);
|
||||||
|
$em->persist($drinkType);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Get the wanted stock history
|
||||||
|
$getWantedHistory = $this->getContainer()->get(GetWantedHistory::class);
|
||||||
|
$result = $getWantedHistory($drinkType);
|
||||||
|
|
||||||
|
// Verify the result
|
||||||
|
expect($result)->toBeArray();
|
||||||
|
expect($result)->toHaveCount(3);
|
||||||
|
|
||||||
|
// Verify that all logs are for wantedStock
|
||||||
|
foreach ($result as $log) {
|
||||||
|
expect($log)->toBeInstanceOf(PropertyChangeLog::class);
|
||||||
|
expect($log->getPropertyName())->toBe('wantedStock');
|
||||||
|
expect($log->getEntityClass())->toBe(DrinkType::class);
|
||||||
|
expect($log->getEntityId())->toBe($drinkType->getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the values in order (oldest first)
|
||||||
|
expect($result[0]->getNewValue())->toBe('10');
|
||||||
|
expect($result[1]->getNewValue())->toBe('15');
|
||||||
|
expect($result[2]->getNewValue())->toBe('5');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it only returns logs for the specified drink type', function (): void {
|
||||||
|
$em = $this->getContainer()->get(EntityManagerInterface::class);
|
||||||
|
|
||||||
|
// Create first drink type
|
||||||
|
$drinkType1 = new DrinkType();
|
||||||
|
$drinkType1->setName('Drink 1');
|
||||||
|
$drinkType1->setWantedStock(10);
|
||||||
|
$em->persist($drinkType1);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Create second drink type
|
||||||
|
$drinkType2 = new DrinkType();
|
||||||
|
$drinkType2->setName('Drink 2');
|
||||||
|
$drinkType2->setWantedStock(20);
|
||||||
|
$em->persist($drinkType2);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
// Get history for first drink type
|
||||||
|
$getWantedHistory = $this->getContainer()->get(GetWantedHistory::class);
|
||||||
|
$result = $getWantedHistory($drinkType1);
|
||||||
|
|
||||||
|
// Verify we only get logs for the first drink type
|
||||||
|
expect($result)->toHaveCount(1);
|
||||||
|
expect($result[0]->getEntityId())->toBe($drinkType1->getId());
|
||||||
|
expect($result[0]->getNewValue())->toBe('10');
|
||||||
|
});
|
18
tests/Feature/Web/HelloWorldTest.php
Normal file
18
tests/Feature/Web/HelloWorldTest.php
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
test('Hello World', function (): void {
|
||||||
|
|
||||||
|
// This calls KernelTestCase::bootKernel(), and creates a
|
||||||
|
// "client" that is acting as the browser
|
||||||
|
$this->ensureKernelShutdown();
|
||||||
|
$client = static::createClient();
|
||||||
|
|
||||||
|
// Request a specific page
|
||||||
|
$crawler = $client->request('GET', '/');
|
||||||
|
|
||||||
|
// Validate a successful response and some content
|
||||||
|
$this->assertResponseIsSuccessful();
|
||||||
|
$this->assertSelectorTextContains('h1', 'Hello World');
|
||||||
|
});
|
|
@ -4,6 +4,6 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace Tests;
|
namespace Tests;
|
||||||
|
|
||||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||||
|
|
||||||
abstract class TestCase extends KernelTestCase {}
|
abstract class TestCase extends WebTestCase {}
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
test('example', function (): void {
|
|
||||||
expect(true)->toBeTrue();
|
|
||||||
});
|
|
Loading…
Add table
Add a link
Reference in a new issue