<?php
declare(strict_types=1);
namespace WbfkExtensions\Subscriber;
use JetBrains\PhpStorm\NoReturn;
use Shopware\Core\Content\Product\Aggregate\ProductPrice\ProductPriceEntity;
use Shopware\Core\Content\Product\ProductDefinition;
use Shopware\Core\Content\Product\ProductEntity;
use Shopware\Core\Defaults;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Shopware\Core\Framework\DataAbstractionLayer\Pricing\Price;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\InsertCommand;
use Shopware\Core\Framework\DataAbstractionLayer\Write\Command\UpdateCommand;
use Shopware\Core\Framework\DataAbstractionLayer\Write\Validation\PreWriteValidationEvent;
use Shopware\Core\Framework\Validation\WriteConstraintViolationException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
class WbfkProductValidator implements EventSubscriberInterface
{
/** @var ProductEntity[] */
private array $productEntityCache = [];
public function __construct(
private readonly EntityRepository $productRepository
) {
}
public static function getSubscribedEvents(): array
{
return [
PreWriteValidationEvent::class => 'onPreValidate',
];
}
public function onPreValidate(PreWriteValidationEvent $event): void
{
$violationList = new ConstraintViolationList();
foreach ($event->getCommands() as $command) {
if (!($command instanceof InsertCommand || $command instanceof UpdateCommand)) {
continue;
}
if ($command->getDefinition()->getClass() === ProductDefinition::class) {
$this->validateProductPrice($violationList, $command, $event->getContext());
}
if ($command->getDefinition()->getClass() === ProductPriceEntity::class) {
$this->validatePriceListPrice($violationList, $command, $event->getContext());
}
}
if ($violationList->count() > 0) {
$event->getExceptions()->add(new WriteConstraintViolationException($violationList));
}
}
#[NoReturn]
private function validateProductPrice(ConstraintViolationList &$violations, InsertCommand|UpdateCommand $command, Context $context): void
{
$payload = $command->getPayload();
$pKeys = $command->getPrimaryKey();
if (empty($payload['price']) && empty($payload['purchase_prices'])) {
return;
}
$product = $this->getProduct($context, $pKeys);
/** @var Price $purchasePrice */
$purchasePrice = $product->getPurchasePrices()->first();
$purchaseNet = $purchasePrice->getNet();
$purchaseGross = $purchasePrice->getGross();
if (!empty($payload['purchase_prices'])) {
$newPurchasePrice = (json_decode($payload['purchase_prices'], true))['c'.Defaults::CURRENCY];
$purchaseNet = $newPurchasePrice['net'];
$purchaseGross = $newPurchasePrice['gross'];
}
/** @var Price $price */
$price = $product->getPrice()->get(Defaults::CURRENCY);
$net = $price->getNet();
$gross = $price->getGross();
if (!empty($payload['price'])) {
$newPrice = (json_decode($payload['price'], true))['c'.Defaults::CURRENCY];
$net = $newPrice['net'];
$gross = $newPrice['gross'];
}
// dd([
// 'net' => $net,
// 'gross' => $gross,
// 'purchaseNet' => $purchaseNet,
// 'purchaseGross' => $purchaseGross,
// ]);
if ($net < $purchaseNet) {
$violations->add(
new ConstraintViolation(
"Der Nettopreis ($net) muss über den Netto-Einkaufspreis ($purchaseNet) liegen",
'Misskonfiguration Preis!',
[],
'',
'/write/price',
$net
)
);
}
if ($gross < $purchaseGross) {
$violations->add(
new ConstraintViolation(
"Der Bruttopreis ($gross) muss über den Brutto-Einkaufspreis ($purchaseGross) liegen",
'Misskonfiguration Preis!',
[],
'',
'/write/price',
$net
)
);
}
}
#[NoReturn]
private function validatePriceListPrice(ConstraintViolationList &$violations, InsertCommand|UpdateCommand $command, Context $context): void
{
}
private function getProduct(Context $context, array $pKeys): ?ProductEntity
{
$id = bin2hex($pKeys['id']);
$productRepository = $this->productRepository;
if (!isset($this->productEntityCache[$id])) {
$this->productEntityCache[$id] = $context->enableInheritance(static function (Context $context) use ($id, $productRepository) {
return $productRepository->search(new Criteria([$id]), $context)->first();
});
}
return $this->productEntityCache[$id];
}
}