<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Quote\Model;
use Magento\Catalog\Model\Product\Attribute\Source\Status as ProductStatus;
use Magento\Customer\Api\Data\CustomerInterface;
use Magento\Customer\Api\Data\GroupInterface;
use Magento\Directory\Model\AllowedCountries;
use Magento\Framework\Api\AttributeValueFactory;
use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Model\AbstractExtensibleModel;
use Magento\Quote\Api\Data\PaymentInterface;
use Magento\Quote\Model\Quote\Address;
use Magento\Quote\Model\Quote\Address\Total as AddressTotal;
use Magento\Sales\Model\Status;
use Magento\Store\Model\ScopeInterface;
/**
* Quote model
*
* Supported events:
* sales_quote_load_after
* sales_quote_save_before
* sales_quote_save_after
* sales_quote_delete_before
* sales_quote_delete_after
*
* @api
* @method int getIsMultiShipping()
* @method Quote setIsMultiShipping(int $value)
* @method float getStoreToBaseRate()
* @method Quote setStoreToBaseRate(float $value)
* @method float getStoreToQuoteRate()
* @method Quote setStoreToQuoteRate(float $value)
* @method string getBaseCurrencyCode()
* @method Quote setBaseCurrencyCode(string $value)
* @method string getStoreCurrencyCode()
* @method Quote setStoreCurrencyCode(string $value)
* @method string getQuoteCurrencyCode()
* @method Quote setQuoteCurrencyCode(string $value)
* @method float getGrandTotal()
* @method Quote setGrandTotal(float $value)
* @method float getBaseGrandTotal()
* @method Quote setBaseGrandTotal(float $value)
* @method int getCustomerId()
* @method Quote setCustomerId(int $value)
* @method Quote setCustomerGroupId(int $value)
* @method string getCustomerEmail()
* @method Quote setCustomerEmail(string $value)
* @method string getCustomerPrefix()
* @method Quote setCustomerPrefix(string $value)
* @method string getCustomerFirstname()
* @method Quote setCustomerFirstname(string $value)
* @method string getCustomerMiddlename()
* @method Quote setCustomerMiddlename(string $value)
* @method string getCustomerLastname()
* @method Quote setCustomerLastname(string $value)
* @method string getCustomerSuffix()
* @method Quote setCustomerSuffix(string $value)
* @method string getCustomerDob()
* @method Quote setCustomerDob(string $value)
* @method string getRemoteIp()
* @method Quote setRemoteIp(string $value)
* @method string getAppliedRuleIds()
* @method Quote setAppliedRuleIds(string $value)
* @method string getPasswordHash()
* @method Quote setPasswordHash(string $value)
* @method string getCouponCode()
* @method Quote setCouponCode(string $value)
* @method string getGlobalCurrencyCode()
* @method Quote setGlobalCurrencyCode(string $value)
* @method float getBaseToGlobalRate()
* @method Quote setBaseToGlobalRate(float $value)
* @method float getBaseToQuoteRate()
* @method Quote setBaseToQuoteRate(float $value)
* @method string getCustomerTaxvat()
* @method Quote setCustomerTaxvat(string $value)
* @method string getCustomerGender()
* @method Quote setCustomerGender(string $value)
* @method float getSubtotal()
* @method Quote setSubtotal(float $value)
* @method float getBaseSubtotal()
* @method Quote setBaseSubtotal(float $value)
* @method float getSubtotalWithDiscount()
* @method Quote setSubtotalWithDiscount(float $value)
* @method float getBaseSubtotalWithDiscount()
* @method Quote setBaseSubtotalWithDiscount(float $value)
* @method int getIsChanged()
* @method Quote setIsChanged(int $value)
* @method int getTriggerRecollect()
* @method Quote setTriggerRecollect(int $value)
* @method string getExtShippingInfo()
* @method Quote setExtShippingInfo(string $value)
* @method int getGiftMessageId()
* @method Quote setGiftMessageId(int $value)
* @method bool|null getIsPersistent()
* @method Quote setIsPersistent(bool $value)
* @method Quote setSharedStoreIds(array $values)
* @method Quote setWebsite($value)
* @SuppressWarnings(PHPMD.ExcessivePublicCount)
* @SuppressWarnings(PHPMD.TooManyFields)
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @since 100.0.2
*/
class Quote extends AbstractExtensibleModel implements \Magento\Quote\Api\Data\CartInterface
{
/**
* Checkout login method key
*/
public const CHECKOUT_METHOD_LOGIN_IN = 'login_in';
/**
* @var string
*/
protected $_eventPrefix = 'sales_quote';
/**
* @var string
*/
protected $_eventObject = 'quote';
/**
* Quote customer model object
*
* @var \Magento\Customer\Model\Customer
*/
protected $_customer;
/**
* Quote addresses collection
*
* @var \Magento\Eav\Model\Entity\Collection\AbstractCollection
*/
protected $_addresses;
/**
* Quote items collection
*
* @var \Magento\Eav\Model\Entity\Collection\AbstractCollection
*/
protected $_items;
/**
* Quote payments
*
* @var \Magento\Eav\Model\Entity\Collection\AbstractCollection
*/
protected $_payments;
/**
* @var \Magento\Quote\Model\Quote\Payment
*/
protected $_currentPayment;
/**
* Different groups of error infos
*
* @var array
*/
protected $_errorInfoGroups = [];
/**
* Whether quote should not be saved
*
* @var bool
*/
protected $_preventSaving = false;
/**
* Product of the catalog
*
* @var \Magento\Catalog\Helper\Product
*/
protected $_catalogProduct;
/**
* To perform validation on the quote
*
* @var \Magento\Quote\Model\QuoteValidator
*/
protected $quoteValidator;
/**
* Core store config
*
* @var \Magento\Framework\App\Config\ScopeConfigInterface
*/
protected $_scopeConfig;
/**
* @var \Magento\Store\Model\StoreManagerInterface
*/
protected $_storeManager;
/**
* @var \Magento\Framework\App\Config\ScopeConfigInterface
*/
protected $_config;
/**
* @var \Magento\Quote\Model\Quote\AddressFactory
*/
protected $_quoteAddressFactory;
/**
* @var \Magento\Customer\Model\CustomerFactory
*/
protected $_customerFactory;
/**
* Repository for group to perform CRUD operations
*
* @var \Magento\Customer\Api\GroupRepositoryInterface
*/
protected $groupRepository;
/**
* @var \Magento\Quote\Model\ResourceModel\Quote\Item\CollectionFactory
*/
protected $_quoteItemCollectionFactory;
/**
* @var \Magento\Quote\Model\Quote\ItemFactory
*/
protected $_quoteItemFactory;
/**
* @var \Magento\Framework\Message\Factory
*/
protected $messageFactory;
/**
* @var \Magento\Sales\Model\Status\ListFactory
*/
protected $_statusListFactory;
/**
* @var \Magento\Catalog\Api\ProductRepositoryInterface
*/
protected $productRepository;
/**
* @var \Magento\Quote\Model\Quote\PaymentFactory
*/
protected $_quotePaymentFactory;
/**
* @var \Magento\Quote\Model\ResourceModel\Quote\Payment\CollectionFactory
*/
protected $_quotePaymentCollectionFactory;
/**
* @var \Magento\Framework\DataObject\Copy
*/
protected $_objectCopyService;
/**
* Repository for customer address to perform crud operations
*
* @var \Magento\Customer\Api\AddressRepositoryInterface
*/
protected $addressRepository;
/**
* It is used for building search criteria
*
* @var \Magento\Framework\Api\SearchCriteriaBuilder
*/
protected $searchCriteriaBuilder;
/**
* This is used for holding builder object for filter service
*
* @var \Magento\Framework\Api\FilterBuilder
*/
protected $filterBuilder;
/**
* @var \Magento\CatalogInventory\Api\StockRegistryInterface
*/
protected $stockRegistry;
/**
* @var \Magento\Quote\Model\Quote\Item\Processor
*/
protected $itemProcessor;
/**
* @var \Magento\Framework\DataObject\Factory
*/
protected $objectFactory;
/**
* @var \Magento\Framework\Api\ExtensibleDataObjectConverter
*/
protected $extensibleDataObjectConverter;
/**
* @var \Magento\Customer\Api\Data\AddressInterfaceFactory
*/
protected $addressDataFactory;
/**
* @var \Magento\Customer\Api\Data\CustomerInterfaceFactory
*/
protected $customerDataFactory;
/**
* @var \Magento\Customer\Api\CustomerRepositoryInterface
*/
protected $customerRepository;
/**
* @var Cart\CurrencyFactory
*/
protected $currencyFactory;
/**
* @var \Magento\Framework\Api\DataObjectHelper
*/
protected $dataObjectHelper;
/**
* @var JoinProcessorInterface
*/
protected $extensionAttributesJoinProcessor;
/**
* @var \Magento\Quote\Model\Quote\TotalsCollector
*/
protected $totalsCollector;
/**
* @var \Magento\Quote\Model\Quote\TotalsReader
*/
protected $totalsReader;
/**
* @var \Magento\Quote\Model\ShippingFactory
*/
protected $shippingFactory;
/**
* @var \Magento\Quote\Model\ShippingAssignmentFactory
*/
protected $shippingAssignmentFactory;
/**
* Quote shipping addresses items cache
*
* @var array
*/
protected $shippingAddressesItems;
/**
* @var \Magento\Sales\Model\OrderIncrementIdChecker
*/
private $orderIncrementIdChecker;
/**
* @var AllowedCountries
*/
private $allowedCountriesReader;
/**
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Registry $registry
* @param \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory
* @param AttributeValueFactory $customAttributeFactory
* @param QuoteValidator $quoteValidator
* @param \Magento\Catalog\Helper\Product $catalogProduct
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param \Magento\Framework\App\Config\ScopeConfigInterface $config
* @param Quote\AddressFactory $quoteAddressFactory
* @param \Magento\Customer\Model\CustomerFactory $customerFactory
* @param \Magento\Customer\Api\GroupRepositoryInterface $groupRepository
* @param \Magento\Quote\Model\ResourceModel\Quote\Item\CollectionFactory $quoteItemCollectionFactory
* @param Quote\ItemFactory $quoteItemFactory
* @param \Magento\Framework\Message\Factory $messageFactory
* @param Status\ListFactory $statusListFactory
* @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository
* @param Quote\PaymentFactory $quotePaymentFactory
* @param \Magento\Quote\Model\ResourceModel\Quote\Payment\CollectionFactory $quotePaymentCollectionFactory
* @param \Magento\Framework\DataObject\Copy $objectCopyService
* @param \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry
* @param Quote\Item\Processor $itemProcessor
* @param \Magento\Framework\DataObject\Factory $objectFactory
* @param \Magento\Customer\Api\AddressRepositoryInterface $addressRepository
* @param \Magento\Framework\Api\SearchCriteriaBuilder $criteriaBuilder
* @param \Magento\Framework\Api\FilterBuilder $filterBuilder
* @param \Magento\Customer\Api\Data\AddressInterfaceFactory $addressDataFactory
* @param \Magento\Customer\Api\Data\CustomerInterfaceFactory $customerDataFactory
* @param \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository
* @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper
* @param \Magento\Framework\Api\ExtensibleDataObjectConverter $extensibleDataObjectConverter
* @param Cart\CurrencyFactory $currencyFactory
* @param JoinProcessorInterface $extensionAttributesJoinProcessor
* @param Quote\TotalsCollector $totalsCollector
* @param Quote\TotalsReader $totalsReader
* @param ShippingFactory $shippingFactory
* @param ShippingAssignmentFactory $shippingAssignmentFactory
* @param \Magento\Framework\Model\ResourceModel\AbstractResource|null $resource
* @param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection
* @param array $data
* @param \Magento\Sales\Model\OrderIncrementIdChecker|null $orderIncrementIdChecker
* @param AllowedCountries|null $allowedCountriesReader
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
\Magento\Framework\Model\Context $context,
\Magento\Framework\Registry $registry,
\Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory,
AttributeValueFactory $customAttributeFactory,
\Magento\Quote\Model\QuoteValidator $quoteValidator,
\Magento\Catalog\Helper\Product $catalogProduct,
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Framework\App\Config\ScopeConfigInterface $config,
\Magento\Quote\Model\Quote\AddressFactory $quoteAddressFactory,
\Magento\Customer\Model\CustomerFactory $customerFactory,
\Magento\Customer\Api\GroupRepositoryInterface $groupRepository,
\Magento\Quote\Model\ResourceModel\Quote\Item\CollectionFactory $quoteItemCollectionFactory,
\Magento\Quote\Model\Quote\ItemFactory $quoteItemFactory,
\Magento\Framework\Message\Factory $messageFactory,
\Magento\Sales\Model\Status\ListFactory $statusListFactory,
\Magento\Catalog\Api\ProductRepositoryInterface $productRepository,
\Magento\Quote\Model\Quote\PaymentFactory $quotePaymentFactory,
\Magento\Quote\Model\ResourceModel\Quote\Payment\CollectionFactory $quotePaymentCollectionFactory,
\Magento\Framework\DataObject\Copy $objectCopyService,
\Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry,
\Magento\Quote\Model\Quote\Item\Processor $itemProcessor,
\Magento\Framework\DataObject\Factory $objectFactory,
\Magento\Customer\Api\AddressRepositoryInterface $addressRepository,
\Magento\Framework\Api\SearchCriteriaBuilder $criteriaBuilder,
\Magento\Framework\Api\FilterBuilder $filterBuilder,
\Magento\Customer\Api\Data\AddressInterfaceFactory $addressDataFactory,
\Magento\Customer\Api\Data\CustomerInterfaceFactory $customerDataFactory,
\Magento\Customer\Api\CustomerRepositoryInterface $customerRepository,
\Magento\Framework\Api\DataObjectHelper $dataObjectHelper,
\Magento\Framework\Api\ExtensibleDataObjectConverter $extensibleDataObjectConverter,
\Magento\Quote\Model\Cart\CurrencyFactory $currencyFactory,
JoinProcessorInterface $extensionAttributesJoinProcessor,
Quote\TotalsCollector $totalsCollector,
Quote\TotalsReader $totalsReader,
\Magento\Quote\Model\ShippingFactory $shippingFactory,
\Magento\Quote\Model\ShippingAssignmentFactory $shippingAssignmentFactory,
\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
array $data = [],
\Magento\Sales\Model\OrderIncrementIdChecker $orderIncrementIdChecker = null,
AllowedCountries $allowedCountriesReader = null
) {
$this->quoteValidator = $quoteValidator;
$this->_catalogProduct = $catalogProduct;
$this->_scopeConfig = $scopeConfig;
$this->_storeManager = $storeManager;
$this->_config = $config;
$this->_quoteAddressFactory = $quoteAddressFactory;
$this->_customerFactory = $customerFactory;
$this->groupRepository = $groupRepository;
$this->_quoteItemCollectionFactory = $quoteItemCollectionFactory;
$this->_quoteItemFactory = $quoteItemFactory;
$this->messageFactory = $messageFactory;
$this->_statusListFactory = $statusListFactory;
$this->productRepository = $productRepository;
$this->_quotePaymentFactory = $quotePaymentFactory;
$this->_quotePaymentCollectionFactory = $quotePaymentCollectionFactory;
$this->_objectCopyService = $objectCopyService;
$this->addressRepository = $addressRepository;
$this->searchCriteriaBuilder = $criteriaBuilder;
$this->filterBuilder = $filterBuilder;
$this->stockRegistry = $stockRegistry;
$this->itemProcessor = $itemProcessor;
$this->objectFactory = $objectFactory;
$this->addressDataFactory = $addressDataFactory;
$this->customerDataFactory = $customerDataFactory;
$this->customerRepository = $customerRepository;
$this->dataObjectHelper = $dataObjectHelper;
$this->extensibleDataObjectConverter = $extensibleDataObjectConverter;
$this->currencyFactory = $currencyFactory;
$this->extensionAttributesJoinProcessor = $extensionAttributesJoinProcessor;
$this->totalsCollector = $totalsCollector;
$this->totalsReader = $totalsReader;
$this->shippingFactory = $shippingFactory;
$this->shippingAssignmentFactory = $shippingAssignmentFactory;
$this->orderIncrementIdChecker = $orderIncrementIdChecker ?: ObjectManager::getInstance()
->get(\Magento\Sales\Model\OrderIncrementIdChecker::class);
$this->allowedCountriesReader = $allowedCountriesReader
?: ObjectManager::getInstance()->get(AllowedCountries::class);
parent::__construct(
$context,
$registry,
$extensionFactory,
$customAttributeFactory,
$resource,
$resourceCollection,
$data
);
}
/**
* Init resource model
*
* @return void
*/
protected function _construct()
{
$this->_init(\Magento\Quote\Model\ResourceModel\Quote::class);
}
/**
* Returns information about quote currency, such as code, exchange rate, and so on.
*
* @return \Magento\Quote\Api\Data\CurrencyInterface|null Quote currency information. Otherwise, null.
* @codeCoverageIgnoreStart
*/
public function getCurrency()
{
$currency = $this->getData(self::KEY_CURRENCY);
if (!$currency) {
$currency = $this->currencyFactory->create()
->setGlobalCurrencyCode($this->getGlobalCurrencyCode())
->setBaseCurrencyCode($this->getBaseCurrencyCode())
->setStoreCurrencyCode($this->getStoreCurrencyCode())
->setQuoteCurrencyCode($this->getQuoteCurrencyCode())
->setStoreToBaseRate($this->getStoreToBaseRate())
->setStoreToQuoteRate($this->getStoreToQuoteRate())
->setBaseToGlobalRate($this->getBaseToGlobalRate())
->setBaseToQuoteRate($this->getBaseToQuoteRate());
$this->setData(self::KEY_CURRENCY, $currency);
}
return $currency;
}
/**
* @inheritdoc
*/
public function setCurrency(\Magento\Quote\Api\Data\CurrencyInterface $currency = null)
{
return $this->setData(self::KEY_CURRENCY, $currency);
}
/**
* @inheritdoc
*/
public function getItems()
{
return $this->_getData(self::KEY_ITEMS);
}
/**
* @inheritdoc
*/
public function setItems(array $items = null)
{
return $this->setData(self::KEY_ITEMS, $items);
}
/**
* @inheritdoc
*/
public function getCreatedAt()
{
return $this->_getData(self::KEY_CREATED_AT);
}
/**
* @inheritdoc
*/
public function setCreatedAt($createdAt)
{
return $this->setData(self::KEY_CREATED_AT, $createdAt);
}
/**
* @inheritdoc
*/
public function getUpdatedAt()
{
return $this->_getData(self::KEY_UPDATED_AT);
}
/**
* @inheritdoc
*/
public function setUpdatedAt($updatedAt)
{
return $this->setData(self::KEY_UPDATED_AT, $updatedAt);
}
/**
* @inheritdoc
*/
public function getConvertedAt()
{
return $this->_getData(self::KEY_CONVERTED_AT);
}
/**
* @inheritdoc
*/
public function setConvertedAt($convertedAt)
{
return $this->setData(self::KEY_CONVERTED_AT, $convertedAt);
}
/**
* @inheritdoc
*/
public function getIsActive()
{
return $this->_getData(self::KEY_IS_ACTIVE);
}
/**
* @inheritdoc
*/
public function setIsActive($isActive)
{
return $this->setData(self::KEY_IS_ACTIVE, $isActive);
}
/**
* @inheritdoc
*/
public function setIsVirtual($isVirtual)
{
return $this->setData(self::KEY_IS_VIRTUAL, $isVirtual);
}
/**
* @inheritdoc
*/
public function getItemsCount()
{
return $this->_getData(self::KEY_ITEMS_COUNT);
}
/**
* @inheritdoc
*/
public function setItemsCount($itemsCount)
{
return $this->setData(self::KEY_ITEMS_COUNT, $itemsCount);
}
/**
* @inheritdoc
*/
public function getItemsQty()
{
return $this->_getData(self::KEY_ITEMS_QTY);
}
/**
* @inheritdoc
*/
public function setItemsQty($itemsQty)
{
return $this->setData(self::KEY_ITEMS_QTY, $itemsQty);
}
/**
* @inheritdoc
*/
public function getOrigOrderId()
{
return $this->_getData(self::KEY_ORIG_ORDER_ID);
}
/**
* @inheritdoc
*/
public function setOrigOrderId($origOrderId)
{
return $this->setData(self::KEY_ORIG_ORDER_ID, $origOrderId);
}
/**
* @inheritdoc
*/
public function getReservedOrderId()
{
return $this->_getData(self::KEY_RESERVED_ORDER_ID);
}
/**
* @inheritdoc
*/
public function setReservedOrderId($reservedOrderId)
{
return $this->setData(self::KEY_RESERVED_ORDER_ID, $reservedOrderId);
}
/**
* @inheritdoc
*/
public function getCustomerIsGuest()
{
return $this->_getData(self::KEY_CUSTOMER_IS_GUEST);
}
/**
* @inheritdoc
*/
public function setCustomerIsGuest($customerIsGuest)
{
return $this->setData(self::KEY_CUSTOMER_IS_GUEST, $customerIsGuest);
}
/**
* @inheritdoc
*/
public function getCustomerNote()
{
return $this->_getData(self::KEY_CUSTOMER_NOTE);
}
/**
* @inheritdoc
*/
public function setCustomerNote($customerNote)
{
return $this->setData(self::KEY_CUSTOMER_NOTE, $customerNote);
}
/**
* @inheritdoc
*/
public function getCustomerNoteNotify()
{
return $this->_getData(self::KEY_CUSTOMER_NOTE_NOTIFY);
}
/**
* @inheritdoc
*/
public function setCustomerNoteNotify($customerNoteNotify)
{
return $this->setData(self::KEY_CUSTOMER_NOTE_NOTIFY, $customerNoteNotify);
}
//@codeCoverageIgnoreEnd
/**
* @inheritdoc
*/
public function getStoreId()
{
if (!$this->hasStoreId()) {
return $this->_storeManager->getStore()->getId();
}
return (int)$this->_getData(self::KEY_STORE_ID);
}
/**
* @inheritdoc
*/
public function setStoreId($storeId)
{
$this->setData(self::KEY_STORE_ID, (int)$storeId);
return $this;
}
/**
* Get quote store model object
*
* @return \Magento\Store\Model\Store
*/
public function getStore()
{
return $this->_storeManager->getStore($this->getStoreId());
}
/**
* Declare quote store model
*
* @param \Magento\Store\Model\Store $store
* @return $this
*/
public function setStore(\Magento\Store\Model\Store $store)
{
$this->setStoreId($store->getId());
return $this;
}
/**
* Get all available store ids for quote
*
* @return array
*/
public function getSharedStoreIds()
{
$ids = $this->_getData('shared_store_ids');
if ($ids === null || !is_array($ids)) {
$website = $this->getWebsite();
if ($website) {
return $website->getStoreIds();
}
return $this->getStore()->getWebsite()->getStoreIds();
}
return $ids;
}
/**
* Prepare data before save
*
* @return $this
*/
public function beforeSave()
{
/**
* Currency logic
*
* global - currency which is set for default in backend
* base - currency which is set for current website. all attributes that
* have 'base_' prefix saved in this currency
* quote/order - currency which was selected by customer or configured by
* admin for current store. currency in which customer sees
* price thought all checkout.
*
* Rates:
* base_to_global & base_to_quote/base_to_order
*/
$globalCurrencyCode = $this->_config->getValue(
\Magento\Directory\Model\Currency::XML_PATH_CURRENCY_BASE,
'default'
);
$baseCurrency = $this->getStore()->getBaseCurrency();
if ($this->hasForcedCurrency()) {
$quoteCurrency = $this->getForcedCurrency();
} else {
$quoteCurrency = $this->getStore()->getCurrentCurrency();
}
$this->setGlobalCurrencyCode($globalCurrencyCode);
$this->setBaseCurrencyCode($baseCurrency->getCode());
$this->setStoreCurrencyCode($baseCurrency->getCode());
$this->setQuoteCurrencyCode($quoteCurrency->getCode());
$this->setBaseToGlobalRate($baseCurrency->getRate($globalCurrencyCode));
$this->setBaseToQuoteRate($baseCurrency->getRate($quoteCurrency));
if (!$this->hasChangedFlag() || $this->getChangedFlag() == true) {
$this->setIsChanged(1);
} else {
$this->setIsChanged(0);
}
if ($this->_customer) {
$this->setCustomerId($this->_customer->getId());
}
//mark quote if it has virtual products only
$this->setIsVirtual($this->getIsVirtual());
if ($this->hasDataChanges()) {
$this->setUpdatedAt(null);
}
parent::beforeSave();
return $this;
}
/**
* Loading quote data by customer
*
* @param \Magento\Customer\Model\Customer|int $customer
* @deprecated 101.0.0 Deprecated to handle external usages of customer methods
* @see https://jira.corp.magento.com/browse/MAGETWO-19935
* @return $this
*/
public function loadByCustomer($customer)
{
/* @TODO: remove this if after external usages of loadByCustomerId are refactored in MAGETWO-19935 */
if ($customer instanceof \Magento\Customer\Model\Customer || $customer instanceof CustomerInterface) {
$customerId = $customer->getId();
} else {
$customerId = (int)$customer;
}
$this->_getResource()->loadByCustomerId($this, $customerId);
$this->_afterLoad();
return $this;
}
/**
* Loading only active quote
*
* @param int $quoteId
* @return $this
*/
public function loadActive($quoteId)
{
$this->_getResource()->loadActive($this, $quoteId);
$this->_afterLoad();
return $this;
}
/**
* Loading quote by identifier
*
* @param int $quoteId
* @return $this
*/
public function loadByIdWithoutStore($quoteId)
{
$this->_getResource()->loadByIdWithoutStore($this, $quoteId);
$this->_afterLoad();
return $this;
}
/**
* Assign customer model object data to quote
*
* @param \Magento\Customer\Api\Data\CustomerInterface $customer
* @return $this
*/
public function assignCustomer(\Magento\Customer\Api\Data\CustomerInterface $customer)
{
return $this->assignCustomerWithAddressChange($customer);
}
/**
* Assign customer model to quote with billing and shipping address change
*
* @param \Magento\Customer\Api\Data\CustomerInterface $customer
* @param Address $billingAddress Quote billing address
* @param Address $shippingAddress Quote shipping address
* @return $this
*/
public function assignCustomerWithAddressChange(
\Magento\Customer\Api\Data\CustomerInterface $customer,
Address $billingAddress = null,
Address $shippingAddress = null
) {
if ($customer->getId()) {
$this->setCustomer($customer);
if (null !== $billingAddress) {
$this->setBillingAddress($billingAddress);
} else {
try {
$defaultBillingAddress = $this->addressRepository->getById($customer->getDefaultBilling());
// phpcs:ignore Magento2.CodeAnalysis.EmptyBlock
} catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
}
if (isset($defaultBillingAddress)) {
/** @var \Magento\Quote\Model\Quote\Address $billingAddress */
$billingAddress = $this->_quoteAddressFactory->create();
$billingAddress->importCustomerAddressData($defaultBillingAddress);
$this->assignAddress($billingAddress);
}
}
if (null === $shippingAddress) {
try {
$defaultShippingAddress = $this->addressRepository->getById($customer->getDefaultShipping());
// phpcs:ignore Magento2.CodeAnalysis.EmptyBlock
} catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
}
if (isset($defaultShippingAddress)) {
/** @var \Magento\Quote\Model\Quote\Address $shippingAddress */
$shippingAddress = $this->_quoteAddressFactory->create();
$shippingAddress->importCustomerAddressData($defaultShippingAddress);
} else {
$shippingAddress = $this->_quoteAddressFactory->create();
}
}
$this->assignAddress($shippingAddress, false);
}
return $this;
}
/**
* Define customer object
*
* @param \Magento\Customer\Api\Data\CustomerInterface $customer
* @return $this
*/
public function setCustomer(\Magento\Customer\Api\Data\CustomerInterface $customer = null)
{
/* @TODO: Remove the method after all external usages are refactored in MAGETWO-19930 */
$this->_customer = $customer;
$this->setCustomerId($customer->getId());
$origAddresses = $customer->getAddresses();
$customer->setAddresses([]);
$customerDataFlatArray = $this->objectFactory->create(
$this->extensibleDataObjectConverter->toFlatArray(
$customer,
[],
\Magento\Customer\Api\Data\CustomerInterface::class
)
);
$customer->setAddresses($origAddresses);
$this->_objectCopyService->copyFieldsetToTarget('customer_account', 'to_quote', $customerDataFlatArray, $this);
return $this;
}
/**
* Retrieve customer model object
*
* @return \Magento\Customer\Api\Data\CustomerInterface|\Magento\Framework\Api\ExtensibleDataInterface
*/
public function getCustomer()
{
/**
* @TODO: Remove the method after all external usages are refactored in MAGETWO-19930
* _customer and _customerFactory variables should be eliminated as well
*/
if (null === $this->_customer) {
try {
$this->_customer = $this->customerRepository->getById($this->getCustomerId());
} catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
$this->_customer = $this->customerDataFactory->create();
$this->_customer->setId(null);
}
}
return $this->_customer;
}
/**
* Substitute customer addresses
*
* @param \Magento\Customer\Api\Data\AddressInterface[] $addresses
* @return $this
*/
public function setCustomerAddressData(array $addresses)
{
foreach ($addresses as $address) {
if (!$address->getId()) {
$this->addCustomerAddress($address);
}
}
return $this;
}
/**
* Add address to the customer, created out of a Data Object
*
* TODO refactor in scope of MAGETWO-19930
*
* @param \Magento\Customer\Api\Data\AddressInterface $address
* @return $this
*/
public function addCustomerAddress(\Magento\Customer\Api\Data\AddressInterface $address)
{
$addresses = (array)$this->getCustomer()->getAddresses();
$addresses[] = $address;
$this->getCustomer()->setAddresses($addresses);
$this->updateCustomerData($this->getCustomer());
return $this;
}
/**
* Update customer data object
*
* @param \Magento\Customer\Api\Data\CustomerInterface $customer
* @return $this
*/
public function updateCustomerData(\Magento\Customer\Api\Data\CustomerInterface $customer)
{
$quoteCustomer = $this->getCustomer();
$this->dataObjectHelper->mergeDataObjects(CustomerInterface::class, $quoteCustomer, $customer);
$this->setCustomer($quoteCustomer);
return $this;
}
/**
* Retrieve customer group id
*
* @return int
*/
public function getCustomerGroupId()
{
if ($this->hasData('customer_group_id')) {
return $this->getData('customer_group_id');
} elseif ($this->getCustomerId()) {
return $this->getCustomer()->getGroupId();
} else {
return GroupInterface::NOT_LOGGED_IN_ID;
}
}
/**
* @inheritdoc
*/
public function getCustomerTaxClassId()
{
/**
* tax class can vary at any time. so instead of using the value from session,
* we need to retrieve from db every time to get the correct tax class
*/
//if (!$this->getData('customer_group_id') && !$this->getData('customer_tax_class_id')) {
$groupId = $this->getCustomerGroupId();
if ($groupId !== null) {
$taxClassId = null;
try {
$taxClassId = $this->groupRepository->getById($this->getCustomerGroupId())->getTaxClassId();
} catch (NoSuchEntityException $e) {
/**
* A customer MAY create a quote and AFTER that customer group MAY be deleted.
* That breaks a quote because it still refers no a non-existent customer group.
* In such a case we should load a new customer group id from the current customer
* object and use it to retrieve tax class and update quote.
*/
$groupId = $this->getCustomer()->getGroupId();
$this->setCustomerGroupId($groupId);
if ($groupId !== null) {
$taxClassId = $this->groupRepository->getById($groupId)->getTaxClassId();
}
}
$this->setCustomerTaxClassId($taxClassId);
}
return $this->getData(self::KEY_CUSTOMER_TAX_CLASS_ID);
}
/**
* @inheritdoc
*/
public function setCustomerTaxClassId($customerTaxClassId)
{
return $this->setData(self::KEY_CUSTOMER_TAX_CLASS_ID, $customerTaxClassId);
}
/**
* Retrieve quote address collection
*
* @return \Magento\Eav\Model\Entity\Collection\AbstractCollection
*/
public function getAddressesCollection()
{
if (null === $this->_addresses) {
$this->_addresses = $this->_quoteAddressFactory->create()->getCollection()->setQuoteFilter($this->getId());
if ($this->getId()) {
foreach ($this->_addresses as $address) {
$address->setQuote($this);
}
}
}
return $this->_addresses;
}
/**
* Retrieve quote address by type
*
* @param string $type
* @return Address
*/
protected function _getAddressByType($type)
{
foreach ($this->getAddressesCollection() as $address) {
if ($address->getAddressType() == $type && !$address->isDeleted()) {
return $address;
}
}
$address = $this->_quoteAddressFactory->create()->setAddressType($type);
$this->addAddress($address);
return $address;
}
/**
* Retrieve quote billing address
*
* @return Address
*/
public function getBillingAddress()
{
return $this->_getAddressByType(Address::TYPE_BILLING);
}
/**
* Retrieve quote shipping address
*
* @return Address
*/
public function getShippingAddress()
{
return $this->_getAddressByType(Address::TYPE_SHIPPING);
}
/**
* Get all shipping addresses.
*
* @return array
*/
public function getAllShippingAddresses()
{
$addresses = [];
foreach ($this->getAddressesCollection() as $address) {
if ($address->getAddressType() == Address::TYPE_SHIPPING && !$address->isDeleted()) {
$addresses[] = $address;
}
}
return $addresses;
}
/**
* Get all quote addresses
*
* @return \Magento\Quote\Model\Quote\Address[]
*/
public function getAllAddresses()
{
$addresses = [];
foreach ($this->getAddressesCollection() as $address) {
if (!$address->isDeleted()) {
$addresses[] = $address;
}
}
return $addresses;
}
/**
* Get address by id.
*
* @param int $addressId
* @return Address|false
*/
public function getAddressById($addressId)
{
foreach ($this->getAddressesCollection() as $address) {
if ($address->getId() == $addressId) {
return $address;
}
}
return false;
}
/**
* Get address by customer address id.
*
* @param int|string $addressId
* @return Address|false
*/
public function getAddressByCustomerAddressId($addressId)
{
foreach ($this->getAddressesCollection() as $address) {
if (!$address->isDeleted() && $address->getCustomerAddressId() == $addressId) {
return $address;
}
}
return false;
}
/**
* Get quote address by customer address ID.
*
* @param int|string $addressId
* @return Address|false
*/
public function getShippingAddressByCustomerAddressId($addressId)
{
/** @var \Magento\Quote\Model\Quote\Address $address */
foreach ($this->getAddressesCollection() as $address) {
if (!$address->isDeleted() &&
$address->getAddressType() == Address::TYPE_SHIPPING &&
$address->getCustomerAddressId() == $addressId
) {
return $address;
}
}
return false;
}
/**
* Remove address.
*
* @param int|string $addressId
* @return $this
*/
public function removeAddress($addressId)
{
foreach ($this->getAddressesCollection() as $address) {
if ($address->getId() == $addressId) {
$address->isDeleted(true);
break;
}
}
return $this;
}
/**
* Leave no more than one billing and one shipping address, fill them with default data
*
* @return $this
*/
public function removeAllAddresses()
{
$addressByType = [];
$addressesCollection = $this->getAddressesCollection();
// mark all addresses as deleted
foreach ($addressesCollection as $address) {
$type = $address->getAddressType();
if (!isset($addressByType[$type]) || $addressByType[$type]->getId() > $address->getId()) {
$addressByType[$type] = $address;
}
$address->isDeleted(true);
}
// create new billing and shipping addresses filled with default values, set this data to existing records
foreach ($addressByType as $type => $address) {
$id = $address->getId();
$emptyAddress = $this->_getAddressByType($type);
$address->setData($emptyAddress->getData())->setId($id)->isDeleted(false);
$emptyAddress->setDeleteImmediately(true);
}
// remove newly created billing and shipping addresses from collection to avoid senseless delete queries
foreach ($addressesCollection as $key => $item) {
if ($item->getDeleteImmediately()) {
$addressesCollection->removeItemByKey($key);
}
}
return $this;
}
/**
* Add address.
*
* @param \Magento\Quote\Api\Data\AddressInterface $address
* @return $this
*/
public function addAddress(\Magento\Quote\Api\Data\AddressInterface $address)
{
$address->setQuote($this);
if (!$address->getId()) {
$this->getAddressesCollection()->addItem($address);
}
return $this;
}
/**
* Set billing address.
*
* @param \Magento\Quote\Api\Data\AddressInterface $address
* @return $this
*/
public function setBillingAddress(\Magento\Quote\Api\Data\AddressInterface $address = null)
{
$old = $this->getAddressesCollection()->getItemById($address->getId())
?? $this->getBillingAddress();
if ($old !== null) {
$old->addData($address->getData());
} else {
$this->addAddress($address->setAddressType(Address::TYPE_BILLING));
}
return $this;
}
/**
* Set shipping address
*
* @param \Magento\Quote\Api\Data\AddressInterface $address
* @return $this
*/
public function setShippingAddress(\Magento\Quote\Api\Data\AddressInterface $address = null)
{
if ($this->getIsMultiShipping()) {
$this->addAddress($address->setAddressType(Address::TYPE_SHIPPING));
} else {
$old = $this->getAddressesCollection()->getItemById($address->getId())
?? $this->getShippingAddress();
if ($old !== null) {
$old->addData($address->getData());
} else {
$this->addAddress($address->setAddressType(Address::TYPE_SHIPPING));
}
}
return $this;
}
/**
* Add shipping address.
*
* @param \Magento\Quote\Api\Data\AddressInterface $address
* @return $this
*/
public function addShippingAddress(\Magento\Quote\Api\Data\AddressInterface $address)
{
$this->setShippingAddress($address);
return $this;
}
/**
* Retrieve quote items collection
*
* @param bool $useCache
* @return \Magento\Eav\Model\Entity\Collection\AbstractCollection
*/
public function getItemsCollection($useCache = true)
{
if ($this->hasItemsCollection() && $useCache) {
return $this->getData('items_collection');
}
if (null === $this->_items || !$useCache) {
$this->_items = $this->_quoteItemCollectionFactory->create();
$this->extensionAttributesJoinProcessor->process($this->_items);
$this->_items->setQuote($this);
}
return $this->_items;
}
/**
* Retrieve quote items array
*
* @return array
*/
public function getAllItems()
{
$items = [];
/** @var \Magento\Quote\Model\Quote\Item $item */
foreach ($this->getItemsCollection() as $item) {
$product = $item->getProduct();
if (!$item->isDeleted() && ($product && (int)$product->getStatus() !== ProductStatus::STATUS_DISABLED)) {
$items[] = $item;
}
}
return $items;
}
/**
* Get array of all items what can be display directly
*
* @return \Magento\Quote\Model\Quote\Item[]
*/
public function getAllVisibleItems()
{
$items = [];
foreach ($this->getItemsCollection() as $item) {
if (!$item->isDeleted() && !$item->getParentItemId() && !$item->getParentItem()) {
$items[] = $item;
}
}
return $items;
}
/**
* Checking items availability
*
* @return bool
*/
public function hasItems()
{
return count($this->getAllItems()) > 0;
}
/**
* Checking availability of items with decimal qty
*
* @return bool
*/
public function hasItemsWithDecimalQty()
{
foreach ($this->getAllItems() as $item) {
$stockItemDo = $this->stockRegistry->getStockItem(
$item->getProduct()->getId(),
$item->getStore()->getWebsiteId()
);
if ($stockItemDo->getItemId() && $stockItemDo->getIsQtyDecimal()) {
return true;
}
}
return false;
}
/**
* Checking product exist in Quote
*
* @param int $productId
* @return bool
*/
public function hasProductId($productId)
{
foreach ($this->getAllItems() as $item) {
if ($item->getProductId() == $productId) {
return true;
}
}
return false;
}
/**
* Retrieve item model object by item identifier
*
* @param int $itemId
* @return \Magento\Quote\Model\Quote\Item|false
*/
public function getItemById($itemId)
{
foreach ($this->getItemsCollection() as $item) {
if ($item->getId() == $itemId) {
return $item;
}
}
return false;
}
/**
* Delete quote item. If it does not have identifier then it will be only removed from collection
*
* @param \Magento\Quote\Model\Quote\Item $item
* @return $this
*/
public function deleteItem(\Magento\Quote\Model\Quote\Item $item)
{
if ($item->getId()) {
$this->removeItem($item->getId());
} else {
$quoteItems = $this->getItemsCollection();
$items = [$item];
if ($item->getHasChildren()) {
foreach ($item->getChildren() as $child) {
$items[] = $child;
}
}
foreach ($quoteItems as $key => $quoteItem) {
foreach ($items as $item) {
if ($quoteItem->compare($item)) {
$quoteItems->removeItemByKey($key);
}
}
}
}
return $this;
}
/**
* Remove quote item by item identifier
*
* @param int $itemId
* @return $this
*/
public function removeItem($itemId)
{
$item = $this->getItemById($itemId);
if ($item) {
$item->setQuote($this);
$item->isDeleted(true);
if ($item->getHasChildren()) {
foreach ($item->getChildren() as $child) {
$child->isDeleted(true);
}
}
$parent = $item->getParentItem();
if ($parent) {
$parent->isDeleted(true);
}
$this->_eventManager->dispatch('sales_quote_remove_item', ['quote_item' => $item]);
}
return $this;
}
/**
* Mark all quote items as deleted (empty quote)
*
* @return $this
*/
public function removeAllItems()
{
foreach ($this->getItemsCollection() as $itemId => $item) {
if ($item->getId() === null) {
$this->getItemsCollection()->removeItemByKey($itemId);
} else {
$item->isDeleted(true);
}
}
return $this;
}
/**
* Adding new item to quote
*
* @param \Magento\Quote\Model\Quote\Item $item
* @return $this
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function addItem(\Magento\Quote\Model\Quote\Item $item)
{
$item->setQuote($this);
if (!$item->getId()) {
$this->getItemsCollection()->addItem($item);
$this->_eventManager->dispatch('sales_quote_add_item', ['quote_item' => $item]);
}
return $this;
}
/**
* Add product. Returns error message if product type instance can't prepare product.
*
* @param mixed $product
* @param null|float|\Magento\Framework\DataObject $request
* @param null|string $processMode
* @return \Magento\Quote\Model\Quote\Item|string
* @throws \Magento\Framework\Exception\LocalizedException
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
public function addProduct(
\Magento\Catalog\Model\Product $product,
$request = null,
$processMode = \Magento\Catalog\Model\Product\Type\AbstractType::PROCESS_MODE_FULL
) {
if ($request === null) {
$request = 1;
}
if (is_numeric($request)) {
$request = $this->objectFactory->create(['qty' => $request]);
}
if (!$request instanceof \Magento\Framework\DataObject) {
throw new \Magento\Framework\Exception\LocalizedException(
__('We found an invalid request for adding product to quote.')
);
}
if (!$product->isSalable()) {
throw new \Magento\Framework\Exception\LocalizedException(
__('Product that you are trying to add is not available.')
);
}
$cartCandidates = $product->getTypeInstance()->prepareForCartAdvanced($request, $product, $processMode);
/**
* Error message
*/
if (is_string($cartCandidates) || $cartCandidates instanceof \Magento\Framework\Phrase) {
return (string)$cartCandidates;
}
/**
* If prepare process return one object
*/
if (!is_array($cartCandidates)) {
$cartCandidates = [$cartCandidates];
}
$parentItem = null;
$errors = [];
$item = null;
$items = [];
foreach ($cartCandidates as $candidate) {
// Child items can be sticked together only within their parent
$stickWithinParent = $candidate->getParentProductId() ? $parentItem : null;
$candidate->setStickWithinParent($stickWithinParent);
$item = $this->getItemByProduct($candidate);
if (!$item) {
$item = $this->itemProcessor->init($candidate, $request);
$item->setQuote($this);
$item->setOptions($candidate->getCustomOptions());
$item->setProduct($candidate);
// Add only item that is not in quote already
$this->addItem($item);
}
$items[] = $item;
/**
* As parent item we should always use the item of first added product
*/
if (!$parentItem) {
$parentItem = $item;
}
if ($parentItem && $candidate->getParentProductId() && !$item->getParentItem()) {
$item->setParentItem($parentItem);
}
$this->itemProcessor->prepare($item, $request, $candidate);
// collect errors instead of throwing first one
if ($item->getHasError()) {
$this->deleteItem($item);
foreach ($item->getMessage(false) as $message) {
if (!in_array($message, $errors)) {
// filter duplicate messages
$errors[] = $message;
}
}
break;
}
}
if (!empty($errors)) {
throw new \Magento\Framework\Exception\LocalizedException(__(implode("\n", $errors)));
}
$this->_eventManager->dispatch('sales_quote_product_add_after', ['items' => $items]);
return $parentItem;
}
/**
* Adding catalog product object data to quote
*
* @param \Magento\Catalog\Model\Product $product
* @param int $qty
* @return \Magento\Quote\Model\Quote\Item
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
protected function _addCatalogProduct(\Magento\Catalog\Model\Product $product, $qty = 1)
{
$newItem = false;
$item = $this->getItemByProduct($product);
if (!$item) {
$item = $this->_quoteItemFactory->create();
$item->setQuote($this);
if ($this->_appState->getAreaCode() === \Magento\Backend\App\Area\FrontNameResolver::AREA_CODE) {
$item->setStoreId($this->getStore()->getId());
} else {
$item->setStoreId($this->_storeManager->getStore()->getId());
}
$newItem = true;
}
/**
* We can't modify existing child items
*/
if ($item->getId() && $product->getParentProductId()) {
return $item;
}
$item->setOptions($product->getCustomOptions())->setProduct($product);
// Add only item that is not in quote already (there can be other new or already saved item
if ($newItem) {
$this->addItem($item);
}
return $item;
}
/**
* Updates quote item with new configuration
*
* $params sets how current item configuration must be taken into account and additional options.
* It's passed to \Magento\Catalog\Helper\Product->addParamsToBuyRequest() to compose resulting buyRequest.
*
* Basically it can hold
* - 'current_config', \Magento\Framework\DataObject or array - current buyRequest that configures product in this
* item, used to restore currently attached files
* - 'files_prefix': string[a-z0-9_] - prefix that was added at frontend to names of file options (file inputs),
* so they won't intersect with other submitted options
*
* For more options see \Magento\Catalog\Helper\Product->addParamsToBuyRequest()
*
* @param int $itemId
* @param \Magento\Framework\DataObject $buyRequest
* @param null|array|\Magento\Framework\DataObject $params
* @return \Magento\Quote\Model\Quote\Item
* @throws \Magento\Framework\Exception\LocalizedException
*
* @see \Magento\Catalog\Helper\Product::addParamsToBuyRequest()
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public function updateItem($itemId, $buyRequest, $params = null)
{
$item = $this->getItemById($itemId);
if (!$item) {
throw new \Magento\Framework\Exception\LocalizedException(
__('This is the wrong quote item id to update configuration.')
);
}
$productId = $item->getProduct()->getId();
//We need to create new clear product instance with same $productId
//to set new option values from $buyRequest
$product = clone $this->productRepository->getById($productId, false, $this->getStore()->getId());
if (!$params) {
$params = new \Magento\Framework\DataObject();
} elseif (is_array($params)) {
$params = new \Magento\Framework\DataObject($params);
}
$params->setCurrentConfig($item->getBuyRequest());
$buyRequest = $this->_catalogProduct->addParamsToBuyRequest($buyRequest, $params);
$buyRequest->setResetCount(true);
$resultItem = $this->addProduct($product, $buyRequest);
if (is_string($resultItem)) {
throw new \Magento\Framework\Exception\LocalizedException(__($resultItem));
}
if ($resultItem->getParentItem()) {
$resultItem = $resultItem->getParentItem();
}
if ($resultItem->getId() != $itemId) {
/**
* Product configuration didn't stick to original quote item
* It either has same configuration as some other quote item's product or completely new configuration
*/
$this->removeItem($itemId);
$items = $this->getAllItems();
foreach ($items as $item) {
if ($item->getProductId() == $productId && $item->getId() != $resultItem->getId()) {
if ($resultItem->compare($item)) {
// Product configuration is same as in other quote item
$resultItem->setQty($resultItem->getQty() + $item->getQty());
$this->removeItem($item->getId());
break;
}
}
}
} else {
$resultItem->setQty($buyRequest->getQty());
}
return $resultItem;
}
/**
* Retrieve quote item by product id
*
* @param \Magento\Catalog\Model\Product $product
* @return \Magento\Quote\Model\Quote\Item|bool
*/
public function getItemByProduct($product)
{
/** @var \Magento\Quote\Model\Quote\Item[] $items */
$items = $this->getItemsCollection()->getItemsByColumnValue('product_id', $product->getId());
foreach ($items as $item) {
if (!$item->isDeleted()
&& $item->getProduct()
&& $item->getProduct()->getStatus() !== ProductStatus::STATUS_DISABLED
&& $item->representProduct($product)
) {
return $item;
}
}
return false;
}
/**
* Get items summary qty.
*
* @return int
*/
public function getItemsSummaryQty()
{
$qty = $this->getData('all_items_qty');
if (null === $qty) {
$qty = 0;
foreach ($this->getAllItems() as $item) {
if ($item->getParentItem()) {
continue;
}
$children = $item->getChildren();
if ($children && $item->isShipSeparately()) {
foreach ($children as $child) {
$qty += $child->getQty() * $item->getQty();
}
} else {
$qty += $item->getQty();
}
}
$this->setData('all_items_qty', $qty);
}
return $qty;
}
/**
* Get item virtual qty.
*
* @return int
*/
public function getItemVirtualQty()
{
$qty = $this->getData('virtual_items_qty');
if (null === $qty) {
$qty = 0;
foreach ($this->getAllItems() as $item) {
if ($item->getParentItem()) {
continue;
}
$children = $item->getChildren();
if ($children && $item->isShipSeparately()) {
foreach ($children as $child) {
if ($child->getProduct()->getIsVirtual()) {
$qty += $child->getQty();
}
}
} else {
if ($item->getProduct()->getIsVirtual()) {
$qty += $item->getQty();
}
}
}
$this->setData('virtual_items_qty', $qty);
}
return $qty;
}
/*********************** PAYMENTS ***************************/
/**
* Get payments collection.
*
* @return \Magento\Eav\Model\Entity\Collection\AbstractCollection
*/
public function getPaymentsCollection()
{
if (null === $this->_payments) {
$this->_payments = $this->_quotePaymentCollectionFactory->create()->setQuoteFilter($this->getId());
if ($this->getId()) {
foreach ($this->_payments as $payment) {
$payment->setQuote($this);
}
}
}
return $this->_payments;
}
/**
* Get payment.
*
* @return \Magento\Quote\Model\Quote\Payment
*/
public function getPayment()
{
if (null === $this->_currentPayment || !$this->_currentPayment) {
$this->_currentPayment = $this->_quotePaymentCollectionFactory->create()
->setQuoteFilter($this->getId())
->getFirstItem();
}
if ($payment = $this->_currentPayment) {
if ($this->getId()) {
$payment->setQuote($this);
}
if (!$payment->isDeleted()) {
return $payment;
}
}
$payment = $this->_quotePaymentFactory->create();
$this->addPayment($payment);
return $payment;
}
/**
* Adds a payment to quote
*
* @param PaymentInterface $payment
* @return $this
*/
protected function addPayment(PaymentInterface $payment)
{
$payment->setQuote($this);
if (!$payment->getId()) {
$this->getPaymentsCollection()->addItem($payment);
}
return $this;
}
/**
* Sets payment to current quote
*
* @param PaymentInterface $payment
* @return PaymentInterface
*/
public function setPayment(PaymentInterface $payment)
{
if (!$this->getIsMultiPayment() && ($old = $this->getPayment())) {
$payment->setId($old->getId());
}
$this->addPayment($payment);
return $payment;
}
/**
* Remove payment.
*
* @return $this
*/
public function removePayment()
{
$this->getPayment()->isDeleted(true);
return $this;
}
/**
* Collect totals
*
* @return $this
*/
public function collectTotals()
{
if ($this->getTotalsCollectedFlag()) {
return $this;
}
$total = $this->totalsCollector->collect($this);
$this->addData($total->getData());
$this->setTotalsCollectedFlag(true);
return $this;
}
/**
* Get all quote totals (sorted by priority)
*
* @return AddressTotal[]
*/
public function getTotals()
{
return $this->totalsReader->fetch($this, $this->getData());
}
/**
* Add message.
*
* @param string $message
* @param string $index
* @return $this
*/
public function addMessage($message, $index = 'error')
{
$messages = $this->getData('messages');
if (null === $messages) {
$messages = [];
}
if (isset($messages[$index])) {
return $this;
}
$message = $this->messageFactory->create(\Magento\Framework\Message\MessageInterface::TYPE_ERROR, $message);
$messages[$index] = $message;
$this->setData('messages', $messages);
return $this;
}
/**
* Retrieve current quote messages
*
* @return array
*/
public function getMessages()
{
$messages = $this->getData('messages');
if (null === $messages) {
$messages = [];
$this->setData('messages', $messages);
}
return $messages;
}
/**
* Retrieve current quote errors
*
* @return array
*/
public function getErrors()
{
$errors = [];
foreach ($this->getMessages() as $message) {
/* @var $error \Magento\Framework\Message\AbstractMessage */
if ($message->getType() == \Magento\Framework\Message\MessageInterface::TYPE_ERROR) {
$errors[] = $message;
}
}
return $errors;
}
/**
* Sets flag, whether this quote has some error associated with it.
*
* @codeCoverageIgnore
*
* @param bool $flag
* @return $this
*/
protected function _setHasError($flag)
{
return $this->setData('has_error', $flag);
}
/**
* Sets flag, whether this quote has some error associated with it.
* When TRUE - also adds 'unknown' error information to list of quote errors.
* When FALSE - clears whole list of quote errors.
* It's recommended to use addErrorInfo() instead - to be able to remove error statuses later.
*
* @param bool $flag
* @return $this
* @see addErrorInfo()
*/
public function setHasError($flag)
{
if ($flag) {
$this->addErrorInfo();
} else {
$this->_clearErrorInfo();
}
return $this;
}
/**
* Clears list of errors, associated with this quote. Also automatically removes error-flag from oneself.
*
* @return $this
*/
protected function _clearErrorInfo()
{
$this->_errorInfoGroups = [];
$this->_setHasError(false);
return $this;
}
/**
* Adds error information to the quote. Automatically sets error flag.
*
* @param string $type An internal error type ('error', 'qty', etc.), passed then to adding messages routine
* @param string|null $origin Usually a name of module, that embeds error
* @param int|null $code Error code, unique for origin, that sets it
* @param string|null $message Error message
* @param \Magento\Framework\DataObject|null $additionalData Any additional data, that caller would like to store
* @return $this
*/
public function addErrorInfo(
$type = 'error',
$origin = null,
$code = null,
$message = null,
$additionalData = null
) {
if (!isset($this->_errorInfoGroups[$type])) {
$this->_errorInfoGroups[$type] = $this->_statusListFactory->create();
}
$this->_errorInfoGroups[$type]->addItem($origin, $code, $message, $additionalData);
if ($message !== null) {
$this->addMessage($message, $type);
}
$this->_setHasError(true);
return $this;
}
/**
* Removes error infos, that have parameters equal to passed in $params.
* $params can have following keys (if not set - then any item is good for this key):
* 'origin', 'code', 'message'
*
* @param string $type An internal error type ('error', 'qty', etc.), passed then to adding messages routine
* @param array $params
* @return $this
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public function removeErrorInfosByParams($type, $params)
{
if ($type && !isset($this->_errorInfoGroups[$type])) {
return $this;
}
$errorLists = [];
if ($type) {
$errorLists[] = $this->_errorInfoGroups[$type];
} else {
$errorLists = $this->_errorInfoGroups;
}
foreach ($errorLists as $type => $errorList) {
$removedItems = $errorList->removeItemsByParams($params);
foreach ($removedItems as $item) {
if ($item['message'] !== null) {
$this->removeMessageByText($type, $item['message']);
}
}
}
$errorsExist = false;
foreach ($this->_errorInfoGroups as $errorListCheck) {
if ($errorListCheck->getItems()) {
$errorsExist = true;
break;
}
}
if (!$errorsExist) {
$this->_setHasError(false);
}
return $this;
}
/**
* Removes message by text
*
* @param string $type
* @param string $text
* @return $this
*/
public function removeMessageByText($type, $text)
{
$messages = $this->getData('messages');
if (null === $messages) {
$messages = [];
}
if (!isset($messages[$type])) {
return $this;
}
$message = $messages[$type];
if ($message instanceof \Magento\Framework\Message\AbstractMessage) {
$message = $message->getText();
} elseif (!is_string($message)) {
return $this;
}
if ($message == $text) {
unset($messages[$type]);
$this->setData('messages', $messages);
}
return $this;
}
/**
* Generate new increment order id and associate it with current quote
*
* @return $this
*/
public function reserveOrderId()
{
if (!$this->getReservedOrderId()) {
$this->setReservedOrderId($this->_getResource()->getReservedOrderId($this));
} else {
//checking if reserved order id was already used for some order
//if yes reserving new one if not using old one
if ($this->orderIncrementIdChecker->isIncrementIdUsed($this->getReservedOrderId())) {
$this->setReservedOrderId($this->_getResource()->getReservedOrderId($this));
}
}
return $this;
}
/**
* Validate minimum amount.
*
* @param bool $multishipping
* @return bool
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
public function validateMinimumAmount($multishipping = false)
{
$storeId = $this->getStoreId();
$minOrderActive = $this->_scopeConfig->isSetFlag(
'sales/minimum_order/active',
\Magento\Store\Model\ScopeInterface::SCOPE_STORE,
$storeId
);
if (!$minOrderActive) {
return true;
}
$includeDiscount = $this->_scopeConfig->getValue(
'sales/minimum_order/include_discount_amount',
\Magento\Store\Model\ScopeInterface::SCOPE_STORE,
$storeId
);
$minOrderMulti = $this->_scopeConfig->isSetFlag(
'sales/minimum_order/multi_address',
\Magento\Store\Model\ScopeInterface::SCOPE_STORE,
$storeId
);
$minAmount = $this->_scopeConfig->getValue(
'sales/minimum_order/amount',
\Magento\Store\Model\ScopeInterface::SCOPE_STORE,
$storeId
);
$taxInclude = $this->_scopeConfig->getValue(
'sales/minimum_order/tax_including',
\Magento\Store\Model\ScopeInterface::SCOPE_STORE,
$storeId
);
$addresses = $this->getAllAddresses();
if (!$multishipping) {
foreach ($addresses as $address) {
/* @var $address Address */
if (!$address->validateMinimumAmount()) {
return false;
}
}
return true;
}
if (!$minOrderMulti) {
foreach ($addresses as $address) {
$taxes = $taxInclude
? $address->getBaseTaxAmount() + $address->getBaseDiscountTaxCompensationAmount()
: 0;
foreach ($address->getQuote()->getItemsCollection() as $item) {
/** @var \Magento\Quote\Model\Quote\Item $item */
$amount = $includeDiscount ?
$item->getBaseRowTotal() - $item->getBaseDiscountAmount() + $taxes :
$item->getBaseRowTotal() + $taxes;
if ($amount < $minAmount) {
return false;
}
}
}
} else {
$baseTotal = 0;
foreach ($addresses as $address) {
$taxes = $taxInclude
? $address->getBaseTaxAmount() + $address->getBaseDiscountTaxCompensationAmount()
: 0;
$baseTotal += $includeDiscount ?
$address->getBaseSubtotalWithDiscount() + $taxes :
$address->getBaseSubtotal() + $taxes;
}
if ($baseTotal < $minAmount) {
return false;
}
}
return true;
}
/**
* Check quote for virtual product only
*
* @return bool
*/
public function isVirtual()
{
$isVirtual = true;
$countItems = 0;
foreach ($this->getItemsCollection() as $_item) {
/* @var $_item \Magento\Quote\Model\Quote\Item */
if ($_item->isDeleted() || $_item->getParentItemId()) {
continue;
}
$countItems++;
if (!$_item->getProduct()->getIsVirtual()) {
$isVirtual = false;
break;
}
}
return $countItems == 0 ? false : $isVirtual;
}
/**
* Check quote for virtual product only
*
* @return bool
* @SuppressWarnings(PHPMD.BooleanGetMethodName)
*/
public function getIsVirtual()
{
return (int)$this->isVirtual();
}
/**
* Has a virtual products on quote
*
* @return bool
*/
public function hasVirtualItems()
{
$hasVirtual = false;
foreach ($this->getItemsCollection() as $item) {
if ($item->getParentItemId()) {
continue;
}
if ($item->getProduct()->isVirtual()) {
$hasVirtual = true;
}
}
return $hasVirtual;
}
/**
* Merge quotes
*
* @param Quote $quote
* @return $this
*/
public function merge(Quote $quote)
{
$this->_eventManager->dispatch(
$this->_eventPrefix . '_merge_before',
[$this->_eventObject => $this, 'source' => $quote]
);
foreach ($quote->getAllVisibleItems() as $item) {
$found = false;
foreach ($this->getAllItems() as $quoteItem) {
if ($quoteItem->compare($item)) {
$quoteItem->setQty($quoteItem->getQty() + $item->getQty());
$this->itemProcessor->merge($item, $quoteItem);
$found = true;
break;
}
}
if (!$found) {
$newItem = clone $item;
$this->addItem($newItem);
if ($item->getHasChildren()) {
foreach ($item->getChildren() as $child) {
$newChild = clone $child;
$newChild->setParentItem($newItem);
$this->addItem($newChild);
}
}
}
}
/**
* Init shipping and billing address if quote is new
*/
if (!$this->getId()) {
$this->getShippingAddress();
$this->getBillingAddress();
}
if ($quote->getCouponCode()) {
$this->setCouponCode($quote->getCouponCode());
}
$this->_eventManager->dispatch(
$this->_eventPrefix . '_merge_after',
[$this->_eventObject => $this, 'source' => $quote]
);
return $this;
}
/**
* Trigger collect totals after loading, if required
*
* @return $this
*/
protected function _afterLoad()
{
// collect totals and save me, if required
if (1 == $this->getTriggerRecollect()) {
$this->collectTotals()
->setTriggerRecollect(0)
->save();
}
return parent::_afterLoad();
}
/**
* Checks if it was set
*
* @return bool
*/
public function addressCollectionWasSet()
{
return null !== $this->_addresses;
}
/**
* Checks if it was set
*
* @return bool
*/
public function itemsCollectionWasSet()
{
return null !== $this->_items;
}
/**
* Checks if it was set
*
* @return bool
*/
public function paymentsCollectionWasSet()
{
return null !== $this->_payments;
}
/**
* Checks if it was set
*
* @return bool
*/
public function currentPaymentWasSet()
{
return null !== $this->_currentPayment;
}
/**
* Return checkout method code
*
* @param boolean $originalMethod if true return defined method from beginning
* @return string
*/
public function getCheckoutMethod($originalMethod = false)
{
if ($this->getCustomerId() && !$originalMethod) {
return self::CHECKOUT_METHOD_LOGIN_IN;
}
return $this->_getData(self::KEY_CHECKOUT_METHOD);
}
/**
* Get quote items assigned to different quote addresses populated per item qty.
*
* @return array
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public function getShippingAddressesItems()
{
if ($this->shippingAddressesItems !== null) {
return $this->shippingAddressesItems;
}
$items = [];
$addresses = $this->getAllAddresses();
foreach ($addresses as $address) {
foreach ($address->getAllItems() as $item) {
if ($item->getParentItemId()) {
continue;
}
if ($item->getProduct()->getIsVirtual()) {
$items[] = $item;
continue;
}
if ($item->getQty() > 1) {
//DB table `quote_item` qty value can not be set to 1, if having more than 1 child references
//in table `quote_address_item`.
if ($item->getItemId() !== null
&& count($this->getQuoteShippingAddressItemsByQuoteItemId($item->getItemId())) > 1) {
continue;
}
for ($itemIndex = 0, $itemQty = $item->getQty(); $itemIndex < $itemQty; $itemIndex++) {
if ($itemIndex === 0) {
$addressItem = $item;
} else {
$addressItem = clone $item;
}
$addressItem->setQty(1)->setCustomerAddressId($address->getCustomerAddressId())->save();
$items[] = $addressItem;
}
} else {
$item->setCustomerAddressId($address->getCustomerAddressId());
$items[] = $item;
}
}
}
$this->shippingAddressesItems = $items;
return $items;
}
/**
* Sets the payment method that is used to process the cart.
*
* @codeCoverageIgnore
*
* @param string $checkoutMethod
* @return $this
*/
public function setCheckoutMethod($checkoutMethod)
{
return $this->setData(self::KEY_CHECKOUT_METHOD, $checkoutMethod);
}
/**
* Prevent quote from saving
*
* @codeCoverageIgnore
*
* @return $this
*/
public function preventSaving()
{
$this->_preventSaving = true;
return $this;
}
/**
* Check if model can be saved
*
* @codeCoverageIgnore
*
* @return bool
*/
public function isPreventSaving()
{
return $this->_preventSaving;
}
/**
* Check if there are more than one shipping address
*
* @return bool
*/
public function isMultipleShippingAddresses()
{
return \count($this->getAllShippingAddresses()) > 1;
}
/**
* Retrieve existing extension attributes object or create a new one.
*
* @return \Magento\Quote\Api\Data\CartExtensionInterface|null
*/
public function getExtensionAttributes()
{
return $this->_getExtensionAttributes();
}
/**
* Set an extension attributes object.
*
* @param \Magento\Quote\Api\Data\CartExtensionInterface $extensionAttributes
* @return $this
*/
public function setExtensionAttributes(\Magento\Quote\Api\Data\CartExtensionInterface $extensionAttributes)
{
return $this->_setExtensionAttributes($extensionAttributes);
}
/**
* Check is address allowed for store
*
* @param Address $address
* @param int|null $storeId
* @return bool
*/
private function isAddressAllowedForWebsite(Address $address, $storeId): bool
{
$allowedCountries = $this->allowedCountriesReader->getAllowedCountries(ScopeInterface::SCOPE_STORE, $storeId);
return in_array($address->getCountryId(), $allowedCountries);
}
/**
* Assign address to quote
*
* @param Address $address
* @param bool $isBillingAddress
* @return void
*/
private function assignAddress(Address $address, bool $isBillingAddress = true): void
{
if ($this->isAddressAllowedForWebsite($address, $this->getStoreId())) {
$isBillingAddress
? $this->setBillingAddress($address)
: $this->setShippingAddress($address);
}
}
/**
* Returns quote address items
*
* @param int $itemId
* @return array
*/
private function getQuoteShippingAddressItemsByQuoteItemId(int $itemId): array
{
$addressItems = [];
if ($this->isMultipleShippingAddresses()) {
$addresses = $this->getAllShippingAddresses();
foreach ($addresses as $address) {
foreach ($address->getAllItems() as $item) {
if ($item->getParentItemId() || $item->getProduct()->getIsVirtual()) {
continue;
}
if ((int)$item->getQuoteItemId() === $itemId) {
$addressItems[] = $item;
}
}
}
}
return $addressItems;
}
}
|