nochmal nue
This commit is contained in:
parent
2c2e34b71e
commit
6f07d70436
27 changed files with 311 additions and 1988 deletions
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\InventoryRecordRepository;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: InventoryRecordRepository::class)]
|
||||
#[ORM\Table(name: 'inventory_record')]
|
||||
class InventoryRecord
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private null|int $id = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: DrinkType::class, inversedBy: 'inventoryRecords')]
|
||||
#[ORM\JoinColumn(name: 'drink_type_id', referencedColumnName: 'id', nullable: false)]
|
||||
private DrinkType $drinkType;
|
||||
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int $quantity;
|
||||
|
||||
#[ORM\Column(type: 'datetime_immutable')]
|
||||
private DateTimeImmutable $timestamp;
|
||||
|
||||
#[ORM\Column(type: 'datetime_immutable')]
|
||||
private DateTimeImmutable $createdAt;
|
||||
|
||||
#[ORM\Column(type: 'datetime_immutable')]
|
||||
private DateTimeImmutable $updatedAt;
|
||||
|
||||
public function __construct(
|
||||
) {
|
||||
$this->createdAt = new DateTimeImmutable();
|
||||
$this->updatedAt = new DateTimeImmutable();
|
||||
$this->timestamp = new DateTimeImmutable();
|
||||
}
|
||||
|
||||
public function getId(): null|int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getDrinkType(): DrinkType
|
||||
{
|
||||
return $this->drinkType;
|
||||
}
|
||||
|
||||
public function setDrinkType(DrinkType $drinkType): self
|
||||
{
|
||||
$this->drinkType = $drinkType;
|
||||
$this->updateTimestamp();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getQuantity(): int
|
||||
{
|
||||
return $this->quantity;
|
||||
}
|
||||
|
||||
public function setQuantity(int $quantity): self
|
||||
{
|
||||
$this->quantity = $quantity;
|
||||
$this->updateTimestamp();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTimestamp(): DateTimeImmutable
|
||||
{
|
||||
return $this->timestamp;
|
||||
}
|
||||
|
||||
public function setTimestamp(DateTimeImmutable $timestamp): self
|
||||
{
|
||||
$this->timestamp = $timestamp;
|
||||
$this->updateTimestamp();
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): DateTimeImmutable
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
public function getUpdatedAt(): DateTimeImmutable
|
||||
{
|
||||
return $this->updatedAt;
|
||||
}
|
||||
|
||||
private function updateTimestamp(): void
|
||||
{
|
||||
$this->updatedAt = new DateTimeImmutable();
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ use Doctrine\ORM\Mapping as ORM;
|
|||
class SystemConfig
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
36
src/EventListener/PostPersistUpdateListener.php
Normal file
36
src/EventListener/PostPersistUpdateListener.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\EventListener;
|
||||
|
||||
|
||||
use App\Entity\DrinkType;
|
||||
use App\Entity\PropertyChangeLog;
|
||||
use App\Service\DrinkTypeUpdate;
|
||||
use Doctrine\Bundle\DoctrineBundle\Attribute\AsDoctrineListener;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Events;
|
||||
use Doctrine\Persistence\Event\LifecycleEventArgs;
|
||||
|
||||
#[AsDoctrineListener(event: Events::postPersist)]
|
||||
#[AsDoctrineListener(event: Events::postUpdate)]
|
||||
final class PostPersistUpdateListener
|
||||
{
|
||||
public function __construct(private DrinkTypeUpdate $drinkTypeUpdate) {}
|
||||
public function postPersist(LifecycleEventArgs $args): void
|
||||
{
|
||||
$this->log(Events::postPersist, $args);
|
||||
}
|
||||
|
||||
public function postUpdate(LifecycleEventArgs $args): void
|
||||
{
|
||||
$this->log(Events::postUpdate, $args);
|
||||
}
|
||||
private function log(string $action, LifecycleEventArgs $args): void
|
||||
{
|
||||
$entity = $args->getObject();
|
||||
match($entity::class) {
|
||||
DrinkType::class => $this->drinkTypeUpdate->handleLog($action, $entity, $args->getObjectManager()),
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\DrinkType;
|
||||
use App\Entity\InventoryRecord;
|
||||
use DateTimeImmutable;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
/**
|
||||
* @extends AbstractRepository<InventoryRecord>
|
||||
*/
|
||||
class InventoryRecordRepository extends AbstractRepository
|
||||
{
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
parent::__construct($entityManager, $entityManager->getClassMetadata(InventoryRecord::class));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, InventoryRecord>
|
||||
*/
|
||||
public function findByDrinkType(DrinkType $drinkType): array
|
||||
{
|
||||
return $this->findBy(
|
||||
[
|
||||
'drinkType' => $drinkType,
|
||||
],
|
||||
[
|
||||
'timestamp' => 'DESC',
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
public function findLatestByDrinkType(DrinkType $drinkType): null|InventoryRecord
|
||||
{
|
||||
$records = $this->findBy(
|
||||
[
|
||||
'drinkType' => $drinkType,
|
||||
],
|
||||
[
|
||||
'timestamp' => 'DESC',
|
||||
],
|
||||
1,
|
||||
);
|
||||
|
||||
return $records[0] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, InventoryRecord>
|
||||
*/
|
||||
public function findByTimestampRange(DateTimeImmutable $start, DateTimeImmutable $end): array
|
||||
{
|
||||
$qb = $this->getEntityManager()->createQueryBuilder();
|
||||
$qb
|
||||
->select('ir')
|
||||
->from(InventoryRecord::class, 'ir')
|
||||
->where('ir.timestamp >= :start')
|
||||
->andWhere('ir.timestamp <= :end')
|
||||
->setParameter('start', $start)
|
||||
->setParameter('end', $end)
|
||||
->orderBy('ir.timestamp', 'DESC');
|
||||
|
||||
/** @var array<int, InventoryRecord> $result */
|
||||
$result = $qb->getQuery()->getResult();
|
||||
return $result;
|
||||
}
|
||||
}
|
|
@ -26,29 +26,9 @@ class SystemConfigRepository extends AbstractRepository
|
|||
if (!($config instanceof SystemConfig)) {
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,144 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Entity\DrinkType;
|
||||
use App\Enum\SystemSettingKey;
|
||||
use App\Repository\DrinkTypeRepository;
|
||||
use InvalidArgumentException;
|
||||
|
||||
readonly class DrinkTypeService
|
||||
{
|
||||
public function __construct(
|
||||
private DrinkTypeRepository $drinkTypeRepository,
|
||||
private ConfigurationService $configService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get all drink types
|
||||
*
|
||||
* @return DrinkType[]
|
||||
*/
|
||||
public function getAllDrinkTypes(): array
|
||||
{
|
||||
return $this->drinkTypeRepository->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a drink type by ID
|
||||
*
|
||||
* @param int $id
|
||||
* @return DrinkType|null
|
||||
*/
|
||||
public function getDrinkTypeById(int $id): null|DrinkType
|
||||
{
|
||||
return $this->drinkTypeRepository->find($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a drink type by name
|
||||
*
|
||||
* @param string $name
|
||||
* @return DrinkType|null
|
||||
*/
|
||||
public function getDrinkTypeByName(string $name): null|DrinkType
|
||||
{
|
||||
return $this->drinkTypeRepository->findOneBy([
|
||||
'name' => $name,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new drink type
|
||||
*
|
||||
* @param string $name
|
||||
* @param string|null $description
|
||||
* @param int|null $desiredStock
|
||||
* @return DrinkType
|
||||
* @throws InvalidArgumentException If a drink type with the same name already exists
|
||||
*/
|
||||
public function createDrinkType(
|
||||
string $name,
|
||||
null|string $description = null,
|
||||
null|int $desiredStock = null,
|
||||
): DrinkType {
|
||||
// Check if a drink type with the same name already exists
|
||||
if (
|
||||
$this->drinkTypeRepository->findOneBy([
|
||||
'name' => $name,
|
||||
]) !== null
|
||||
) {
|
||||
throw new InvalidArgumentException("A drink type with the name '{$name}' already exists");
|
||||
}
|
||||
|
||||
// If no desired stock is provided, use the default from configuration
|
||||
if ($desiredStock === null) {
|
||||
$desiredStock = (int) $this->configService->getConfigByKey(SystemSettingKey::DEFAULT_DESIRED_STOCK)->getValue();
|
||||
}
|
||||
|
||||
$drinkType = new DrinkType();
|
||||
$drinkType->setName($name);
|
||||
$drinkType->setDescription($description);
|
||||
$drinkType->setDesiredStock($desiredStock);
|
||||
$this->drinkTypeRepository->save($drinkType);
|
||||
|
||||
return $drinkType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing drink type
|
||||
*
|
||||
* @param DrinkType $drinkType
|
||||
* @param string|null $name
|
||||
* @param string|null $description
|
||||
* @param int|null $desiredStock
|
||||
* @return DrinkType
|
||||
* @throws InvalidArgumentException If a drink type with the same name already exists
|
||||
*/
|
||||
public function updateDrinkType(
|
||||
DrinkType $drinkType,
|
||||
null|string $name = null,
|
||||
null|string $description = null,
|
||||
null|int $desiredStock = null,
|
||||
): DrinkType {
|
||||
// Update name if provided
|
||||
if ($name !== null && $name !== $drinkType->getName()) {
|
||||
// Check if a drink type with the same name already exists
|
||||
if (
|
||||
$this->drinkTypeRepository->findOneBy([
|
||||
'name' => $name,
|
||||
]) !== null
|
||||
) {
|
||||
throw new InvalidArgumentException("A drink type with the name '{$name}' already exists");
|
||||
}
|
||||
$drinkType->setName($name);
|
||||
}
|
||||
|
||||
// Update description if provided
|
||||
if ($description !== null) {
|
||||
$drinkType->setDescription($description);
|
||||
}
|
||||
|
||||
// Update desired stock if provided
|
||||
if ($desiredStock !== null) {
|
||||
$drinkType->setDesiredStock($desiredStock);
|
||||
}
|
||||
|
||||
$this->drinkTypeRepository->save($drinkType);
|
||||
|
||||
return $drinkType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a drink type
|
||||
*
|
||||
* @param DrinkType $drinkType
|
||||
* @return void
|
||||
*/
|
||||
public function deleteDrinkType(DrinkType $drinkType): void
|
||||
{
|
||||
$this->drinkTypeRepository->remove($drinkType);
|
||||
}
|
||||
}
|
61
src/Service/DrinkTypeUpdate.php
Normal file
61
src/Service/DrinkTypeUpdate.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Entity\DrinkType;
|
||||
use App\Entity\PropertyChangeLog;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\Events;
|
||||
|
||||
final class DrinkTypeUpdate
|
||||
{
|
||||
public function handleLog(string $action, DrinkType $entity, EntityManagerInterface $em): void
|
||||
{
|
||||
match ($action) {
|
||||
Events::postPersist => $this->handleCreate($entity, $em),
|
||||
Events::postUpdate => $this->handleUpdate($entity, $em),
|
||||
};
|
||||
}
|
||||
|
||||
private function handleCreate(DrinkType $entity, EntityManagerInterface $em): void
|
||||
{
|
||||
$this->logWanted($entity, $em);
|
||||
$this->logCurrent($entity, $em);
|
||||
}
|
||||
|
||||
private function logWanted(DrinkType $entity, EntityManagerInterface $em): void
|
||||
{
|
||||
$logWanted = new PropertyChangeLog();
|
||||
$logWanted->setNewValue((string) $entity->getWantedStock());
|
||||
$logWanted->setEntityClass(DrinkType::class);
|
||||
$logWanted->setEntityId($entity->getId());
|
||||
$logWanted->setPropertyName('wantedStock');
|
||||
|
||||
$em->persist($logWanted);
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
private function logCurrent(DrinkType $entity, EntityManagerInterface $em): void
|
||||
{
|
||||
$logCurrent = new PropertyChangeLog();
|
||||
$logCurrent->setNewValue((string) $entity->getCurrentStock());
|
||||
$logCurrent->setEntityClass(DrinkType::class);
|
||||
$logCurrent->setEntityId($entity->getId());
|
||||
$logCurrent->setPropertyName('currentStock');
|
||||
|
||||
$em->persist($logCurrent);
|
||||
$em->flush();
|
||||
}
|
||||
|
||||
private function handleUpdate(DrinkType $entity, EntityManagerInterface $em): void
|
||||
{
|
||||
$uow = $em->getUnitOfWork();
|
||||
$changeSet = $uow->getEntityChangeSet($entity);
|
||||
if (isset($changeSet['wantedStock'])) {
|
||||
$this->logWanted($entity, $em);;
|
||||
}
|
||||
if (isset($changeSet['currentStock'])) {
|
||||
$this->logCurrent($entity,$em);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Entity\DrinkType;
|
||||
use App\Entity\InventoryRecord;
|
||||
use App\Repository\DrinkTypeRepository;
|
||||
use App\Repository\InventoryRecordRepository;
|
||||
use App\Service\Config\LowStockMultiplier;
|
||||
use App\ValueObject\DrinkStock;
|
||||
use DateTimeImmutable;
|
||||
|
||||
readonly class InventoryService
|
||||
{
|
||||
public function __construct(
|
||||
private InventoryRecordRepository $inventoryRecordRepository,
|
||||
private DrinkTypeRepository $drinkTypeRepository,
|
||||
private LowStockMultiplier $lowStockMultiplier,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get all inventory records
|
||||
*
|
||||
* @return InventoryRecord[]
|
||||
*/
|
||||
public function getAllInventoryRecords(): array
|
||||
{
|
||||
return $this->inventoryRecordRepository->findAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get inventory records for a specific drink type
|
||||
*
|
||||
* @param DrinkType $drinkType
|
||||
* @return InventoryRecord[]
|
||||
*/
|
||||
public function getInventoryRecordsByDrinkType(DrinkType $drinkType): array
|
||||
{
|
||||
return $this->inventoryRecordRepository->findByDrinkType($drinkType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest inventory record for a specific drink type
|
||||
*
|
||||
* @param DrinkType $drinkType
|
||||
* @return InventoryRecord
|
||||
*/
|
||||
public function getLatestInventoryRecord(DrinkType $drinkType): InventoryRecord
|
||||
{
|
||||
$record = $this->inventoryRecordRepository->findLatestByDrinkType($drinkType);
|
||||
if (!($record instanceof InventoryRecord)) {
|
||||
$record = new InventoryRecord();
|
||||
$record->setDrinkType($drinkType);
|
||||
$record->setQuantity(0);
|
||||
$this->inventoryRecordRepository->save($record);
|
||||
}
|
||||
return $record;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the current stock level for a specific drink type
|
||||
*
|
||||
* @param DrinkType $drinkType
|
||||
* @return int
|
||||
*/
|
||||
public function getCurrentStockLevel(DrinkType $drinkType): int
|
||||
{
|
||||
$latestRecord = $this->getLatestInventoryRecord($drinkType);
|
||||
return ($latestRecord instanceof InventoryRecord) ? $latestRecord->getQuantity() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the stock level for a specific drink type
|
||||
*
|
||||
* @param DrinkType $drinkType
|
||||
* @param int $quantity
|
||||
* @param DateTimeImmutable|null $timestamp
|
||||
* @return InventoryRecord
|
||||
*/
|
||||
public function updateStockLevel(
|
||||
DrinkType $drinkType,
|
||||
int $quantity,
|
||||
DateTimeImmutable $timestamp = new DateTimeImmutable(),
|
||||
): InventoryRecord {
|
||||
$inventoryRecord = new InventoryRecord();
|
||||
$inventoryRecord->setDrinkType($drinkType);
|
||||
$inventoryRecord->setQuantity($quantity);
|
||||
$inventoryRecord->setTimestamp($timestamp);
|
||||
|
||||
$this->inventoryRecordRepository->save($inventoryRecord);
|
||||
|
||||
return $inventoryRecord;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DrinkStock[]
|
||||
*/
|
||||
public function getAllDrinkTypesWithStockLevels(bool $includeZeroDesiredStock = false): array
|
||||
{
|
||||
if ($includeZeroDesiredStock) {
|
||||
$drinkTypes = $this->drinkTypeRepository->findAll();
|
||||
} else {
|
||||
$drinkTypes = $this->drinkTypeRepository->findDesired();
|
||||
}
|
||||
$result = [];
|
||||
|
||||
foreach ($drinkTypes as $drinkType) {
|
||||
$result[] = $this->getDrinkStock($drinkType);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getDrinkStock(DrinkType $drinkType): DrinkStock
|
||||
{
|
||||
return DrinkStock::fromInventoryRecord(
|
||||
$this->getLatestInventoryRecord($drinkType),
|
||||
$this->lowStockMultiplier->getValue(),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -20,7 +20,6 @@ readonly class OrderService
|
|||
private OrderRepository $orderRepository,
|
||||
private OrderItemRepository $orderItemRepository,
|
||||
private DrinkTypeRepository $drinkTypeRepository,
|
||||
private InventoryService $inventoryService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,124 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Entity\DrinkType;
|
||||
use App\Enum\SystemSettingKey;
|
||||
use App\Repository\DrinkTypeRepository;
|
||||
use App\Repository\InventoryRecordRepository;
|
||||
use App\Repository\OrderRepository;
|
||||
use App\Repository\SystemConfigRepository;
|
||||
use App\ValueObject\StockAdjustmentProposal;
|
||||
|
||||
readonly class StockAdjustmentService
|
||||
{
|
||||
public function __construct(
|
||||
private DrinkTypeRepository $drinkTypeRepository,
|
||||
private InventoryRecordRepository $inventoryRecordRepository,
|
||||
private OrderRepository $orderRepository,
|
||||
private SystemConfigRepository $systemConfigRepository,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Proposes adjusted stock levels for all drink types
|
||||
*
|
||||
* @return array<int, StockAdjustmentProposal> Array of stock adjustment proposals
|
||||
*/
|
||||
public function proposeStockAdjustments(): array
|
||||
{
|
||||
$drinkTypes = $this->drinkTypeRepository->findAll();
|
||||
$proposals = [];
|
||||
|
||||
foreach ($drinkTypes as $drinkType) {
|
||||
$proposals[] = $this->proposeStockAdjustmentForDrinkType($drinkType);
|
||||
}
|
||||
|
||||
return $proposals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Proposes an adjusted stock level for a specific drink type
|
||||
*/
|
||||
public function proposeStockAdjustmentForDrinkType(DrinkType $drinkType): StockAdjustmentProposal
|
||||
{
|
||||
$currentDesiredStock = $drinkType->getDesiredStock();
|
||||
$lookbackOrders =
|
||||
(int) $this->systemConfigRepository->getValue(SystemSettingKey::STOCK_ADJUSTMENT_LOOKBACK_ORDERS);
|
||||
$increaseAmount = (int) $this->systemConfigRepository->getValue(SystemSettingKey::STOCK_INCREASE_AMOUNT);
|
||||
$decreaseAmount = (int) $this->systemConfigRepository->getValue(SystemSettingKey::STOCK_DECREASE_AMOUNT);
|
||||
|
||||
// Get the last N orders for this drink type
|
||||
$lastOrders = $this->orderRepository->findLastOrdersForDrinkType($drinkType, $lookbackOrders);
|
||||
|
||||
// If there are no orders, return the current desired stock
|
||||
if ($lastOrders === []) {
|
||||
return new StockAdjustmentProposal($drinkType, $currentDesiredStock);
|
||||
}
|
||||
|
||||
// Check if stock was 0 in the last order
|
||||
$lastOrder = $lastOrders[0];
|
||||
$lastOrderItems = $lastOrder->getOrderItems();
|
||||
$stockWasZeroInLastOrder = false;
|
||||
|
||||
foreach ($lastOrderItems as $orderItem) {
|
||||
if ($orderItem->getDrinkType()->getId() === $drinkType->getId()) {
|
||||
// Find the inventory record closest to the order creation date
|
||||
$inventoryRecords = $this->inventoryRecordRepository->findByDrinkType($drinkType);
|
||||
foreach ($inventoryRecords as $record) {
|
||||
if ($record->getTimestamp() <= $lastOrder->getCreatedAt()) {
|
||||
if ($record->getQuantity() === 0) {
|
||||
$stockWasZeroInLastOrder = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If stock was 0 in the last order, increase desired stock
|
||||
if ($stockWasZeroInLastOrder) {
|
||||
return new StockAdjustmentProposal($drinkType, $currentDesiredStock + $increaseAmount);
|
||||
}
|
||||
|
||||
// Check if stock was above zero in all lookback orders
|
||||
$stockWasAboveZeroInAllOrders = true;
|
||||
$ordersToCheck = min(count($lastOrders), $lookbackOrders);
|
||||
|
||||
for ($i = 0; $i < $ordersToCheck; $i++) {
|
||||
$order = $lastOrders[$i];
|
||||
$orderItems = $order->getOrderItems();
|
||||
|
||||
foreach ($orderItems as $orderItem) {
|
||||
if ($orderItem->getDrinkType()->getId() === $drinkType->getId()) {
|
||||
// Find the inventory record closest to the order creation date
|
||||
$inventoryRecords = $this->inventoryRecordRepository->findByDrinkType($drinkType);
|
||||
foreach ($inventoryRecords as $record) {
|
||||
if ($record->getTimestamp() <= $order->getCreatedAt()) {
|
||||
if ($record->getQuantity() === 0) {
|
||||
$stockWasAboveZeroInAllOrders = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$stockWasAboveZeroInAllOrders) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If stock was above zero in all lookback orders, decrease desired stock
|
||||
if ($stockWasAboveZeroInAllOrders && $ordersToCheck === $lookbackOrders) {
|
||||
$proposedStock = max(0, $currentDesiredStock - $decreaseAmount);
|
||||
return new StockAdjustmentProposal($drinkType, $proposedStock);
|
||||
}
|
||||
|
||||
// Otherwise, keep the current desired stock
|
||||
return new StockAdjustmentProposal($drinkType, $currentDesiredStock);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue