nochmal nue

This commit is contained in:
lubiana 2025-06-09 22:05:01 +02:00
parent 2c2e34b71e
commit 6f07d70436
Signed by: lubiana
SSH key fingerprint: SHA256:vW1EA0fRR3Fw+dD/sM0K+x3Il2gSry6YRYHqOeQwrfk
27 changed files with 311 additions and 1988 deletions

View file

@ -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
{

View file

@ -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();

View file

@ -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();
}
}

View file

@ -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();
}

View file

@ -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,

View 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,
};
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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));
}
}

View file

@ -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);
}
}

View 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);
}
}
}

View file

@ -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(),
);
}
}

View file

@ -20,7 +20,6 @@ readonly class OrderService
private OrderRepository $orderRepository,
private OrderItemRepository $orderItemRepository,
private DrinkTypeRepository $drinkTypeRepository,
private InventoryService $inventoryService,
) {}
/**

View file

@ -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);
}
}