<?php declare(strict_types=1);
namespace Acris\RuleSurchargeDiscount\Core\Checkout\SurchargeDiscount\Cart;
use Acris\RuleSurchargeDiscount\Storefront\Subscriber\SurchargeDiscountTagSubscriber;
use Acris\RuleSurchargeDiscount\Components\Service\SurchargeDiscountRecalculationService;
use Acris\RuleSurchargeDiscount\Custom\SurchargeDiscountCollection;
use Acris\RuleSurchargeDiscount\Custom\SurchargeDiscountEntity;
use Shopware\Core\Checkout\Cart\Cart;
use Shopware\Core\Checkout\Cart\CartBehavior;
use Shopware\Core\Checkout\Cart\CartDataCollectorInterface;
use Shopware\Core\Checkout\Cart\LineItem\CartDataCollection;
use Shopware\Core\Checkout\Cart\LineItem\LineItem;
use Shopware\Core\Checkout\Cart\LineItem\LineItemCollection;
use Shopware\Core\Checkout\Cart\Price\Struct\AbsolutePriceDefinition;
use Shopware\Core\Checkout\Customer\Aggregate\CustomerGroup\CustomerGroupDefinition;
use Shopware\Core\Checkout\Promotion\Cart\PromotionCartInformationTrait;
use Shopware\Core\Checkout\Promotion\Cart\PromotionProcessor;
use Shopware\Core\Checkout\Promotion\Gateway\PromotionGatewayInterface;
use Shopware\Core\Framework\Adapter\Cache\CacheValueCompressor;
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting;
use Shopware\Core\System\Currency\CurrencyDefinition;
use Shopware\Core\System\Language\LanguageDefinition;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
use Symfony\Contracts\Cache\ItemInterface;
class SurchargeDiscountCollector implements CartDataCollectorInterface
{
use PromotionCartInformationTrait;
public const BUILD_NAME = 'acris-rule-surcharge-discount';
public const SKIP_SURCHARGE_DISCOUNT_ON_CART_RECALCULATION = 'recalculation';
const ACRIS_SURCHAGE_DISCOUNT_RESULT = 'acrisSurchageDiscountResult';
/**
* @var PromotionGatewayInterface
*/
private PromotionGatewayInterface $gateway;
/**
* @var SurchargeDiscountItemBuilder
*/
private SurchargeDiscountItemBuilder $itemBuilder;
/**
* @var EntityRepositoryInterface
*/
private EntityRepositoryInterface $surchargeDiscountRepository;
/**
* @var TagAwareAdapterInterface
*/
private TagAwareAdapterInterface $cache;
public function __construct(
PromotionGatewayInterface $gateway,
SurchargeDiscountItemBuilder $itemBuilder,
EntityRepositoryInterface $surchargeDiscountRepository,
TagAwareAdapterInterface $cache
)
{
$this->gateway = $gateway;
$this->itemBuilder = $itemBuilder;
$this->surchargeDiscountRepository = $surchargeDiscountRepository;
$this->cache = $cache;
}
/**
* @param CartDataCollection $data
* @param Cart $cart
* @param SalesChannelContext $context
* @param CartBehavior $behavior
*/
public function collect(CartDataCollection $data, Cart $cart, SalesChannelContext $context, CartBehavior $behavior): void
{
$surchargeDiscountResult = $this->getCachedValue($context);
// if we are in recalculation,
// we must not re-add any surcharges/discounts. just leave it as it is.
if ($behavior->hasPermission(AbstractSurchargeDiscountProcessor::SKIP_AUTOMATIC_SURCHARGE_DISCOUNT)) {
return;
}
$toggle = false;
if ($context->hasExtension(SurchargeDiscountRecalculationService::TOGGLE_AUTOMATIC_SURCHARGE_DISCOUNT)) {
$toggle = true;
$context->removeExtension(SurchargeDiscountRecalculationService::TOGGLE_AUTOMATIC_SURCHARGE_DISCOUNT);
}
$discountLineItems = [];
$lineItems = $this->buildDiscountLineItems($surchargeDiscountResult->getEntities(), $cart, $toggle, $context);
/** @var LineItem $nested */
foreach ($lineItems as $nested) {
$discountLineItems[] = $nested;
}
if (count($discountLineItems) > 0) {
$data->set(AbstractSurchargeDiscountProcessor::DATA_KEY, new LineItemCollection($discountLineItems));
} else {
$data->remove(AbstractSurchargeDiscountProcessor::DATA_KEY);
}
}
/**
* @param SurchargeDiscountEntity $promotion
* @param Cart $cart
* @param SalesChannelContext $context
* @return array
*/
private function buildDiscountLineItems(SurchargeDiscountCollection $collection, Cart $cart, bool $toggle, SalesChannelContext $context): array
{
$lineItems = [];
$itemIds = $this->getAllLineItemIds($cart);
$salesChannelId = $context->getSalesChannel()->getId();
$rules = !empty($cart->getRuleIds()) ? $cart->getRuleIds() : $context->getRuleIds();
foreach ($collection->getElements() as $surchargeDiscount) {
$surchargeDiscountSalesChannels = $surchargeDiscount->getSalesChannels();
$surchargeDiscountRules = $surchargeDiscount->getRules();
$surchargeDiscountSalesChannelExists = false;
foreach ($surchargeDiscountSalesChannels as $key => $surchargeDiscountSalesChannel) {
if ($salesChannelId === $key) {
$surchargeDiscountSalesChannelExists = true;
}
}
if (!$surchargeDiscountSalesChannelExists) continue;
$surchargeDiscountRulesExists = false;
foreach ($surchargeDiscountRules as $key => $surchargeDiscountRule) {
if (!empty($rules) && is_array($rules) && in_array($key, $rules)) {
$surchargeDiscountRulesExists = true;
}
}
if ($surchargeDiscountRules->count() === 0) $surchargeDiscountRulesExists = true;
if (!$surchargeDiscountRulesExists) {
$originalLineItems = clone $cart->getLineItems();
foreach ($cart->getLineItems() as $lineItem) {
if ($lineItem->getType() !== LineItem::PRODUCT_LINE_ITEM_TYPE) {
$cart->getLineItems()->remove($lineItem->getId());
}
}
$match = $surchargeDiscountRules->filterMatchingRules($cart, $context)->count();
$cart->setLineItems($originalLineItems);
if (!$match > 0) continue;
}
if (count($itemIds) <= 0) {
continue;
}
$quantity = 1;
$price = null;
$changedPercentageToAbsolute = null;
if ($cart->getName() === AbstractSurchargeDiscountProcessor::SKIP_SURCHARGE_DISCOUNT_ON_CART_RECALCULATION) {
/** @var LineItem $surchargeDiscountLineItem */
$surchargeDiscountLineItem = $cart->getLineItems()->filter(function (LineItem $lineItem) use ($surchargeDiscount) {
return $lineItem->hasPayloadValue('surchargeDiscountId') && $surchargeDiscount->getId() === $lineItem->getPayloadValue('surchargeDiscountId');
})->first();
if (!empty($surchargeDiscountLineItem)) {
$quantity = $surchargeDiscountLineItem->getQuantity();
if (!empty($surchargeDiscountLineItem->getPriceDefinition()) && $surchargeDiscountLineItem->getPriceDefinition() instanceof AbsolutePriceDefinition) {
$price = $surchargeDiscountLineItem->getPriceDefinition()->getPrice();
}
if ($surchargeDiscountLineItem->getPayloadValue('discountType') === 'percentage' && !empty($surchargeDiscountLineItem->getPriceDefinition()) && $surchargeDiscountLineItem->getPriceDefinition() instanceof AbsolutePriceDefinition) {
$price = $surchargeDiscountLineItem->getPriceDefinition()->getPrice();
$changedPercentageToAbsolute = $price;
$surchargeDiscount->setType('absolute');
} elseif ($surchargeDiscountLineItem->getPayloadValue('discountType') === 'absolute' && $surchargeDiscountLineItem->hasPayloadValue('value')) {
$price = $surchargeDiscountLineItem->getPriceDefinition()->getPrice();
$surchargeDiscount->setType('absolute');
}
}
if (empty($surchargeDiscountLineItem) && $surchargeDiscount->getAssignMethod() === AbstractSurchargeDiscountProcessor::ASSIGN_METHOD_LINE_ITEM && !$toggle) {
continue;
}
}
/* @var LineItem $discountItem */
$discountItem = $this->itemBuilder->buildLineItem(
$surchargeDiscount,
$context,
$cart->getName() === AbstractSurchargeDiscountProcessor::SKIP_SURCHARGE_DISCOUNT_ON_CART_RECALCULATION,
$quantity,
$price
);
if (!empty($changedPercentageToAbsolute)) {
$discountItem->setPayloadValue('changedValue', $changedPercentageToAbsolute);
}
$lineItems[] = $discountItem;
}
return $lineItems;
}
private function getAllLineItemIds(Cart $cart): array
{
return $cart->getLineItems()->fmap(
static function (LineItem $lineItem) {
if ($lineItem->getType() === PromotionProcessor::LINE_ITEM_TYPE || $lineItem->getType() === AbstractSurchargeDiscountProcessor::LINE_ITEM_TYPE) {
return null;
}
return $lineItem->getId();
}
);
}
private function getCachedValue( SalesChannelContext $salesChannelContext){
$key = $this->getCacheKey($salesChannelContext);
$value = $this->cache->get($key, function (ItemInterface $item) use ($salesChannelContext) {
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('active', true));
$criteria->addSorting(new FieldSorting('priority', FieldSorting::DESCENDING));
$criteria->addSorting(new FieldSorting('value', FieldSorting::ASCENDING, true));
$criteria->addAssociation('salesChannels');
$criteria->addAssociation('rules');
$criteria->addAssociation('discountRules');
$criteria->addAssociation('deliveryRules');
$criteria->addAssociation('tax');
$criteria->addAssociation('media');
$surchargeDiscountResult = $this->surchargeDiscountRepository->search($criteria, $salesChannelContext->getContext());
$item->tag([SurchargeDiscountTagSubscriber::ACRIS_SURCHARGE_DISCOUNT_ENTITY]);
$this->addCacheTagsForSalesChannelContext($item, $salesChannelContext);
return CacheValueCompressor::compress($surchargeDiscountResult);
});
return CacheValueCompressor::uncompress($value);
}
private function getCacheKey(SalesChannelContext $salesChannelContext): string
{
return self::BUILD_NAME . '-' . md5(\json_encode([
$salesChannelContext->getSalesChannel()->getId(),
$salesChannelContext->getCurrentCustomerGroup()->getId(),
$salesChannelContext->getCurrency()->getId(),
$salesChannelContext->getContext()->getLanguageId(),
$salesChannelContext->getRuleIds()
]));
}
private function addCacheTagsForSalesChannelContext(ItemInterface $item, SalesChannelContext $salesChannelContext)
{
$item->tag(CustomerGroupDefinition::ENTITY_NAME . '-' . $salesChannelContext->getCurrentCustomerGroup()->getId());
$item->tag(CurrencyDefinition::ENTITY_NAME . '-' . $salesChannelContext->getCurrency()->getId());
$item->tag(LanguageDefinition::ENTITY_NAME . '-' . $salesChannelContext->getContext()->getLanguageId());
}
}