<?php declare(strict_types=1);
namespace Wbfk\Bundles\Subscriber;
use ApplifactionPriceMachine\Event\BasePriceCalculatedEvent;
use ApplifactionPriceMachine\Event\BeforeProductPricesCalculateEvent;
use ApplifactionPriceMachine\Event\ProductPricesCalculatedEvent;
use ApplifactionPriceMachine\Service\ProductPriceCalculationService;
use Doctrine\DBAL\Connection;
use Psr\Log\LoggerInterface;
use Shopware\Core\Defaults;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class PriceMachineSubscriber implements EventSubscriberInterface
{
public function __construct(
private readonly Connection $connection,
private readonly ?ProductPriceCalculationService $productPriceCalculationService,
private readonly EntityRepository $productRepository,
private readonly ?LoggerInterface $logger
)
{
}
public static function getSubscribedEvents(): array
{
return [
BasePriceCalculatedEvent::class => 'calculateBundleChildrenSummaryPrice',
ProductPricesCalculatedEvent::class => 'runBundleParentPriceCalculations',
BeforeProductPricesCalculateEvent::class => 'runBundleParentBasePriceCalculation'
];
}
public function calculateBundleChildrenSummaryPrice(BasePriceCalculatedEvent $event): void
{
if ($event->hasTemporaryPrice() && !$event->isListPrice()) return;
$bundleChildrenPriceSql = 'SELECT
LOWER(HEX(cp.id)) AS child_product_id,
cpp.quantity_start AS product_price_quantity_start,
bp.quantity AS quantity,
LOWER(HEX(cpp.rule_id)) AS rule_id,
IFNULL(cpp.price, IFNULL(cp.price, cpa.price)) AS price
FROM wbfk_bundle_product bp
INNER JOIN product cp ON bp.child_product_id = cp.id AND bp.child_product_version_id = cp.version_id
LEFT JOIN product_price cpp ON cpp.product_id = cp.id
AND cpp.product_version_id = cp.version_id
AND :quantity >= cpp.quantity_start
AND (:quantity <= cpp.quantity_end || cpp.quantity_end IS NULL)
AND LOWER(HEX(cpp.rule_id)) = :ruleId
LEFT JOIN product cpa ON cp.parent_id = cpa.id AND cp.version_id = cpa.version_id
WHERE LOWER(HEX(bp.product_id)) = :productId
AND LOWER(HEX(bp.product_version_id)) = :versionId
ORDER BY cp.id ASC, cpp.quantity_start ASC';
$bundleChildrenPriceResults = $this->connection->fetchAllAssociative($bundleChildrenPriceSql, [
'quantity' => $event->getQuantityStart(),
'ruleId' => $event->getRuleId(),
'productId' => $event->getProductId(),
'versionId' => Defaults::LIVE_VERSION
]);
$totalNet = 0;
$totalGross = 0;
foreach ($bundleChildrenPriceResults as $bundleChildrenPriceResult) {
$childProductId = $bundleChildrenPriceResult['child_product_id'];
$quantity = $bundleChildrenPriceResult['quantity'];
$priceRaw = $bundleChildrenPriceResult['price'];
$priceArray = json_decode($bundleChildrenPriceResult['price'], true);
if (isset($priceArray[$event->getCurrencyId()])) {
$totalGross += $priceArray[$event->getCurrencyId()]['gross'] * $quantity;
$totalNet += $priceArray[$event->getCurrencyId()]['net'] * $quantity;
}
}
// Modify event price
if ($totalNet === 0 || $totalGross === 0) {
$event->setPrice(null);
} else {
$eventPrice = $event->getPrice();
$eventPrice['gross'] = $totalGross;
$eventPrice['net'] = $totalNet;
$event->setPrice($eventPrice);
}
}
public function runBundleParentPriceCalculations(ProductPricesCalculatedEvent $event)
{
if (!$this->productPriceCalculationService) return;
$selectBundleParentsSql = <<<SQL
SELECT LOWER(HEX(product_id)) AS product_id
FROM wbfk_bundle_product bp
WHERE
LOWER(HEX(bp.product_version_id)) = :versionId AND
LOWER(HEX(bp.child_product_id)) = :childProductId AND
LOWER(HEX(bp.child_product_version_id)) = :versionId
GROUP BY bp.product_id
SQL;
$bundleChildrenPriceResults = $this->connection->fetchAllAssociative($selectBundleParentsSql, [
'childProductId' => $event->getProductId(),
'versionId' => Defaults::LIVE_VERSION
]);
foreach ($bundleChildrenPriceResults as $bundleChildrenPriceResult) {
$this->productPriceCalculationService->runProductPriceCalculation($bundleChildrenPriceResult['product_id'], $event->isDryRun());
}
}
public function runBundleParentBasePriceCalculation(BeforeProductPricesCalculateEvent $event)
{
if (!$this->productPriceCalculationService) return;
$productId = $event->getProductId();
// Check if product is a bundle parent with no active temporary prices
$bundleParentCheckSql = <<<SQL
SELECT p.price AS price
FROM product p
INNER JOIN wbfk_bundle_product bp ON p.id = bp.product_id AND p.version_id = bp.product_version_id
LEFT JOIN apm_temporary_price atp ON atp.product_id = p.id AND
atp.product_version_id = p.version_id AND
:currentDate >= atp.valid_from AND
:currentDate <= atp.valid_to
WHERE
LOWER(HEX(bp.product_version_id)) = :versionId AND
LOWER(HEX(bp.product_id)) = :productId AND
atp.id IS NULL
LIMIT 1
SQL;
$bundleParentCheckResults = $this->connection->fetchAllAssociative($bundleParentCheckSql, [
'productId' => $event->getProductId(),
'versionId' => Defaults::LIVE_VERSION,
'currentDate' => gmdate("Y-m-d H:i:s")
]);
if (sizeof($bundleParentCheckResults) === 0 || !isset($bundleParentCheckResults[0]['price'])) return;
$standardCustomerPriceRuleId = '257e09ae47eb4823bdc0f59ed7ce242a';
$currencyId = 'cb7d2554b0ce847cd82f3ac9bd1c0dfca';
$parentProductPrices = json_decode($bundleParentCheckResults[0]['price'], true);
$parentProductPrice = isset($parentProductPrices[$currencyId]) ? $parentProductPrices[$currencyId] : null;
if (!$parentProductPrice) return;
$basePriceCalculatedEvent = new BasePriceCalculatedEvent($parentProductPrice, $productId, $standardCustomerPriceRuleId, 1, $currencyId, false);
$this->calculateBundleChildrenSummaryPrice($basePriceCalculatedEvent);
$childrenSummaryPrice = $basePriceCalculatedEvent->getPrice();
if ($parentProductPrice['gross'] !== $childrenSummaryPrice['gross'] || $parentProductPrice['net'] !== $childrenSummaryPrice['net']) {
$differenceNet = $childrenSummaryPrice['net'] - $parentProductPrice['net'];
$parentProductPrice['gross'] = $childrenSummaryPrice['gross'];
$parentProductPrice['net'] = $childrenSummaryPrice['net'];
$parentProductPrices[$currencyId] = $parentProductPrice;
$this->productRepository->update([[
'id' => $productId,
'price' => $parentProductPrices
]], $event->getContext());
if (!!$this->logger) {
$this->logger->info('[BUNDLE PRICE] Bundle Parent Price was updated. Difference (net): ' . $differenceNet . ' P-ID: ' . $productId . ' New Price: ' . json_encode($parentProductPrice));
}
}
}
}