Quote.php 77 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Quote\Model;
  7. use Magento\Customer\Api\Data\CustomerInterface;
  8. use Magento\Customer\Api\Data\GroupInterface;
  9. use Magento\Framework\Api\AttributeValueFactory;
  10. use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface;
  11. use Magento\Framework\Model\AbstractExtensibleModel;
  12. use Magento\Quote\Api\Data\PaymentInterface;
  13. use Magento\Quote\Model\Quote\Address;
  14. use Magento\Quote\Model\Quote\Address\Total as AddressTotal;
  15. use Magento\Sales\Model\Status;
  16. use Magento\Framework\App\ObjectManager;
  17. /**
  18. * Quote model
  19. *
  20. * Supported events:
  21. * sales_quote_load_after
  22. * sales_quote_save_before
  23. * sales_quote_save_after
  24. * sales_quote_delete_before
  25. * sales_quote_delete_after
  26. *
  27. * @api
  28. * @method int getIsMultiShipping()
  29. * @method Quote setIsMultiShipping(int $value)
  30. * @method float getStoreToBaseRate()
  31. * @method Quote setStoreToBaseRate(float $value)
  32. * @method float getStoreToQuoteRate()
  33. * @method Quote setStoreToQuoteRate(float $value)
  34. * @method string getBaseCurrencyCode()
  35. * @method Quote setBaseCurrencyCode(string $value)
  36. * @method string getStoreCurrencyCode()
  37. * @method Quote setStoreCurrencyCode(string $value)
  38. * @method string getQuoteCurrencyCode()
  39. * @method Quote setQuoteCurrencyCode(string $value)
  40. * @method float getGrandTotal()
  41. * @method Quote setGrandTotal(float $value)
  42. * @method float getBaseGrandTotal()
  43. * @method Quote setBaseGrandTotal(float $value)
  44. * @method int getCustomerId()
  45. * @method Quote setCustomerId(int $value)
  46. * @method Quote setCustomerGroupId(int $value)
  47. * @method string getCustomerEmail()
  48. * @method Quote setCustomerEmail(string $value)
  49. * @method string getCustomerPrefix()
  50. * @method Quote setCustomerPrefix(string $value)
  51. * @method string getCustomerFirstname()
  52. * @method Quote setCustomerFirstname(string $value)
  53. * @method string getCustomerMiddlename()
  54. * @method Quote setCustomerMiddlename(string $value)
  55. * @method string getCustomerLastname()
  56. * @method Quote setCustomerLastname(string $value)
  57. * @method string getCustomerSuffix()
  58. * @method Quote setCustomerSuffix(string $value)
  59. * @method string getCustomerDob()
  60. * @method Quote setCustomerDob(string $value)
  61. * @method string getRemoteIp()
  62. * @method Quote setRemoteIp(string $value)
  63. * @method string getAppliedRuleIds()
  64. * @method Quote setAppliedRuleIds(string $value)
  65. * @method string getPasswordHash()
  66. * @method Quote setPasswordHash(string $value)
  67. * @method string getCouponCode()
  68. * @method Quote setCouponCode(string $value)
  69. * @method string getGlobalCurrencyCode()
  70. * @method Quote setGlobalCurrencyCode(string $value)
  71. * @method float getBaseToGlobalRate()
  72. * @method Quote setBaseToGlobalRate(float $value)
  73. * @method float getBaseToQuoteRate()
  74. * @method Quote setBaseToQuoteRate(float $value)
  75. * @method string getCustomerTaxvat()
  76. * @method Quote setCustomerTaxvat(string $value)
  77. * @method string getCustomerGender()
  78. * @method Quote setCustomerGender(string $value)
  79. * @method float getSubtotal()
  80. * @method Quote setSubtotal(float $value)
  81. * @method float getBaseSubtotal()
  82. * @method Quote setBaseSubtotal(float $value)
  83. * @method float getSubtotalWithDiscount()
  84. * @method Quote setSubtotalWithDiscount(float $value)
  85. * @method float getBaseSubtotalWithDiscount()
  86. * @method Quote setBaseSubtotalWithDiscount(float $value)
  87. * @method int getIsChanged()
  88. * @method Quote setIsChanged(int $value)
  89. * @method int getTriggerRecollect()
  90. * @method Quote setTriggerRecollect(int $value)
  91. * @method string getExtShippingInfo()
  92. * @method Quote setExtShippingInfo(string $value)
  93. * @method int getGiftMessageId()
  94. * @method Quote setGiftMessageId(int $value)
  95. * @method bool|null getIsPersistent()
  96. * @method Quote setIsPersistent(bool $value)
  97. * @method Quote setSharedStoreIds(array $values)
  98. * @method Quote setWebsite($value)
  99. * @SuppressWarnings(PHPMD.ExcessivePublicCount)
  100. * @SuppressWarnings(PHPMD.TooManyFields)
  101. * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
  102. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  103. * @since 100.0.2
  104. */
  105. class Quote extends AbstractExtensibleModel implements \Magento\Quote\Api\Data\CartInterface
  106. {
  107. /**
  108. * Checkout login method key
  109. */
  110. const CHECKOUT_METHOD_LOGIN_IN = 'login_in';
  111. /**
  112. * @var string
  113. */
  114. protected $_eventPrefix = 'sales_quote';
  115. /**
  116. * @var string
  117. */
  118. protected $_eventObject = 'quote';
  119. /**
  120. * Quote customer model object
  121. *
  122. * @var \Magento\Customer\Model\Customer
  123. */
  124. protected $_customer;
  125. /**
  126. * Quote addresses collection
  127. *
  128. * @var \Magento\Eav\Model\Entity\Collection\AbstractCollection
  129. */
  130. protected $_addresses;
  131. /**
  132. * Quote items collection
  133. *
  134. * @var \Magento\Eav\Model\Entity\Collection\AbstractCollection
  135. */
  136. protected $_items;
  137. /**
  138. * Quote payments
  139. *
  140. * @var \Magento\Eav\Model\Entity\Collection\AbstractCollection
  141. */
  142. protected $_payments;
  143. /**
  144. * @var \Magento\Quote\Model\Quote\Payment
  145. */
  146. protected $_currentPayment;
  147. /**
  148. * Different groups of error infos
  149. *
  150. * @var array
  151. */
  152. protected $_errorInfoGroups = [];
  153. /**
  154. * Whether quote should not be saved
  155. *
  156. * @var bool
  157. */
  158. protected $_preventSaving = false;
  159. /**
  160. * Catalog product
  161. *
  162. * @var \Magento\Catalog\Helper\Product
  163. */
  164. protected $_catalogProduct;
  165. /**
  166. * Quote validator
  167. *
  168. * @var \Magento\Quote\Model\QuoteValidator
  169. */
  170. protected $quoteValidator;
  171. /**
  172. * Core store config
  173. *
  174. * @var \Magento\Framework\App\Config\ScopeConfigInterface
  175. */
  176. protected $_scopeConfig;
  177. /**
  178. * @var \Magento\Store\Model\StoreManagerInterface
  179. */
  180. protected $_storeManager;
  181. /**
  182. * @var \Magento\Framework\App\Config\ScopeConfigInterface
  183. */
  184. protected $_config;
  185. /**
  186. * @var \Magento\Quote\Model\Quote\AddressFactory
  187. */
  188. protected $_quoteAddressFactory;
  189. /**
  190. * @var \Magento\Customer\Model\CustomerFactory
  191. */
  192. protected $_customerFactory;
  193. /**
  194. * Group repository
  195. *
  196. * @var \Magento\Customer\Api\GroupRepositoryInterface
  197. */
  198. protected $groupRepository;
  199. /**
  200. * @var \Magento\Quote\Model\ResourceModel\Quote\Item\CollectionFactory
  201. */
  202. protected $_quoteItemCollectionFactory;
  203. /**
  204. * @var \Magento\Quote\Model\Quote\ItemFactory
  205. */
  206. protected $_quoteItemFactory;
  207. /**
  208. * @var \Magento\Framework\Message\Factory
  209. */
  210. protected $messageFactory;
  211. /**
  212. * @var \Magento\Sales\Model\Status\ListFactory
  213. */
  214. protected $_statusListFactory;
  215. /**
  216. * @var \Magento\Catalog\Api\ProductRepositoryInterface
  217. */
  218. protected $productRepository;
  219. /**
  220. * @var \Magento\Quote\Model\Quote\PaymentFactory
  221. */
  222. protected $_quotePaymentFactory;
  223. /**
  224. * @var \Magento\Quote\Model\ResourceModel\Quote\Payment\CollectionFactory
  225. */
  226. protected $_quotePaymentCollectionFactory;
  227. /**
  228. * @var \Magento\Framework\DataObject\Copy
  229. */
  230. protected $_objectCopyService;
  231. /**
  232. * Address repository
  233. *
  234. * @var \Magento\Customer\Api\AddressRepositoryInterface
  235. */
  236. protected $addressRepository;
  237. /**
  238. * Search criteria builder
  239. *
  240. * @var \Magento\Framework\Api\SearchCriteriaBuilder
  241. */
  242. protected $searchCriteriaBuilder;
  243. /**
  244. * Filter builder
  245. *
  246. * @var \Magento\Framework\Api\FilterBuilder
  247. */
  248. protected $filterBuilder;
  249. /**
  250. * @var \Magento\CatalogInventory\Api\StockRegistryInterface
  251. */
  252. protected $stockRegistry;
  253. /**
  254. * @var \Magento\Quote\Model\Quote\Item\Processor
  255. */
  256. protected $itemProcessor;
  257. /**
  258. * @var \Magento\Framework\DataObject\Factory
  259. */
  260. protected $objectFactory;
  261. /**
  262. * @var \Magento\Framework\Api\ExtensibleDataObjectConverter
  263. */
  264. protected $extensibleDataObjectConverter;
  265. /**
  266. * @var \Magento\Customer\Api\Data\AddressInterfaceFactory
  267. */
  268. protected $addressDataFactory;
  269. /**
  270. * @var \Magento\Customer\Api\Data\CustomerInterfaceFactory
  271. */
  272. protected $customerDataFactory;
  273. /**
  274. * @var \Magento\Customer\Api\CustomerRepositoryInterface
  275. */
  276. protected $customerRepository;
  277. /**
  278. * @var Cart\CurrencyFactory
  279. */
  280. protected $currencyFactory;
  281. /**
  282. * @var \Magento\Framework\Api\DataObjectHelper
  283. */
  284. protected $dataObjectHelper;
  285. /**
  286. * @var JoinProcessorInterface
  287. */
  288. protected $extensionAttributesJoinProcessor;
  289. /**
  290. * @var \Magento\Quote\Model\Quote\TotalsCollector
  291. */
  292. protected $totalsCollector;
  293. /**
  294. * @var \Magento\Quote\Model\Quote\TotalsReader
  295. */
  296. protected $totalsReader;
  297. /**
  298. * @var \Magento\Quote\Model\ShippingFactory
  299. */
  300. protected $shippingFactory;
  301. /**
  302. * @var \Magento\Quote\Model\ShippingAssignmentFactory
  303. */
  304. protected $shippingAssignmentFactory;
  305. /**
  306. * Quote shipping addresses items cache
  307. *
  308. * @var array
  309. */
  310. protected $shippingAddressesItems;
  311. /**
  312. * @var \Magento\Sales\Model\OrderIncrementIdChecker
  313. */
  314. private $orderIncrementIdChecker;
  315. /**
  316. * @param \Magento\Framework\Model\Context $context
  317. * @param \Magento\Framework\Registry $registry
  318. * @param \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory
  319. * @param AttributeValueFactory $customAttributeFactory
  320. * @param QuoteValidator $quoteValidator
  321. * @param \Magento\Catalog\Helper\Product $catalogProduct
  322. * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
  323. * @param \Magento\Store\Model\StoreManagerInterface $storeManager
  324. * @param \Magento\Framework\App\Config\ScopeConfigInterface $config
  325. * @param Quote\AddressFactory $quoteAddressFactory
  326. * @param \Magento\Customer\Model\CustomerFactory $customerFactory
  327. * @param \Magento\Customer\Api\GroupRepositoryInterface $groupRepository
  328. * @param \Magento\Quote\Model\ResourceModel\Quote\Item\CollectionFactory $quoteItemCollectionFactory
  329. * @param Quote\ItemFactory $quoteItemFactory
  330. * @param \Magento\Framework\Message\Factory $messageFactory
  331. * @param Status\ListFactory $statusListFactory
  332. * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository
  333. * @param Quote\PaymentFactory $quotePaymentFactory
  334. * @param \Magento\Quote\Model\ResourceModel\Quote\Payment\CollectionFactory $quotePaymentCollectionFactory
  335. * @param \Magento\Framework\DataObject\Copy $objectCopyService
  336. * @param \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry
  337. * @param Quote\Item\Processor $itemProcessor
  338. * @param \Magento\Framework\DataObject\Factory $objectFactory
  339. * @param \Magento\Customer\Api\AddressRepositoryInterface $addressRepository
  340. * @param \Magento\Framework\Api\SearchCriteriaBuilder $criteriaBuilder
  341. * @param \Magento\Framework\Api\FilterBuilder $filterBuilder
  342. * @param \Magento\Customer\Api\Data\AddressInterfaceFactory $addressDataFactory
  343. * @param \Magento\Customer\Api\Data\CustomerInterfaceFactory $customerDataFactory
  344. * @param \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository
  345. * @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper
  346. * @param \Magento\Framework\Api\ExtensibleDataObjectConverter $extensibleDataObjectConverter
  347. * @param Cart\CurrencyFactory $currencyFactory
  348. * @param JoinProcessorInterface $extensionAttributesJoinProcessor
  349. * @param Quote\TotalsCollector $totalsCollector
  350. * @param Quote\TotalsReader $totalsReader
  351. * @param ShippingFactory $shippingFactory
  352. * @param ShippingAssignmentFactory $shippingAssignmentFactory
  353. * @param \Magento\Framework\Model\ResourceModel\AbstractResource|null $resource
  354. * @param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection
  355. * @param array $data
  356. * @param \Magento\Sales\Model\OrderIncrementIdChecker|null $orderIncrementIdChecker
  357. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  358. */
  359. public function __construct(
  360. \Magento\Framework\Model\Context $context,
  361. \Magento\Framework\Registry $registry,
  362. \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory,
  363. AttributeValueFactory $customAttributeFactory,
  364. \Magento\Quote\Model\QuoteValidator $quoteValidator,
  365. \Magento\Catalog\Helper\Product $catalogProduct,
  366. \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
  367. \Magento\Store\Model\StoreManagerInterface $storeManager,
  368. \Magento\Framework\App\Config\ScopeConfigInterface $config,
  369. \Magento\Quote\Model\Quote\AddressFactory $quoteAddressFactory,
  370. \Magento\Customer\Model\CustomerFactory $customerFactory,
  371. \Magento\Customer\Api\GroupRepositoryInterface $groupRepository,
  372. \Magento\Quote\Model\ResourceModel\Quote\Item\CollectionFactory $quoteItemCollectionFactory,
  373. \Magento\Quote\Model\Quote\ItemFactory $quoteItemFactory,
  374. \Magento\Framework\Message\Factory $messageFactory,
  375. \Magento\Sales\Model\Status\ListFactory $statusListFactory,
  376. \Magento\Catalog\Api\ProductRepositoryInterface $productRepository,
  377. \Magento\Quote\Model\Quote\PaymentFactory $quotePaymentFactory,
  378. \Magento\Quote\Model\ResourceModel\Quote\Payment\CollectionFactory $quotePaymentCollectionFactory,
  379. \Magento\Framework\DataObject\Copy $objectCopyService,
  380. \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry,
  381. \Magento\Quote\Model\Quote\Item\Processor $itemProcessor,
  382. \Magento\Framework\DataObject\Factory $objectFactory,
  383. \Magento\Customer\Api\AddressRepositoryInterface $addressRepository,
  384. \Magento\Framework\Api\SearchCriteriaBuilder $criteriaBuilder,
  385. \Magento\Framework\Api\FilterBuilder $filterBuilder,
  386. \Magento\Customer\Api\Data\AddressInterfaceFactory $addressDataFactory,
  387. \Magento\Customer\Api\Data\CustomerInterfaceFactory $customerDataFactory,
  388. \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository,
  389. \Magento\Framework\Api\DataObjectHelper $dataObjectHelper,
  390. \Magento\Framework\Api\ExtensibleDataObjectConverter $extensibleDataObjectConverter,
  391. \Magento\Quote\Model\Cart\CurrencyFactory $currencyFactory,
  392. JoinProcessorInterface $extensionAttributesJoinProcessor,
  393. Quote\TotalsCollector $totalsCollector,
  394. Quote\TotalsReader $totalsReader,
  395. \Magento\Quote\Model\ShippingFactory $shippingFactory,
  396. \Magento\Quote\Model\ShippingAssignmentFactory $shippingAssignmentFactory,
  397. \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
  398. \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
  399. array $data = [],
  400. \Magento\Sales\Model\OrderIncrementIdChecker $orderIncrementIdChecker = null
  401. ) {
  402. $this->quoteValidator = $quoteValidator;
  403. $this->_catalogProduct = $catalogProduct;
  404. $this->_scopeConfig = $scopeConfig;
  405. $this->_storeManager = $storeManager;
  406. $this->_config = $config;
  407. $this->_quoteAddressFactory = $quoteAddressFactory;
  408. $this->_customerFactory = $customerFactory;
  409. $this->groupRepository = $groupRepository;
  410. $this->_quoteItemCollectionFactory = $quoteItemCollectionFactory;
  411. $this->_quoteItemFactory = $quoteItemFactory;
  412. $this->messageFactory = $messageFactory;
  413. $this->_statusListFactory = $statusListFactory;
  414. $this->productRepository = $productRepository;
  415. $this->_quotePaymentFactory = $quotePaymentFactory;
  416. $this->_quotePaymentCollectionFactory = $quotePaymentCollectionFactory;
  417. $this->_objectCopyService = $objectCopyService;
  418. $this->addressRepository = $addressRepository;
  419. $this->searchCriteriaBuilder = $criteriaBuilder;
  420. $this->filterBuilder = $filterBuilder;
  421. $this->stockRegistry = $stockRegistry;
  422. $this->itemProcessor = $itemProcessor;
  423. $this->objectFactory = $objectFactory;
  424. $this->addressDataFactory = $addressDataFactory;
  425. $this->customerDataFactory = $customerDataFactory;
  426. $this->customerRepository = $customerRepository;
  427. $this->dataObjectHelper = $dataObjectHelper;
  428. $this->extensibleDataObjectConverter = $extensibleDataObjectConverter;
  429. $this->currencyFactory = $currencyFactory;
  430. $this->extensionAttributesJoinProcessor = $extensionAttributesJoinProcessor;
  431. $this->totalsCollector = $totalsCollector;
  432. $this->totalsReader = $totalsReader;
  433. $this->shippingFactory = $shippingFactory;
  434. $this->shippingAssignmentFactory = $shippingAssignmentFactory;
  435. $this->orderIncrementIdChecker = $orderIncrementIdChecker ?: ObjectManager::getInstance()
  436. ->get(\Magento\Sales\Model\OrderIncrementIdChecker::class);
  437. parent::__construct(
  438. $context,
  439. $registry,
  440. $extensionFactory,
  441. $customAttributeFactory,
  442. $resource,
  443. $resourceCollection,
  444. $data
  445. );
  446. }
  447. /**
  448. * Init resource model
  449. *
  450. * @return void
  451. */
  452. protected function _construct()
  453. {
  454. $this->_init(\Magento\Quote\Model\ResourceModel\Quote::class);
  455. }
  456. /**
  457. * Returns information about quote currency, such as code, exchange rate, and so on.
  458. *
  459. * @return \Magento\Quote\Api\Data\CurrencyInterface|null Quote currency information. Otherwise, null.
  460. * @codeCoverageIgnoreStart
  461. */
  462. public function getCurrency()
  463. {
  464. $currency = $this->getData(self::KEY_CURRENCY);
  465. if (!$currency) {
  466. $currency = $this->currencyFactory->create()
  467. ->setGlobalCurrencyCode($this->getGlobalCurrencyCode())
  468. ->setBaseCurrencyCode($this->getBaseCurrencyCode())
  469. ->setStoreCurrencyCode($this->getStoreCurrencyCode())
  470. ->setQuoteCurrencyCode($this->getQuoteCurrencyCode())
  471. ->setStoreToBaseRate($this->getStoreToBaseRate())
  472. ->setStoreToQuoteRate($this->getStoreToQuoteRate())
  473. ->setBaseToGlobalRate($this->getBaseToGlobalRate())
  474. ->setBaseToQuoteRate($this->getBaseToQuoteRate());
  475. $this->setData(self::KEY_CURRENCY, $currency);
  476. }
  477. return $currency;
  478. }
  479. /**
  480. * @inheritdoc
  481. */
  482. public function setCurrency(\Magento\Quote\Api\Data\CurrencyInterface $currency = null)
  483. {
  484. return $this->setData(self::KEY_CURRENCY, $currency);
  485. }
  486. /**
  487. * @inheritdoc
  488. */
  489. public function getItems()
  490. {
  491. return $this->_getData(self::KEY_ITEMS);
  492. }
  493. /**
  494. * @inheritdoc
  495. */
  496. public function setItems(array $items = null)
  497. {
  498. return $this->setData(self::KEY_ITEMS, $items);
  499. }
  500. /**
  501. * @inheritdoc
  502. */
  503. public function getCreatedAt()
  504. {
  505. return $this->_getData(self::KEY_CREATED_AT);
  506. }
  507. /**
  508. * @inheritdoc
  509. */
  510. public function setCreatedAt($createdAt)
  511. {
  512. return $this->setData(self::KEY_CREATED_AT, $createdAt);
  513. }
  514. /**
  515. * @inheritdoc
  516. */
  517. public function getUpdatedAt()
  518. {
  519. return $this->_getData(self::KEY_UPDATED_AT);
  520. }
  521. /**
  522. * @inheritdoc
  523. */
  524. public function setUpdatedAt($updatedAt)
  525. {
  526. return $this->setData(self::KEY_UPDATED_AT, $updatedAt);
  527. }
  528. /**
  529. * @inheritdoc
  530. */
  531. public function getConvertedAt()
  532. {
  533. return $this->_getData(self::KEY_CONVERTED_AT);
  534. }
  535. /**
  536. * @inheritdoc
  537. */
  538. public function setConvertedAt($convertedAt)
  539. {
  540. return $this->setData(self::KEY_CONVERTED_AT, $convertedAt);
  541. }
  542. /**
  543. * @inheritdoc
  544. */
  545. public function getIsActive()
  546. {
  547. return $this->_getData(self::KEY_IS_ACTIVE);
  548. }
  549. /**
  550. * @inheritdoc
  551. */
  552. public function setIsActive($isActive)
  553. {
  554. return $this->setData(self::KEY_IS_ACTIVE, $isActive);
  555. }
  556. /**
  557. * @inheritdoc
  558. */
  559. public function setIsVirtual($isVirtual)
  560. {
  561. return $this->setData(self::KEY_IS_VIRTUAL, $isVirtual);
  562. }
  563. /**
  564. * @inheritdoc
  565. */
  566. public function getItemsCount()
  567. {
  568. return $this->_getData(self::KEY_ITEMS_COUNT);
  569. }
  570. /**
  571. * @inheritdoc
  572. */
  573. public function setItemsCount($itemsCount)
  574. {
  575. return $this->setData(self::KEY_ITEMS_COUNT, $itemsCount);
  576. }
  577. /**
  578. * @inheritdoc
  579. */
  580. public function getItemsQty()
  581. {
  582. return $this->_getData(self::KEY_ITEMS_QTY);
  583. }
  584. /**
  585. * @inheritdoc
  586. */
  587. public function setItemsQty($itemsQty)
  588. {
  589. return $this->setData(self::KEY_ITEMS_QTY, $itemsQty);
  590. }
  591. /**
  592. * @inheritdoc
  593. */
  594. public function getOrigOrderId()
  595. {
  596. return $this->_getData(self::KEY_ORIG_ORDER_ID);
  597. }
  598. /**
  599. * @inheritdoc
  600. */
  601. public function setOrigOrderId($origOrderId)
  602. {
  603. return $this->setData(self::KEY_ORIG_ORDER_ID, $origOrderId);
  604. }
  605. /**
  606. * @inheritdoc
  607. */
  608. public function getReservedOrderId()
  609. {
  610. return $this->_getData(self::KEY_RESERVED_ORDER_ID);
  611. }
  612. /**
  613. * @inheritdoc
  614. */
  615. public function setReservedOrderId($reservedOrderId)
  616. {
  617. return $this->setData(self::KEY_RESERVED_ORDER_ID, $reservedOrderId);
  618. }
  619. /**
  620. * @inheritdoc
  621. */
  622. public function getCustomerIsGuest()
  623. {
  624. return $this->_getData(self::KEY_CUSTOMER_IS_GUEST);
  625. }
  626. /**
  627. * @inheritdoc
  628. */
  629. public function setCustomerIsGuest($customerIsGuest)
  630. {
  631. return $this->setData(self::KEY_CUSTOMER_IS_GUEST, $customerIsGuest);
  632. }
  633. /**
  634. * @inheritdoc
  635. */
  636. public function getCustomerNote()
  637. {
  638. return $this->_getData(self::KEY_CUSTOMER_NOTE);
  639. }
  640. /**
  641. * @inheritdoc
  642. */
  643. public function setCustomerNote($customerNote)
  644. {
  645. return $this->setData(self::KEY_CUSTOMER_NOTE, $customerNote);
  646. }
  647. /**
  648. * @inheritdoc
  649. */
  650. public function getCustomerNoteNotify()
  651. {
  652. return $this->_getData(self::KEY_CUSTOMER_NOTE_NOTIFY);
  653. }
  654. /**
  655. * @inheritdoc
  656. */
  657. public function setCustomerNoteNotify($customerNoteNotify)
  658. {
  659. return $this->setData(self::KEY_CUSTOMER_NOTE_NOTIFY, $customerNoteNotify);
  660. }
  661. //@codeCoverageIgnoreEnd
  662. /**
  663. * @inheritdoc
  664. */
  665. public function getStoreId()
  666. {
  667. if (!$this->hasStoreId()) {
  668. return $this->_storeManager->getStore()->getId();
  669. }
  670. return (int)$this->_getData(self::KEY_STORE_ID);
  671. }
  672. /**
  673. * @inheritdoc
  674. */
  675. public function setStoreId($storeId)
  676. {
  677. $this->setData(self::KEY_STORE_ID, (int)$storeId);
  678. return $this;
  679. }
  680. /**
  681. * Get quote store model object
  682. *
  683. * @return \Magento\Store\Model\Store
  684. */
  685. public function getStore()
  686. {
  687. return $this->_storeManager->getStore($this->getStoreId());
  688. }
  689. /**
  690. * Declare quote store model
  691. *
  692. * @param \Magento\Store\Model\Store $store
  693. * @return $this
  694. */
  695. public function setStore(\Magento\Store\Model\Store $store)
  696. {
  697. $this->setStoreId($store->getId());
  698. return $this;
  699. }
  700. /**
  701. * Get all available store ids for quote
  702. *
  703. * @return array
  704. */
  705. public function getSharedStoreIds()
  706. {
  707. $ids = $this->_getData('shared_store_ids');
  708. if ($ids === null || !is_array($ids)) {
  709. $website = $this->getWebsite();
  710. if ($website) {
  711. return $website->getStoreIds();
  712. }
  713. return $this->getStore()->getWebsite()->getStoreIds();
  714. }
  715. return $ids;
  716. }
  717. /**
  718. * Prepare data before save
  719. *
  720. * @return $this
  721. */
  722. public function beforeSave()
  723. {
  724. /**
  725. * Currency logic
  726. *
  727. * global - currency which is set for default in backend
  728. * base - currency which is set for current website. all attributes that
  729. * have 'base_' prefix saved in this currency
  730. * quote/order - currency which was selected by customer or configured by
  731. * admin for current store. currency in which customer sees
  732. * price thought all checkout.
  733. *
  734. * Rates:
  735. * base_to_global & base_to_quote/base_to_order
  736. */
  737. $globalCurrencyCode = $this->_config->getValue(
  738. \Magento\Directory\Model\Currency::XML_PATH_CURRENCY_BASE,
  739. 'default'
  740. );
  741. $baseCurrency = $this->getStore()->getBaseCurrency();
  742. if ($this->hasForcedCurrency()) {
  743. $quoteCurrency = $this->getForcedCurrency();
  744. } else {
  745. $quoteCurrency = $this->getStore()->getCurrentCurrency();
  746. }
  747. $this->setGlobalCurrencyCode($globalCurrencyCode);
  748. $this->setBaseCurrencyCode($baseCurrency->getCode());
  749. $this->setStoreCurrencyCode($baseCurrency->getCode());
  750. $this->setQuoteCurrencyCode($quoteCurrency->getCode());
  751. $this->setBaseToGlobalRate($baseCurrency->getRate($globalCurrencyCode));
  752. $this->setBaseToQuoteRate($baseCurrency->getRate($quoteCurrency));
  753. if (!$this->hasChangedFlag() || $this->getChangedFlag() == true) {
  754. $this->setIsChanged(1);
  755. } else {
  756. $this->setIsChanged(0);
  757. }
  758. if ($this->_customer) {
  759. $this->setCustomerId($this->_customer->getId());
  760. }
  761. //mark quote if it has virtual products only
  762. $this->setIsVirtual($this->getIsVirtual());
  763. if ($this->hasDataChanges()) {
  764. $this->setUpdatedAt(null);
  765. }
  766. parent::beforeSave();
  767. }
  768. /**
  769. * Loading quote data by customer
  770. *
  771. * @param \Magento\Customer\Model\Customer|int $customer
  772. * @deprecated 101.0.0
  773. * @return $this
  774. */
  775. public function loadByCustomer($customer)
  776. {
  777. /* @TODO: remove this if after external usages of loadByCustomerId are refactored in MAGETWO-19935 */
  778. if ($customer instanceof \Magento\Customer\Model\Customer || $customer instanceof CustomerInterface) {
  779. $customerId = $customer->getId();
  780. } else {
  781. $customerId = (int)$customer;
  782. }
  783. $this->_getResource()->loadByCustomerId($this, $customerId);
  784. $this->_afterLoad();
  785. return $this;
  786. }
  787. /**
  788. * Loading only active quote
  789. *
  790. * @param int $quoteId
  791. * @return $this
  792. */
  793. public function loadActive($quoteId)
  794. {
  795. $this->_getResource()->loadActive($this, $quoteId);
  796. $this->_afterLoad();
  797. return $this;
  798. }
  799. /**
  800. * Loading quote by identifier
  801. *
  802. * @param int $quoteId
  803. * @return $this
  804. */
  805. public function loadByIdWithoutStore($quoteId)
  806. {
  807. $this->_getResource()->loadByIdWithoutStore($this, $quoteId);
  808. $this->_afterLoad();
  809. return $this;
  810. }
  811. /**
  812. * Assign customer model object data to quote
  813. *
  814. * @param \Magento\Customer\Api\Data\CustomerInterface $customer
  815. * @return $this
  816. */
  817. public function assignCustomer(\Magento\Customer\Api\Data\CustomerInterface $customer)
  818. {
  819. return $this->assignCustomerWithAddressChange($customer);
  820. }
  821. /**
  822. * Assign customer model to quote with billing and shipping address change
  823. *
  824. * @param \Magento\Customer\Api\Data\CustomerInterface $customer
  825. * @param Address $billingAddress Quote billing address
  826. * @param Address $shippingAddress Quote shipping address
  827. * @return $this
  828. */
  829. public function assignCustomerWithAddressChange(
  830. \Magento\Customer\Api\Data\CustomerInterface $customer,
  831. Address $billingAddress = null,
  832. Address $shippingAddress = null
  833. ) {
  834. if ($customer->getId()) {
  835. $this->setCustomer($customer);
  836. if (null !== $billingAddress) {
  837. $this->setBillingAddress($billingAddress);
  838. } else {
  839. try {
  840. $defaultBillingAddress = $this->addressRepository->getById($customer->getDefaultBilling());
  841. } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
  842. //
  843. }
  844. if (isset($defaultBillingAddress)) {
  845. /** @var \Magento\Quote\Model\Quote\Address $billingAddress */
  846. $billingAddress = $this->_quoteAddressFactory->create();
  847. $billingAddress->importCustomerAddressData($defaultBillingAddress);
  848. $this->setBillingAddress($billingAddress);
  849. }
  850. }
  851. if (null === $shippingAddress) {
  852. try {
  853. $defaultShippingAddress = $this->addressRepository->getById($customer->getDefaultShipping());
  854. } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
  855. //
  856. }
  857. if (isset($defaultShippingAddress)) {
  858. /** @var \Magento\Quote\Model\Quote\Address $shippingAddress */
  859. $shippingAddress = $this->_quoteAddressFactory->create();
  860. $shippingAddress->importCustomerAddressData($defaultShippingAddress);
  861. } else {
  862. $shippingAddress = $this->_quoteAddressFactory->create();
  863. }
  864. }
  865. $this->setShippingAddress($shippingAddress);
  866. }
  867. return $this;
  868. }
  869. /**
  870. * Define customer object
  871. *
  872. * @param \Magento\Customer\Api\Data\CustomerInterface $customer
  873. * @return $this
  874. */
  875. public function setCustomer(\Magento\Customer\Api\Data\CustomerInterface $customer = null)
  876. {
  877. /* @TODO: Remove the method after all external usages are refactored in MAGETWO-19930 */
  878. $this->_customer = $customer;
  879. $this->setCustomerId($customer->getId());
  880. $origAddresses = $customer->getAddresses();
  881. $customer->setAddresses([]);
  882. $customerDataFlatArray = $this->objectFactory->create(
  883. $this->extensibleDataObjectConverter->toFlatArray(
  884. $customer,
  885. [],
  886. \Magento\Customer\Api\Data\CustomerInterface::class
  887. )
  888. );
  889. $customer->setAddresses($origAddresses);
  890. $this->_objectCopyService->copyFieldsetToTarget('customer_account', 'to_quote', $customerDataFlatArray, $this);
  891. return $this;
  892. }
  893. /**
  894. * Retrieve customer model object
  895. *
  896. * @return \Magento\Customer\Api\Data\CustomerInterface|\Magento\Framework\Api\ExtensibleDataInterface
  897. */
  898. public function getCustomer()
  899. {
  900. /**
  901. * @TODO: Remove the method after all external usages are refactored in MAGETWO-19930
  902. * _customer and _customerFactory variables should be eliminated as well
  903. */
  904. if (null === $this->_customer) {
  905. try {
  906. $this->_customer = $this->customerRepository->getById($this->getCustomerId());
  907. } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
  908. $this->_customer = $this->customerDataFactory->create();
  909. $this->_customer->setId(null);
  910. }
  911. }
  912. return $this->_customer;
  913. }
  914. /**
  915. * Substitute customer addresses
  916. *
  917. * @param \Magento\Customer\Api\Data\AddressInterface[] $addresses
  918. * @return $this
  919. */
  920. public function setCustomerAddressData(array $addresses)
  921. {
  922. foreach ($addresses as $address) {
  923. if (!$address->getId()) {
  924. $this->addCustomerAddress($address);
  925. }
  926. }
  927. return $this;
  928. }
  929. /**
  930. * Add address to the customer, created out of a Data Object
  931. *
  932. * TODO refactor in scope of MAGETWO-19930
  933. *
  934. * @param \Magento\Customer\Api\Data\AddressInterface $address
  935. * @return $this
  936. */
  937. public function addCustomerAddress(\Magento\Customer\Api\Data\AddressInterface $address)
  938. {
  939. $addresses = (array)$this->getCustomer()->getAddresses();
  940. $addresses[] = $address;
  941. $this->getCustomer()->setAddresses($addresses);
  942. $this->updateCustomerData($this->getCustomer());
  943. return $this;
  944. }
  945. /**
  946. * Update customer data object
  947. *
  948. * @param \Magento\Customer\Api\Data\CustomerInterface $customer
  949. * @return $this
  950. */
  951. public function updateCustomerData(\Magento\Customer\Api\Data\CustomerInterface $customer)
  952. {
  953. $quoteCustomer = $this->getCustomer();
  954. $this->dataObjectHelper->mergeDataObjects(CustomerInterface::class, $quoteCustomer, $customer);
  955. $this->setCustomer($quoteCustomer);
  956. return $this;
  957. }
  958. /**
  959. * Retrieve customer group id
  960. *
  961. * @return int
  962. */
  963. public function getCustomerGroupId()
  964. {
  965. if ($this->hasData('customer_group_id')) {
  966. return $this->getData('customer_group_id');
  967. } elseif ($this->getCustomerId()) {
  968. return $this->getCustomer()->getGroupId();
  969. } else {
  970. return GroupInterface::NOT_LOGGED_IN_ID;
  971. }
  972. }
  973. /**
  974. * @inheritdoc
  975. */
  976. public function getCustomerTaxClassId()
  977. {
  978. /**
  979. * tax class can vary at any time. so instead of using the value from session,
  980. * we need to retrieve from db every time to get the correct tax class
  981. */
  982. //if (!$this->getData('customer_group_id') && !$this->getData('customer_tax_class_id')) {
  983. $groupId = $this->getCustomerGroupId();
  984. if ($groupId !== null) {
  985. $taxClassId = $this->groupRepository->getById($this->getCustomerGroupId())->getTaxClassId();
  986. $this->setCustomerTaxClassId($taxClassId);
  987. }
  988. return $this->getData(self::KEY_CUSTOMER_TAX_CLASS_ID);
  989. }
  990. /**
  991. * @inheritdoc
  992. */
  993. public function setCustomerTaxClassId($customerTaxClassId)
  994. {
  995. return $this->setData(self::KEY_CUSTOMER_TAX_CLASS_ID, $customerTaxClassId);
  996. }
  997. /**
  998. * Retrieve quote address collection
  999. *
  1000. * @return \Magento\Eav\Model\Entity\Collection\AbstractCollection
  1001. */
  1002. public function getAddressesCollection()
  1003. {
  1004. if (null === $this->_addresses) {
  1005. $this->_addresses = $this->_quoteAddressFactory->create()->getCollection()->setQuoteFilter($this->getId());
  1006. if ($this->getId()) {
  1007. foreach ($this->_addresses as $address) {
  1008. $address->setQuote($this);
  1009. }
  1010. }
  1011. }
  1012. return $this->_addresses;
  1013. }
  1014. /**
  1015. * Retrieve quote address by type
  1016. *
  1017. * @param string $type
  1018. * @return Address
  1019. */
  1020. protected function _getAddressByType($type)
  1021. {
  1022. foreach ($this->getAddressesCollection() as $address) {
  1023. if ($address->getAddressType() == $type && !$address->isDeleted()) {
  1024. return $address;
  1025. }
  1026. }
  1027. $address = $this->_quoteAddressFactory->create()->setAddressType($type);
  1028. $this->addAddress($address);
  1029. return $address;
  1030. }
  1031. /**
  1032. * Retrieve quote billing address
  1033. *
  1034. * @return Address
  1035. */
  1036. public function getBillingAddress()
  1037. {
  1038. return $this->_getAddressByType(Address::TYPE_BILLING);
  1039. }
  1040. /**
  1041. * Retrieve quote shipping address
  1042. *
  1043. * @return Address
  1044. */
  1045. public function getShippingAddress()
  1046. {
  1047. return $this->_getAddressByType(Address::TYPE_SHIPPING);
  1048. }
  1049. /**
  1050. * Get all shipping addresses.
  1051. *
  1052. * @return array
  1053. */
  1054. public function getAllShippingAddresses()
  1055. {
  1056. $addresses = [];
  1057. foreach ($this->getAddressesCollection() as $address) {
  1058. if ($address->getAddressType() == Address::TYPE_SHIPPING && !$address->isDeleted()) {
  1059. $addresses[] = $address;
  1060. }
  1061. }
  1062. return $addresses;
  1063. }
  1064. /**
  1065. * Get all quote addresses
  1066. *
  1067. * @return \Magento\Quote\Model\Quote\Address[]
  1068. */
  1069. public function getAllAddresses()
  1070. {
  1071. $addresses = [];
  1072. foreach ($this->getAddressesCollection() as $address) {
  1073. if (!$address->isDeleted()) {
  1074. $addresses[] = $address;
  1075. }
  1076. }
  1077. return $addresses;
  1078. }
  1079. /**
  1080. * Get address by id.
  1081. *
  1082. * @param int $addressId
  1083. * @return Address|false
  1084. */
  1085. public function getAddressById($addressId)
  1086. {
  1087. foreach ($this->getAddressesCollection() as $address) {
  1088. if ($address->getId() == $addressId) {
  1089. return $address;
  1090. }
  1091. }
  1092. return false;
  1093. }
  1094. /**
  1095. * Get address by customer address id.
  1096. *
  1097. * @param int|string $addressId
  1098. * @return Address|false
  1099. */
  1100. public function getAddressByCustomerAddressId($addressId)
  1101. {
  1102. foreach ($this->getAddressesCollection() as $address) {
  1103. if (!$address->isDeleted() && $address->getCustomerAddressId() == $addressId) {
  1104. return $address;
  1105. }
  1106. }
  1107. return false;
  1108. }
  1109. /**
  1110. * Get quote address by customer address ID.
  1111. *
  1112. * @param int|string $addressId
  1113. * @return Address|false
  1114. */
  1115. public function getShippingAddressByCustomerAddressId($addressId)
  1116. {
  1117. /** @var \Magento\Quote\Model\Quote\Address $address */
  1118. foreach ($this->getAddressesCollection() as $address) {
  1119. if (!$address->isDeleted() &&
  1120. $address->getAddressType() == Address::TYPE_SHIPPING &&
  1121. $address->getCustomerAddressId() == $addressId
  1122. ) {
  1123. return $address;
  1124. }
  1125. }
  1126. return false;
  1127. }
  1128. /**
  1129. * Remove address.
  1130. *
  1131. * @param int|string $addressId
  1132. * @return $this
  1133. */
  1134. public function removeAddress($addressId)
  1135. {
  1136. foreach ($this->getAddressesCollection() as $address) {
  1137. if ($address->getId() == $addressId) {
  1138. $address->isDeleted(true);
  1139. break;
  1140. }
  1141. }
  1142. return $this;
  1143. }
  1144. /**
  1145. * Leave no more than one billing and one shipping address, fill them with default data
  1146. *
  1147. * @return $this
  1148. */
  1149. public function removeAllAddresses()
  1150. {
  1151. $addressByType = [];
  1152. $addressesCollection = $this->getAddressesCollection();
  1153. // mark all addresses as deleted
  1154. foreach ($addressesCollection as $address) {
  1155. $type = $address->getAddressType();
  1156. if (!isset($addressByType[$type]) || $addressByType[$type]->getId() > $address->getId()) {
  1157. $addressByType[$type] = $address;
  1158. }
  1159. $address->isDeleted(true);
  1160. }
  1161. // create new billing and shipping addresses filled with default values, set this data to existing records
  1162. foreach ($addressByType as $type => $address) {
  1163. $id = $address->getId();
  1164. $emptyAddress = $this->_getAddressByType($type);
  1165. $address->setData($emptyAddress->getData())->setId($id)->isDeleted(false);
  1166. $emptyAddress->setDeleteImmediately(true);
  1167. }
  1168. // remove newly created billing and shipping addresses from collection to avoid senseless delete queries
  1169. foreach ($addressesCollection as $key => $item) {
  1170. if ($item->getDeleteImmediately()) {
  1171. $addressesCollection->removeItemByKey($key);
  1172. }
  1173. }
  1174. return $this;
  1175. }
  1176. /**
  1177. * Add address.
  1178. *
  1179. * @param \Magento\Quote\Api\Data\AddressInterface $address
  1180. * @return $this
  1181. */
  1182. public function addAddress(\Magento\Quote\Api\Data\AddressInterface $address)
  1183. {
  1184. $address->setQuote($this);
  1185. if (!$address->getId()) {
  1186. $this->getAddressesCollection()->addItem($address);
  1187. }
  1188. return $this;
  1189. }
  1190. /**
  1191. * Set billing address.
  1192. *
  1193. * @param \Magento\Quote\Api\Data\AddressInterface $address
  1194. * @return $this
  1195. */
  1196. public function setBillingAddress(\Magento\Quote\Api\Data\AddressInterface $address = null)
  1197. {
  1198. $old = $this->getAddressesCollection()->getItemById($address->getId())
  1199. ?? $this->getBillingAddress();
  1200. if (!empty($old)) {
  1201. $old->addData($address->getData());
  1202. } else {
  1203. $this->addAddress($address->setAddressType(Address::TYPE_BILLING));
  1204. }
  1205. return $this;
  1206. }
  1207. /**
  1208. * Set shipping address
  1209. *
  1210. * @param \Magento\Quote\Api\Data\AddressInterface $address
  1211. * @return $this
  1212. */
  1213. public function setShippingAddress(\Magento\Quote\Api\Data\AddressInterface $address = null)
  1214. {
  1215. if ($this->getIsMultiShipping()) {
  1216. $this->addAddress($address->setAddressType(Address::TYPE_SHIPPING));
  1217. } else {
  1218. $old = $this->getAddressesCollection()->getItemById($address->getId())
  1219. ?? $this->getShippingAddress();
  1220. if (!empty($old)) {
  1221. $old->addData($address->getData());
  1222. } else {
  1223. $this->addAddress($address->setAddressType(Address::TYPE_SHIPPING));
  1224. }
  1225. }
  1226. return $this;
  1227. }
  1228. /**
  1229. * Add shipping address.
  1230. *
  1231. * @param \Magento\Quote\Api\Data\AddressInterface $address
  1232. * @return $this
  1233. */
  1234. public function addShippingAddress(\Magento\Quote\Api\Data\AddressInterface $address)
  1235. {
  1236. $this->setShippingAddress($address);
  1237. return $this;
  1238. }
  1239. /**
  1240. * Retrieve quote items collection
  1241. *
  1242. * @param bool $useCache
  1243. * @return \Magento\Eav\Model\Entity\Collection\AbstractCollection
  1244. */
  1245. public function getItemsCollection($useCache = true)
  1246. {
  1247. if ($this->hasItemsCollection() && $useCache) {
  1248. return $this->getData('items_collection');
  1249. }
  1250. if (null === $this->_items || !$useCache) {
  1251. $this->_items = $this->_quoteItemCollectionFactory->create();
  1252. $this->extensionAttributesJoinProcessor->process($this->_items);
  1253. $this->_items->setQuote($this);
  1254. }
  1255. return $this->_items;
  1256. }
  1257. /**
  1258. * Retrieve quote items array
  1259. *
  1260. * @return array
  1261. */
  1262. public function getAllItems()
  1263. {
  1264. $items = [];
  1265. foreach ($this->getItemsCollection() as $item) {
  1266. /** @var \Magento\Quote\Model\Quote\Item $item */
  1267. if (!$item->isDeleted()) {
  1268. $items[] = $item;
  1269. }
  1270. }
  1271. return $items;
  1272. }
  1273. /**
  1274. * Get array of all items what can be display directly
  1275. *
  1276. * @return \Magento\Quote\Model\Quote\Item[]
  1277. */
  1278. public function getAllVisibleItems()
  1279. {
  1280. $items = [];
  1281. foreach ($this->getItemsCollection() as $item) {
  1282. if (!$item->isDeleted() && !$item->getParentItemId() && !$item->getParentItem()) {
  1283. $items[] = $item;
  1284. }
  1285. }
  1286. return $items;
  1287. }
  1288. /**
  1289. * Checking items availability
  1290. *
  1291. * @return bool
  1292. */
  1293. public function hasItems()
  1294. {
  1295. return sizeof($this->getAllItems()) > 0;
  1296. }
  1297. /**
  1298. * Checking availability of items with decimal qty
  1299. *
  1300. * @return bool
  1301. */
  1302. public function hasItemsWithDecimalQty()
  1303. {
  1304. foreach ($this->getAllItems() as $item) {
  1305. $stockItemDo = $this->stockRegistry->getStockItem(
  1306. $item->getProduct()->getId(),
  1307. $item->getStore()->getWebsiteId()
  1308. );
  1309. if ($stockItemDo->getItemId() && $stockItemDo->getIsQtyDecimal()) {
  1310. return true;
  1311. }
  1312. }
  1313. return false;
  1314. }
  1315. /**
  1316. * Checking product exist in Quote
  1317. *
  1318. * @param int $productId
  1319. * @return bool
  1320. */
  1321. public function hasProductId($productId)
  1322. {
  1323. foreach ($this->getAllItems() as $item) {
  1324. if ($item->getProductId() == $productId) {
  1325. return true;
  1326. }
  1327. }
  1328. return false;
  1329. }
  1330. /**
  1331. * Retrieve item model object by item identifier
  1332. *
  1333. * @param int $itemId
  1334. * @return \Magento\Quote\Model\Quote\Item|false
  1335. */
  1336. public function getItemById($itemId)
  1337. {
  1338. foreach ($this->getItemsCollection() as $item) {
  1339. if ($item->getId() == $itemId) {
  1340. return $item;
  1341. }
  1342. }
  1343. return false;
  1344. }
  1345. /**
  1346. * Delete quote item. If it does not have identifier then it will be only removed from collection
  1347. *
  1348. * @param \Magento\Quote\Model\Quote\Item $item
  1349. * @return $this
  1350. */
  1351. public function deleteItem(\Magento\Quote\Model\Quote\Item $item)
  1352. {
  1353. if ($item->getId()) {
  1354. $this->removeItem($item->getId());
  1355. } else {
  1356. $quoteItems = $this->getItemsCollection();
  1357. $items = [$item];
  1358. if ($item->getHasChildren()) {
  1359. foreach ($item->getChildren() as $child) {
  1360. $items[] = $child;
  1361. }
  1362. }
  1363. foreach ($quoteItems as $key => $quoteItem) {
  1364. foreach ($items as $item) {
  1365. if ($quoteItem->compare($item)) {
  1366. $quoteItems->removeItemByKey($key);
  1367. }
  1368. }
  1369. }
  1370. }
  1371. return $this;
  1372. }
  1373. /**
  1374. * Remove quote item by item identifier
  1375. *
  1376. * @param int $itemId
  1377. * @return $this
  1378. */
  1379. public function removeItem($itemId)
  1380. {
  1381. $item = $this->getItemById($itemId);
  1382. if ($item) {
  1383. $item->setQuote($this);
  1384. /**
  1385. * If we remove item from quote - we can't use multishipping mode
  1386. */
  1387. $this->setIsMultiShipping(false);
  1388. $item->isDeleted(true);
  1389. if ($item->getHasChildren()) {
  1390. foreach ($item->getChildren() as $child) {
  1391. $child->isDeleted(true);
  1392. }
  1393. }
  1394. $parent = $item->getParentItem();
  1395. if ($parent) {
  1396. $parent->isDeleted(true);
  1397. }
  1398. $this->_eventManager->dispatch('sales_quote_remove_item', ['quote_item' => $item]);
  1399. }
  1400. return $this;
  1401. }
  1402. /**
  1403. * Mark all quote items as deleted (empty quote)
  1404. *
  1405. * @return $this
  1406. */
  1407. public function removeAllItems()
  1408. {
  1409. foreach ($this->getItemsCollection() as $itemId => $item) {
  1410. if ($item->getId() === null) {
  1411. $this->getItemsCollection()->removeItemByKey($itemId);
  1412. } else {
  1413. $item->isDeleted(true);
  1414. }
  1415. }
  1416. return $this;
  1417. }
  1418. /**
  1419. * Adding new item to quote
  1420. *
  1421. * @param \Magento\Quote\Model\Quote\Item $item
  1422. * @return $this
  1423. * @throws \Magento\Framework\Exception\LocalizedException
  1424. */
  1425. public function addItem(\Magento\Quote\Model\Quote\Item $item)
  1426. {
  1427. $item->setQuote($this);
  1428. if (!$item->getId()) {
  1429. $this->getItemsCollection()->addItem($item);
  1430. $this->_eventManager->dispatch('sales_quote_add_item', ['quote_item' => $item]);
  1431. }
  1432. return $this;
  1433. }
  1434. /**
  1435. * Add product. Returns error message if product type instance can't prepare product.
  1436. *
  1437. * @param mixed $product
  1438. * @param null|float|\Magento\Framework\DataObject $request
  1439. * @param null|string $processMode
  1440. * @return \Magento\Quote\Model\Quote\Item|string
  1441. * @throws \Magento\Framework\Exception\LocalizedException
  1442. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  1443. * @SuppressWarnings(PHPMD.NPathComplexity)
  1444. */
  1445. public function addProduct(
  1446. \Magento\Catalog\Model\Product $product,
  1447. $request = null,
  1448. $processMode = \Magento\Catalog\Model\Product\Type\AbstractType::PROCESS_MODE_FULL
  1449. ) {
  1450. if ($request === null) {
  1451. $request = 1;
  1452. }
  1453. if (is_numeric($request)) {
  1454. $request = $this->objectFactory->create(['qty' => $request]);
  1455. }
  1456. if (!$request instanceof \Magento\Framework\DataObject) {
  1457. throw new \Magento\Framework\Exception\LocalizedException(
  1458. __('We found an invalid request for adding product to quote.')
  1459. );
  1460. }
  1461. if (!$product->isSalable()) {
  1462. throw new \Magento\Framework\Exception\LocalizedException(
  1463. __('Product that you are trying to add is not available.')
  1464. );
  1465. }
  1466. $cartCandidates = $product->getTypeInstance()->prepareForCartAdvanced($request, $product, $processMode);
  1467. /**
  1468. * Error message
  1469. */
  1470. if (is_string($cartCandidates) || $cartCandidates instanceof \Magento\Framework\Phrase) {
  1471. return (string)$cartCandidates;
  1472. }
  1473. /**
  1474. * If prepare process return one object
  1475. */
  1476. if (!is_array($cartCandidates)) {
  1477. $cartCandidates = [$cartCandidates];
  1478. }
  1479. $parentItem = null;
  1480. $errors = [];
  1481. $item = null;
  1482. $items = [];
  1483. foreach ($cartCandidates as $candidate) {
  1484. // Child items can be sticked together only within their parent
  1485. $stickWithinParent = $candidate->getParentProductId() ? $parentItem : null;
  1486. $candidate->setStickWithinParent($stickWithinParent);
  1487. $item = $this->getItemByProduct($candidate);
  1488. if (!$item) {
  1489. $item = $this->itemProcessor->init($candidate, $request);
  1490. $item->setQuote($this);
  1491. $item->setOptions($candidate->getCustomOptions());
  1492. $item->setProduct($candidate);
  1493. // Add only item that is not in quote already
  1494. $this->addItem($item);
  1495. }
  1496. $items[] = $item;
  1497. /**
  1498. * As parent item we should always use the item of first added product
  1499. */
  1500. if (!$parentItem) {
  1501. $parentItem = $item;
  1502. }
  1503. if ($parentItem && $candidate->getParentProductId() && !$item->getParentItem()) {
  1504. $item->setParentItem($parentItem);
  1505. }
  1506. $this->itemProcessor->prepare($item, $request, $candidate);
  1507. // collect errors instead of throwing first one
  1508. if ($item->getHasError()) {
  1509. foreach ($item->getMessage(false) as $message) {
  1510. if (!in_array($message, $errors)) {
  1511. // filter duplicate messages
  1512. $errors[] = $message;
  1513. }
  1514. }
  1515. }
  1516. }
  1517. if (!empty($errors)) {
  1518. throw new \Magento\Framework\Exception\LocalizedException(__(implode("\n", $errors)));
  1519. }
  1520. $this->_eventManager->dispatch('sales_quote_product_add_after', ['items' => $items]);
  1521. return $parentItem;
  1522. }
  1523. /**
  1524. * Adding catalog product object data to quote
  1525. *
  1526. * @param \Magento\Catalog\Model\Product $product
  1527. * @param int $qty
  1528. * @return \Magento\Quote\Model\Quote\Item
  1529. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  1530. */
  1531. protected function _addCatalogProduct(\Magento\Catalog\Model\Product $product, $qty = 1)
  1532. {
  1533. $newItem = false;
  1534. $item = $this->getItemByProduct($product);
  1535. if (!$item) {
  1536. $item = $this->_quoteItemFactory->create();
  1537. $item->setQuote($this);
  1538. if ($this->_appState->getAreaCode() === \Magento\Backend\App\Area\FrontNameResolver::AREA_CODE) {
  1539. $item->setStoreId($this->getStore()->getId());
  1540. } else {
  1541. $item->setStoreId($this->_storeManager->getStore()->getId());
  1542. }
  1543. $newItem = true;
  1544. }
  1545. /**
  1546. * We can't modify existing child items
  1547. */
  1548. if ($item->getId() && $product->getParentProductId()) {
  1549. return $item;
  1550. }
  1551. $item->setOptions($product->getCustomOptions())->setProduct($product);
  1552. // Add only item that is not in quote already (there can be other new or already saved item
  1553. if ($newItem) {
  1554. $this->addItem($item);
  1555. }
  1556. return $item;
  1557. }
  1558. /**
  1559. * Updates quote item with new configuration
  1560. *
  1561. * $params sets how current item configuration must be taken into account and additional options.
  1562. * It's passed to \Magento\Catalog\Helper\Product->addParamsToBuyRequest() to compose resulting buyRequest.
  1563. *
  1564. * Basically it can hold
  1565. * - 'current_config', \Magento\Framework\DataObject or array - current buyRequest that configures product in this
  1566. * item, used to restore currently attached files
  1567. * - 'files_prefix': string[a-z0-9_] - prefix that was added at frontend to names of file options (file inputs),
  1568. * so they won't intersect with other submitted options
  1569. *
  1570. * For more options see \Magento\Catalog\Helper\Product->addParamsToBuyRequest()
  1571. *
  1572. * @param int $itemId
  1573. * @param \Magento\Framework\DataObject $buyRequest
  1574. * @param null|array|\Magento\Framework\DataObject $params
  1575. * @return \Magento\Quote\Model\Quote\Item
  1576. * @throws \Magento\Framework\Exception\LocalizedException
  1577. *
  1578. * @see \Magento\Catalog\Helper\Product::addParamsToBuyRequest()
  1579. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  1580. */
  1581. public function updateItem($itemId, $buyRequest, $params = null)
  1582. {
  1583. $item = $this->getItemById($itemId);
  1584. if (!$item) {
  1585. throw new \Magento\Framework\Exception\LocalizedException(
  1586. __('This is the wrong quote item id to update configuration.')
  1587. );
  1588. }
  1589. $productId = $item->getProduct()->getId();
  1590. //We need to create new clear product instance with same $productId
  1591. //to set new option values from $buyRequest
  1592. $product = clone $this->productRepository->getById($productId, false, $this->getStore()->getId());
  1593. if (!$params) {
  1594. $params = new \Magento\Framework\DataObject();
  1595. } elseif (is_array($params)) {
  1596. $params = new \Magento\Framework\DataObject($params);
  1597. }
  1598. $params->setCurrentConfig($item->getBuyRequest());
  1599. $buyRequest = $this->_catalogProduct->addParamsToBuyRequest($buyRequest, $params);
  1600. $buyRequest->setResetCount(true);
  1601. $resultItem = $this->addProduct($product, $buyRequest);
  1602. if (is_string($resultItem)) {
  1603. throw new \Magento\Framework\Exception\LocalizedException(__($resultItem));
  1604. }
  1605. if ($resultItem->getParentItem()) {
  1606. $resultItem = $resultItem->getParentItem();
  1607. }
  1608. if ($resultItem->getId() != $itemId) {
  1609. /**
  1610. * Product configuration didn't stick to original quote item
  1611. * It either has same configuration as some other quote item's product or completely new configuration
  1612. */
  1613. $this->removeItem($itemId);
  1614. $items = $this->getAllItems();
  1615. foreach ($items as $item) {
  1616. if ($item->getProductId() == $productId && $item->getId() != $resultItem->getId()) {
  1617. if ($resultItem->compare($item)) {
  1618. // Product configuration is same as in other quote item
  1619. $resultItem->setQty($resultItem->getQty() + $item->getQty());
  1620. $this->removeItem($item->getId());
  1621. break;
  1622. }
  1623. }
  1624. }
  1625. } else {
  1626. $resultItem->setQty($buyRequest->getQty());
  1627. }
  1628. return $resultItem;
  1629. }
  1630. /**
  1631. * Retrieve quote item by product id
  1632. *
  1633. * @param \Magento\Catalog\Model\Product $product
  1634. * @return \Magento\Quote\Model\Quote\Item|bool
  1635. */
  1636. public function getItemByProduct($product)
  1637. {
  1638. foreach ($this->getAllItems() as $item) {
  1639. if ($item->representProduct($product)) {
  1640. return $item;
  1641. }
  1642. }
  1643. return false;
  1644. }
  1645. /**
  1646. * Get items summary qty.
  1647. *
  1648. * @return int
  1649. */
  1650. public function getItemsSummaryQty()
  1651. {
  1652. $qty = $this->getData('all_items_qty');
  1653. if (null === $qty) {
  1654. $qty = 0;
  1655. foreach ($this->getAllItems() as $item) {
  1656. if ($item->getParentItem()) {
  1657. continue;
  1658. }
  1659. $children = $item->getChildren();
  1660. if ($children && $item->isShipSeparately()) {
  1661. foreach ($children as $child) {
  1662. $qty += $child->getQty() * $item->getQty();
  1663. }
  1664. } else {
  1665. $qty += $item->getQty();
  1666. }
  1667. }
  1668. $this->setData('all_items_qty', $qty);
  1669. }
  1670. return $qty;
  1671. }
  1672. /**
  1673. * Get item virtual qty.
  1674. *
  1675. * @return int
  1676. */
  1677. public function getItemVirtualQty()
  1678. {
  1679. $qty = $this->getData('virtual_items_qty');
  1680. if (null === $qty) {
  1681. $qty = 0;
  1682. foreach ($this->getAllItems() as $item) {
  1683. if ($item->getParentItem()) {
  1684. continue;
  1685. }
  1686. $children = $item->getChildren();
  1687. if ($children && $item->isShipSeparately()) {
  1688. foreach ($children as $child) {
  1689. if ($child->getProduct()->getIsVirtual()) {
  1690. $qty += $child->getQty();
  1691. }
  1692. }
  1693. } else {
  1694. if ($item->getProduct()->getIsVirtual()) {
  1695. $qty += $item->getQty();
  1696. }
  1697. }
  1698. }
  1699. $this->setData('virtual_items_qty', $qty);
  1700. }
  1701. return $qty;
  1702. }
  1703. /*********************** PAYMENTS ***************************/
  1704. /**
  1705. * Get payments collection.
  1706. *
  1707. * @return \Magento\Eav\Model\Entity\Collection\AbstractCollection
  1708. */
  1709. public function getPaymentsCollection()
  1710. {
  1711. if (null === $this->_payments) {
  1712. $this->_payments = $this->_quotePaymentCollectionFactory->create()->setQuoteFilter($this->getId());
  1713. if ($this->getId()) {
  1714. foreach ($this->_payments as $payment) {
  1715. $payment->setQuote($this);
  1716. }
  1717. }
  1718. }
  1719. return $this->_payments;
  1720. }
  1721. /**
  1722. * Get payment.
  1723. *
  1724. * @return \Magento\Quote\Model\Quote\Payment
  1725. */
  1726. public function getPayment()
  1727. {
  1728. if (null === $this->_currentPayment || !$this->_currentPayment) {
  1729. $this->_currentPayment = $this->_quotePaymentCollectionFactory->create()
  1730. ->setQuoteFilter($this->getId())
  1731. ->getFirstItem();
  1732. }
  1733. if ($payment = $this->_currentPayment) {
  1734. if ($this->getId()) {
  1735. $payment->setQuote($this);
  1736. }
  1737. if (!$payment->isDeleted()) {
  1738. return $payment;
  1739. }
  1740. }
  1741. $payment = $this->_quotePaymentFactory->create();
  1742. $this->addPayment($payment);
  1743. return $payment;
  1744. }
  1745. /**
  1746. * Adds a payment to quote
  1747. *
  1748. * @param PaymentInterface $payment
  1749. * @return $this
  1750. */
  1751. protected function addPayment(PaymentInterface $payment)
  1752. {
  1753. $payment->setQuote($this);
  1754. if (!$payment->getId()) {
  1755. $this->getPaymentsCollection()->addItem($payment);
  1756. }
  1757. return $this;
  1758. }
  1759. /**
  1760. * Sets payment to current quote
  1761. *
  1762. * @param PaymentInterface $payment
  1763. * @return PaymentInterface
  1764. */
  1765. public function setPayment(PaymentInterface $payment)
  1766. {
  1767. if (!$this->getIsMultiPayment() && ($old = $this->getPayment())) {
  1768. $payment->setId($old->getId());
  1769. }
  1770. $this->addPayment($payment);
  1771. return $payment;
  1772. }
  1773. /**
  1774. * Remove payment.
  1775. *
  1776. * @return $this
  1777. */
  1778. public function removePayment()
  1779. {
  1780. $this->getPayment()->isDeleted(true);
  1781. return $this;
  1782. }
  1783. /**
  1784. * Collect totals
  1785. *
  1786. * @return $this
  1787. */
  1788. public function collectTotals()
  1789. {
  1790. if ($this->getTotalsCollectedFlag()) {
  1791. return $this;
  1792. }
  1793. $total = $this->totalsCollector->collect($this);
  1794. $this->addData($total->getData());
  1795. $this->setTotalsCollectedFlag(true);
  1796. return $this;
  1797. }
  1798. /**
  1799. * Get all quote totals (sorted by priority)
  1800. *
  1801. * @return AddressTotal[]
  1802. */
  1803. public function getTotals()
  1804. {
  1805. return $this->totalsReader->fetch($this, $this->getData());
  1806. }
  1807. /**
  1808. * Add message.
  1809. *
  1810. * @param string $message
  1811. * @param string $index
  1812. * @return $this
  1813. */
  1814. public function addMessage($message, $index = 'error')
  1815. {
  1816. $messages = $this->getData('messages');
  1817. if (null === $messages) {
  1818. $messages = [];
  1819. }
  1820. if (isset($messages[$index])) {
  1821. return $this;
  1822. }
  1823. $message = $this->messageFactory->create(\Magento\Framework\Message\MessageInterface::TYPE_ERROR, $message);
  1824. $messages[$index] = $message;
  1825. $this->setData('messages', $messages);
  1826. return $this;
  1827. }
  1828. /**
  1829. * Retrieve current quote messages
  1830. *
  1831. * @return array
  1832. */
  1833. public function getMessages()
  1834. {
  1835. $messages = $this->getData('messages');
  1836. if (null === $messages) {
  1837. $messages = [];
  1838. $this->setData('messages', $messages);
  1839. }
  1840. return $messages;
  1841. }
  1842. /**
  1843. * Retrieve current quote errors
  1844. *
  1845. * @return array
  1846. */
  1847. public function getErrors()
  1848. {
  1849. $errors = [];
  1850. foreach ($this->getMessages() as $message) {
  1851. /* @var $error \Magento\Framework\Message\AbstractMessage */
  1852. if ($message->getType() == \Magento\Framework\Message\MessageInterface::TYPE_ERROR) {
  1853. $errors[] = $message;
  1854. }
  1855. }
  1856. return $errors;
  1857. }
  1858. /**
  1859. * Sets flag, whether this quote has some error associated with it.
  1860. *
  1861. * @codeCoverageIgnore
  1862. *
  1863. * @param bool $flag
  1864. * @return $this
  1865. */
  1866. protected function _setHasError($flag)
  1867. {
  1868. return $this->setData('has_error', $flag);
  1869. }
  1870. /**
  1871. * Sets flag, whether this quote has some error associated with it.
  1872. * When TRUE - also adds 'unknown' error information to list of quote errors.
  1873. * When FALSE - clears whole list of quote errors.
  1874. * It's recommended to use addErrorInfo() instead - to be able to remove error statuses later.
  1875. *
  1876. * @param bool $flag
  1877. * @return $this
  1878. * @see addErrorInfo()
  1879. */
  1880. public function setHasError($flag)
  1881. {
  1882. if ($flag) {
  1883. $this->addErrorInfo();
  1884. } else {
  1885. $this->_clearErrorInfo();
  1886. }
  1887. return $this;
  1888. }
  1889. /**
  1890. * Clears list of errors, associated with this quote. Also automatically removes error-flag from oneself.
  1891. *
  1892. * @return $this
  1893. */
  1894. protected function _clearErrorInfo()
  1895. {
  1896. $this->_errorInfoGroups = [];
  1897. $this->_setHasError(false);
  1898. return $this;
  1899. }
  1900. /**
  1901. * Adds error information to the quote. Automatically sets error flag.
  1902. *
  1903. * @param string $type An internal error type ('error', 'qty', etc.), passed then to adding messages routine
  1904. * @param string|null $origin Usually a name of module, that embeds error
  1905. * @param int|null $code Error code, unique for origin, that sets it
  1906. * @param string|null $message Error message
  1907. * @param \Magento\Framework\DataObject|null $additionalData Any additional data, that caller would like to store
  1908. * @return $this
  1909. */
  1910. public function addErrorInfo(
  1911. $type = 'error',
  1912. $origin = null,
  1913. $code = null,
  1914. $message = null,
  1915. $additionalData = null
  1916. ) {
  1917. if (!isset($this->_errorInfoGroups[$type])) {
  1918. $this->_errorInfoGroups[$type] = $this->_statusListFactory->create();
  1919. }
  1920. $this->_errorInfoGroups[$type]->addItem($origin, $code, $message, $additionalData);
  1921. if ($message !== null) {
  1922. $this->addMessage($message, $type);
  1923. }
  1924. $this->_setHasError(true);
  1925. return $this;
  1926. }
  1927. /**
  1928. * Removes error infos, that have parameters equal to passed in $params.
  1929. * $params can have following keys (if not set - then any item is good for this key):
  1930. * 'origin', 'code', 'message'
  1931. *
  1932. * @param string $type An internal error type ('error', 'qty', etc.), passed then to adding messages routine
  1933. * @param array $params
  1934. * @return $this
  1935. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  1936. */
  1937. public function removeErrorInfosByParams($type, $params)
  1938. {
  1939. if ($type && !isset($this->_errorInfoGroups[$type])) {
  1940. return $this;
  1941. }
  1942. $errorLists = [];
  1943. if ($type) {
  1944. $errorLists[] = $this->_errorInfoGroups[$type];
  1945. } else {
  1946. $errorLists = $this->_errorInfoGroups;
  1947. }
  1948. foreach ($errorLists as $type => $errorList) {
  1949. $removedItems = $errorList->removeItemsByParams($params);
  1950. foreach ($removedItems as $item) {
  1951. if ($item['message'] !== null) {
  1952. $this->removeMessageByText($type, $item['message']);
  1953. }
  1954. }
  1955. }
  1956. $errorsExist = false;
  1957. foreach ($this->_errorInfoGroups as $errorListCheck) {
  1958. if ($errorListCheck->getItems()) {
  1959. $errorsExist = true;
  1960. break;
  1961. }
  1962. }
  1963. if (!$errorsExist) {
  1964. $this->_setHasError(false);
  1965. }
  1966. return $this;
  1967. }
  1968. /**
  1969. * Removes message by text
  1970. *
  1971. * @param string $type
  1972. * @param string $text
  1973. * @return $this
  1974. */
  1975. public function removeMessageByText($type, $text)
  1976. {
  1977. $messages = $this->getData('messages');
  1978. if (null === $messages) {
  1979. $messages = [];
  1980. }
  1981. if (!isset($messages[$type])) {
  1982. return $this;
  1983. }
  1984. $message = $messages[$type];
  1985. if ($message instanceof \Magento\Framework\Message\AbstractMessage) {
  1986. $message = $message->getText();
  1987. } elseif (!is_string($message)) {
  1988. return $this;
  1989. }
  1990. if ($message == $text) {
  1991. unset($messages[$type]);
  1992. $this->setData('messages', $messages);
  1993. }
  1994. return $this;
  1995. }
  1996. /**
  1997. * Generate new increment order id and associate it with current quote
  1998. *
  1999. * @return $this
  2000. */
  2001. public function reserveOrderId()
  2002. {
  2003. if (!$this->getReservedOrderId()) {
  2004. $this->setReservedOrderId($this->_getResource()->getReservedOrderId($this));
  2005. } else {
  2006. //checking if reserved order id was already used for some order
  2007. //if yes reserving new one if not using old one
  2008. if ($this->orderIncrementIdChecker->isIncrementIdUsed($this->getReservedOrderId())) {
  2009. $this->setReservedOrderId($this->_getResource()->getReservedOrderId($this));
  2010. }
  2011. }
  2012. return $this;
  2013. }
  2014. /**
  2015. * Validate minimum amount.
  2016. *
  2017. * @param bool $multishipping
  2018. * @return bool
  2019. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  2020. * @SuppressWarnings(PHPMD.NPathComplexity)
  2021. */
  2022. public function validateMinimumAmount($multishipping = false)
  2023. {
  2024. $storeId = $this->getStoreId();
  2025. $minOrderActive = $this->_scopeConfig->isSetFlag(
  2026. 'sales/minimum_order/active',
  2027. \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
  2028. $storeId
  2029. );
  2030. if (!$minOrderActive) {
  2031. return true;
  2032. }
  2033. $includeDiscount = $this->_scopeConfig->getValue(
  2034. 'sales/minimum_order/include_discount_amount',
  2035. \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
  2036. $storeId
  2037. );
  2038. $minOrderMulti = $this->_scopeConfig->isSetFlag(
  2039. 'sales/minimum_order/multi_address',
  2040. \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
  2041. $storeId
  2042. );
  2043. $minAmount = $this->_scopeConfig->getValue(
  2044. 'sales/minimum_order/amount',
  2045. \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
  2046. $storeId
  2047. );
  2048. $taxInclude = $this->_scopeConfig->getValue(
  2049. 'sales/minimum_order/tax_including',
  2050. \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
  2051. $storeId
  2052. );
  2053. $addresses = $this->getAllAddresses();
  2054. if (!$multishipping) {
  2055. foreach ($addresses as $address) {
  2056. /* @var $address Address */
  2057. if (!$address->validateMinimumAmount()) {
  2058. return false;
  2059. }
  2060. }
  2061. return true;
  2062. }
  2063. if (!$minOrderMulti) {
  2064. foreach ($addresses as $address) {
  2065. $taxes = ($taxInclude) ? $address->getBaseTaxAmount() : 0;
  2066. foreach ($address->getQuote()->getItemsCollection() as $item) {
  2067. /** @var \Magento\Quote\Model\Quote\Item $item */
  2068. $amount = $includeDiscount ?
  2069. $item->getBaseRowTotal() - $item->getBaseDiscountAmount() + $taxes :
  2070. $item->getBaseRowTotal() + $taxes;
  2071. if ($amount < $minAmount) {
  2072. return false;
  2073. }
  2074. }
  2075. }
  2076. } else {
  2077. $baseTotal = 0;
  2078. foreach ($addresses as $address) {
  2079. $taxes = ($taxInclude) ? $address->getBaseTaxAmount() : 0;
  2080. $baseTotal += $includeDiscount ?
  2081. $address->getBaseSubtotalWithDiscount() + $taxes :
  2082. $address->getBaseSubtotal() + $taxes;
  2083. }
  2084. if ($baseTotal < $minAmount) {
  2085. return false;
  2086. }
  2087. }
  2088. return true;
  2089. }
  2090. /**
  2091. * Check quote for virtual product only
  2092. *
  2093. * @return bool
  2094. */
  2095. public function isVirtual()
  2096. {
  2097. $isVirtual = true;
  2098. $countItems = 0;
  2099. foreach ($this->getItemsCollection() as $_item) {
  2100. /* @var $_item \Magento\Quote\Model\Quote\Item */
  2101. if ($_item->isDeleted() || $_item->getParentItemId()) {
  2102. continue;
  2103. }
  2104. $countItems++;
  2105. if (!$_item->getProduct()->getIsVirtual()) {
  2106. $isVirtual = false;
  2107. break;
  2108. }
  2109. }
  2110. return $countItems == 0 ? false : $isVirtual;
  2111. }
  2112. /**
  2113. * Check quote for virtual product only
  2114. *
  2115. * @return bool
  2116. * @SuppressWarnings(PHPMD.BooleanGetMethodName)
  2117. */
  2118. public function getIsVirtual()
  2119. {
  2120. return (int)$this->isVirtual();
  2121. }
  2122. /**
  2123. * Has a virtual products on quote
  2124. *
  2125. * @return bool
  2126. */
  2127. public function hasVirtualItems()
  2128. {
  2129. $hasVirtual = false;
  2130. foreach ($this->getItemsCollection() as $item) {
  2131. if ($item->getParentItemId()) {
  2132. continue;
  2133. }
  2134. if ($item->getProduct()->isVirtual()) {
  2135. $hasVirtual = true;
  2136. }
  2137. }
  2138. return $hasVirtual;
  2139. }
  2140. /**
  2141. * Merge quotes
  2142. *
  2143. * @param Quote $quote
  2144. * @return $this
  2145. */
  2146. public function merge(Quote $quote)
  2147. {
  2148. $this->_eventManager->dispatch(
  2149. $this->_eventPrefix . '_merge_before',
  2150. [$this->_eventObject => $this, 'source' => $quote]
  2151. );
  2152. foreach ($quote->getAllVisibleItems() as $item) {
  2153. $found = false;
  2154. foreach ($this->getAllItems() as $quoteItem) {
  2155. if ($quoteItem->compare($item)) {
  2156. $quoteItem->setQty($quoteItem->getQty() + $item->getQty());
  2157. $this->itemProcessor->merge($item, $quoteItem);
  2158. $found = true;
  2159. break;
  2160. }
  2161. }
  2162. if (!$found) {
  2163. $newItem = clone $item;
  2164. $this->addItem($newItem);
  2165. if ($item->getHasChildren()) {
  2166. foreach ($item->getChildren() as $child) {
  2167. $newChild = clone $child;
  2168. $newChild->setParentItem($newItem);
  2169. $this->addItem($newChild);
  2170. }
  2171. }
  2172. }
  2173. }
  2174. /**
  2175. * Init shipping and billing address if quote is new
  2176. */
  2177. if (!$this->getId()) {
  2178. $this->getShippingAddress();
  2179. $this->getBillingAddress();
  2180. }
  2181. if ($quote->getCouponCode()) {
  2182. $this->setCouponCode($quote->getCouponCode());
  2183. }
  2184. $this->_eventManager->dispatch(
  2185. $this->_eventPrefix . '_merge_after',
  2186. [$this->_eventObject => $this, 'source' => $quote]
  2187. );
  2188. return $this;
  2189. }
  2190. /**
  2191. * Trigger collect totals after loading, if required
  2192. *
  2193. * @return $this
  2194. */
  2195. protected function _afterLoad()
  2196. {
  2197. // collect totals and save me, if required
  2198. if (1 == $this->getTriggerRecollect()) {
  2199. $this->collectTotals()
  2200. ->setTriggerRecollect(0)
  2201. ->save();
  2202. }
  2203. return parent::_afterLoad();
  2204. }
  2205. /**
  2206. * Checks if it was set
  2207. *
  2208. * @return bool
  2209. */
  2210. public function addressCollectionWasSet()
  2211. {
  2212. return null !== $this->_addresses;
  2213. }
  2214. /**
  2215. * Checks if it was set
  2216. *
  2217. * @return bool
  2218. */
  2219. public function itemsCollectionWasSet()
  2220. {
  2221. return null !== $this->_items;
  2222. }
  2223. /**
  2224. * Checks if it was set
  2225. *
  2226. * @return bool
  2227. */
  2228. public function paymentsCollectionWasSet()
  2229. {
  2230. return null !== $this->_payments;
  2231. }
  2232. /**
  2233. * Checks if it was set
  2234. *
  2235. * @return bool
  2236. */
  2237. public function currentPaymentWasSet()
  2238. {
  2239. return null !== $this->_currentPayment;
  2240. }
  2241. /**
  2242. * Return checkout method code
  2243. *
  2244. * @param boolean $originalMethod if true return defined method from beginning
  2245. * @return string
  2246. */
  2247. public function getCheckoutMethod($originalMethod = false)
  2248. {
  2249. if ($this->getCustomerId() && !$originalMethod) {
  2250. return self::CHECKOUT_METHOD_LOGIN_IN;
  2251. }
  2252. return $this->_getData(self::KEY_CHECKOUT_METHOD);
  2253. }
  2254. /**
  2255. * Get quote items assigned to different quote addresses populated per item qty.
  2256. *
  2257. * @return array
  2258. */
  2259. public function getShippingAddressesItems()
  2260. {
  2261. if ($this->shippingAddressesItems !== null) {
  2262. return $this->shippingAddressesItems;
  2263. }
  2264. $items = [];
  2265. $addresses = $this->getAllAddresses();
  2266. foreach ($addresses as $address) {
  2267. foreach ($address->getAllItems() as $item) {
  2268. if ($item->getParentItemId()) {
  2269. continue;
  2270. }
  2271. if ($item->getProduct()->getIsVirtual()) {
  2272. $items[] = $item;
  2273. continue;
  2274. }
  2275. if ($item->getQty() > 1) {
  2276. for ($itemIndex = 0, $itemQty = $item->getQty(); $itemIndex < $itemQty; $itemIndex++) {
  2277. if ($itemIndex == 0) {
  2278. $addressItem = $item;
  2279. } else {
  2280. $addressItem = clone $item;
  2281. }
  2282. $addressItem->setQty(1)->setCustomerAddressId($address->getCustomerAddressId())->save();
  2283. $items[] = $addressItem;
  2284. }
  2285. } else {
  2286. $item->setCustomerAddressId($address->getCustomerAddressId());
  2287. $items[] = $item;
  2288. }
  2289. }
  2290. }
  2291. $this->shippingAddressesItems = $items;
  2292. return $items;
  2293. }
  2294. /**
  2295. * Sets the payment method that is used to process the cart.
  2296. *
  2297. * @codeCoverageIgnore
  2298. *
  2299. * @param string $checkoutMethod
  2300. * @return $this
  2301. */
  2302. public function setCheckoutMethod($checkoutMethod)
  2303. {
  2304. return $this->setData(self::KEY_CHECKOUT_METHOD, $checkoutMethod);
  2305. }
  2306. /**
  2307. * Prevent quote from saving
  2308. *
  2309. * @codeCoverageIgnore
  2310. *
  2311. * @return $this
  2312. */
  2313. public function preventSaving()
  2314. {
  2315. $this->_preventSaving = true;
  2316. return $this;
  2317. }
  2318. /**
  2319. * Check if model can be saved
  2320. *
  2321. * @codeCoverageIgnore
  2322. *
  2323. * @return bool
  2324. */
  2325. public function isPreventSaving()
  2326. {
  2327. return $this->_preventSaving;
  2328. }
  2329. /**
  2330. * Check if there are more than one shipping address
  2331. *
  2332. * @return bool
  2333. */
  2334. public function isMultipleShippingAddresses()
  2335. {
  2336. return \count($this->getAllShippingAddresses()) > 1;
  2337. }
  2338. /**
  2339. * Retrieve existing extension attributes object or create a new one.
  2340. *
  2341. * @return \Magento\Quote\Api\Data\CartExtensionInterface|null
  2342. */
  2343. public function getExtensionAttributes()
  2344. {
  2345. return $this->_getExtensionAttributes();
  2346. }
  2347. /**
  2348. * Set an extension attributes object.
  2349. *
  2350. * @param \Magento\Quote\Api\Data\CartExtensionInterface $extensionAttributes
  2351. * @return $this
  2352. */
  2353. public function setExtensionAttributes(\Magento\Quote\Api\Data\CartExtensionInterface $extensionAttributes)
  2354. {
  2355. return $this->_setExtensionAttributes($extensionAttributes);
  2356. }
  2357. }