Files
esun3d/app/code/Esun/FreeGift/Model/GiftSynchronizer.php
T
2026-09-15 10:35:56 +08:00

166 lines
5.1 KiB
PHP

<?php
declare(strict_types=1);
namespace Esun\FreeGift\Model;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Model\Product\Type;
use Magento\Framework\DataObjectFactory;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Serialize\Serializer\Json;
use Magento\Quote\Model\Quote;
use Magento\Quote\Model\Quote\Item;
use Psr\Log\LoggerInterface;
class GiftSynchronizer
{
public function __construct(
private readonly Config $config,
private readonly ProductRepositoryInterface $productRepository,
private readonly DataObjectFactory $dataObjectFactory,
private readonly Json $json,
private readonly InternalAddGuard $internalAddGuard,
private readonly LoggerInterface $logger
) {
}
public function synchronize(Quote $quote, float $eligibleBaseSubtotal): bool
{
$storeId = (int)$quote->getStoreId();
if ($storeId <= 0) {
return false;
}
$targetQuantity = $this->config->getTargetQuantity($eligibleBaseSubtotal, $storeId);
$giftItems = $this->getGiftItems($quote);
if ($targetQuantity <= 0) {
return $this->removeGiftItems($quote, $giftItems);
}
$changed = false;
$configuredGiftItems = [];
foreach ($giftItems as $giftItem) {
if ($this->config->isGiftSku((string)$giftItem->getSku(), $storeId)) {
$configuredGiftItems[] = $giftItem;
continue;
}
$quote->deleteItem($giftItem);
$changed = true;
}
if (!$configuredGiftItems) {
return $this->addGiftItem($quote, $targetQuantity, $storeId) || $changed;
}
$primaryItem = array_shift($configuredGiftItems);
if ($primaryItem instanceof Item) {
$changed = $this->prepareGiftItem($primaryItem, $targetQuantity) || $changed;
}
foreach ($configuredGiftItems as $duplicateItem) {
$quote->deleteItem($duplicateItem);
$changed = true;
}
return $changed;
}
/** @return Item[] */
private function getGiftItems(Quote $quote): array
{
$items = [];
foreach ($quote->getAllVisibleItems() as $item) {
if ((bool)$item->getData(GiftItem::FLAG)) {
$items[] = $item;
}
}
return $items;
}
/** @param Item[] $giftItems */
private function removeGiftItems(Quote $quote, array $giftItems): bool
{
foreach ($giftItems as $giftItem) {
$quote->deleteItem($giftItem);
}
return $giftItems !== [];
}
private function addGiftItem(Quote $quote, int $quantity, int $storeId): bool
{
$sku = $this->config->getGiftSku($storeId);
try {
$product = clone $this->productRepository->get($sku, false, $storeId, true);
if ($product->getTypeId() !== Type::TYPE_SIMPLE) {
throw new LocalizedException(__('The configured free gift must be a simple product.'));
}
$product->setStoreId($storeId);
$product->setIsSuperMode(true);
$product->addCustomOption(GiftItem::OPTION_CODE, '1');
$product->addCustomOption(
'additional_options',
$this->json->serialize([
[
'label' => (string)__('Promotion'),
'value' => (string)__('Free Gift')
]
])
);
$request = $this->dataObjectFactory->create(['data' => ['qty' => $quantity]]);
$result = $this->internalAddGuard->execute(
static fn () => $quote->addProduct($product, $request)
);
if (!$result instanceof Item) {
throw new LocalizedException(__(is_string($result) ? $result : 'The free gift could not be added.'));
}
$this->prepareGiftItem($result, $quantity);
return true;
} catch (\Throwable $exception) {
$this->logger->error(
'Unable to add the configured automatic free gift.',
[
'sku' => $sku,
'store_id' => $storeId,
'exception' => $exception
]
);
return false;
}
}
private function prepareGiftItem(Item $item, int $quantity): bool
{
$changed = abs((float)$item->getQty() - $quantity) > 0.0001
|| !(bool)$item->getData(GiftItem::FLAG)
|| $item->getCustomPrice() === null
|| abs((float)$item->getCustomPrice()) > 0.0001;
$item->setQty($quantity);
$item->setData(GiftItem::FLAG, 1);
$item->setCustomPrice(0.0);
$item->setOriginalCustomPrice(0.0);
$item->setOriginalPrice(0.0);
$item->setBaseOriginalPrice(0.0);
$item->setNoDiscount(1);
$item->setDiscountAmount(0.0);
$item->setBaseDiscountAmount(0.0);
$item->setAppliedRuleIds(null);
$item->getProduct()->setIsSuperMode(true);
return $changed;
}
}