tetssssss
This commit is contained in:
parent
6f07d70436
commit
8063e7bec9
28 changed files with 771 additions and 273 deletions
|
@ -58,8 +58,10 @@ final class DrinkTypeController extends AbstractController
|
|||
$desiredStockHistory = $propertyChangeLogRepository->findBy([
|
||||
'entityClass' => DrinkType::class,
|
||||
'propertyName' => 'desiredStock',
|
||||
'entityId' => $drinkType->getId()
|
||||
], ['changeDate' => 'DESC']);
|
||||
'entityId' => $drinkType->getId(),
|
||||
], [
|
||||
'changeDate' => 'DESC',
|
||||
]);
|
||||
|
||||
return $this->render('drink_type/show.html.twig', [
|
||||
'drink_type' => $drinkType,
|
||||
|
|
|
@ -4,9 +4,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Enum\StockState;
|
||||
use App\Service\InventoryService;
|
||||
use App\ValueObject\DrinkStock;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
@ -14,19 +11,8 @@ use Symfony\Component\Routing\Attribute\Route;
|
|||
#[Route(path: '/', name: 'app_index')]
|
||||
final class Index extends AbstractController
|
||||
{
|
||||
|
||||
public function __invoke(): Response
|
||||
{
|
||||
$drinkStocks = $this->inventoryService->getAllDrinkTypesWithStockLevels();
|
||||
|
||||
$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,
|
||||
]);
|
||||
return new Response('<h1>Hello World!</h1>');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,13 +6,14 @@ namespace App\Entity;
|
|||
|
||||
use App\Repository\PropertyChangeLogRepository;
|
||||
use Doctrine\ORM\Mapping\Column;
|
||||
use Doctrine\ORM\Mapping\Entity;
|
||||
use Doctrine\ORM\Mapping\GeneratedValue;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\Mapping\Id;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Doctrine\ORM\Mapping\Table;
|
||||
|
||||
#[ORM\Entity(repositoryClass: PropertyChangeLogRepository::class)]
|
||||
#[ORM\Table(name: 'property_change_log')]
|
||||
#[Entity(repositoryClass: PropertyChangeLogRepository::class)]
|
||||
#[Table(name: 'property_change_log')]
|
||||
final class PropertyChangeLog
|
||||
{
|
||||
#[Id]
|
||||
|
|
|
@ -1,21 +1,20 @@
|
|||
<?php declare(strict_types=1);
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\EventListener;
|
||||
|
||||
|
||||
use App\Entity\DrinkType;
|
||||
use App\Entity\PropertyChangeLog;
|
||||
use App\Service\DrinkTypeUpdate;
|
||||
use App\Service\DrinkType\DrinkTypeUpdateLog;
|
||||
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Events;
|
||||
use Doctrine\Persistence\Event\LifecycleEventArgs;
|
||||
|
||||
#[AsDoctrineListener(event: Events::postPersist)]
|
||||
#[AsDoctrineListener(event: Events::postUpdate)]
|
||||
final class PostPersistUpdateListener
|
||||
final readonly class PostPersistUpdateListener
|
||||
{
|
||||
public function __construct(private DrinkTypeUpdate $drinkTypeUpdate) {}
|
||||
public function __construct(private DrinkTypeUpdateLog $drinkTypeUpdate) {}
|
||||
public function postPersist(LifecycleEventArgs $args): void
|
||||
{
|
||||
$this->log(Events::postPersist, $args);
|
||||
|
@ -25,12 +24,12 @@ final class PostPersistUpdateListener
|
|||
{
|
||||
$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,
|
||||
};
|
||||
}
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,20 +25,20 @@ class DrinkTypeRepository extends AbstractRepository
|
|||
return parent::findBy(
|
||||
criteria: [],
|
||||
orderBy: [
|
||||
'desiredStock' => 'DESC',
|
||||
'wantedStock' => 'DESC',
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/** @return DrinkType[] */
|
||||
public function findDesired(): array
|
||||
public function findWanted(): array
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
$qb
|
||||
->select('d')
|
||||
->from(DrinkType::class, 'd')
|
||||
->where('d.desiredStock > 0')
|
||||
->orderBy('d.desiredStock', 'DESC')
|
||||
->where('d.wantedStock > 0')
|
||||
->orderBy('d.wantedStock', 'DESC')
|
||||
->addOrderBy('d.name', 'ASC');
|
||||
|
||||
/** @var array<int, DrinkType> $result */
|
||||
|
|
|
@ -26,7 +26,7 @@ class SystemConfigRepository extends AbstractRepository
|
|||
if (!($config instanceof SystemConfig)) {
|
||||
$config = new SystemConfig();
|
||||
$config->setKey($key);
|
||||
$config->setValue($key->defaultValue($key));
|
||||
$config->setValue($key->defaultValue());
|
||||
$this->save($config);
|
||||
}
|
||||
return $config;
|
||||
|
|
|
@ -4,7 +4,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Service\Config;
|
||||
|
||||
use App\Entity\SystemConfig;
|
||||
use App\Enum\SystemSettingKey;
|
||||
use App\Service\ConfigurationService;
|
||||
use Stringable;
|
||||
|
|
|
@ -4,7 +4,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Service\Config;
|
||||
|
||||
use App\Entity\SystemConfig;
|
||||
use App\Enum\SystemSettingKey;
|
||||
use App\Service\ConfigurationService;
|
||||
|
||||
|
@ -16,11 +15,6 @@ final readonly class LowStockMultiplier
|
|||
|
||||
public function getValue(): float
|
||||
{
|
||||
$value = $this->configService->getConfigValue(
|
||||
SystemSettingKey::STOCK_LOW_MULTIPLIER,
|
||||
SystemConfig::DEFAULT_STOCK_LOW_MULTIPLIER,
|
||||
);
|
||||
|
||||
return (float) $value;
|
||||
return (float) $this->configService->get(SystemSettingKey::STOCK_LOW_MULTIPLIER);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Entity\SystemConfig;
|
||||
use App\Enum\SystemSettingKey;
|
||||
use App\Repository\SystemConfigRepository;
|
||||
use InvalidArgumentException;
|
||||
|
||||
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;
|
||||
|
||||
use App\Entity\DrinkType;
|
||||
use App\Entity\Order;
|
||||
use App\Entity\OrderItem;
|
||||
use App\Enum\OrderStatus;
|
||||
|
@ -132,111 +131,4 @@ readonly class OrderService
|
|||
|
||||
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);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue