123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513 |
- <?php
- /**
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
- */
- namespace Magento\Shipping\Model;
- use Magento\Framework\App\ObjectManager;
- use Magento\Quote\Model\Quote\Address\RateCollectorInterface;
- use Magento\Quote\Model\Quote\Address\RateRequestFactory;
- use Magento\Sales\Model\Order\Shipment;
- /**
- * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
- */
- class Shipping implements RateCollectorInterface
- {
- /**
- * Default shipping orig for requests
- *
- * @var array
- */
- protected $_orig = null;
- /**
- * Cached result
- *
- * @var \Magento\Shipping\Model\Rate\Result
- */
- protected $_result = null;
- /**
- * Part of carrier xml config path
- *
- * @var string
- */
- protected $_availabilityConfigField = 'active';
- /**
- * Core store config
- *
- * @var \Magento\Framework\App\Config\ScopeConfigInterface
- */
- protected $_scopeConfig;
- /**
- * @var \Magento\Store\Model\StoreManagerInterface
- */
- protected $_storeManager;
- /**
- * @var \Magento\Shipping\Model\Config
- */
- protected $_shippingConfig;
- /**
- * @var \Magento\Shipping\Model\CarrierFactory
- */
- protected $_carrierFactory;
- /**
- * @var \Magento\Shipping\Model\Rate\ResultFactory
- */
- protected $_rateResultFactory;
- /**
- * @var \Magento\Quote\Model\Quote\Address\RateRequestFactory
- */
- protected $_shipmentRequestFactory;
- /**
- * @var \Magento\Directory\Model\RegionFactory
- */
- protected $_regionFactory;
- /**
- * @var \Magento\Framework\Math\Division
- */
- protected $mathDivision;
- /**
- * @var \Magento\CatalogInventory\Api\StockRegistryInterface
- */
- protected $stockRegistry;
- /**
- * @var RateRequestFactory
- */
- private $rateRequestFactory;
- /**
- * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
- * @param \Magento\Shipping\Model\Config $shippingConfig
- * @param \Magento\Store\Model\StoreManagerInterface $storeManager
- * @param \Magento\Shipping\Model\CarrierFactory $carrierFactory
- * @param \Magento\Shipping\Model\Rate\ResultFactory $rateResultFactory
- * @param \Magento\Shipping\Model\Shipment\RequestFactory $shipmentRequestFactory
- * @param \Magento\Directory\Model\RegionFactory $regionFactory
- * @param \Magento\Framework\Math\Division $mathDivision
- * @param \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry
- * @param RateRequestFactory $rateRequestFactory
- *
- * @SuppressWarnings(PHPMD.ExcessiveParameterList)
- */
- public function __construct(
- \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
- \Magento\Shipping\Model\Config $shippingConfig,
- \Magento\Store\Model\StoreManagerInterface $storeManager,
- \Magento\Shipping\Model\CarrierFactory $carrierFactory,
- \Magento\Shipping\Model\Rate\ResultFactory $rateResultFactory,
- \Magento\Shipping\Model\Shipment\RequestFactory $shipmentRequestFactory,
- \Magento\Directory\Model\RegionFactory $regionFactory,
- \Magento\Framework\Math\Division $mathDivision,
- \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry,
- RateRequestFactory $rateRequestFactory = null
- ) {
- $this->_scopeConfig = $scopeConfig;
- $this->_shippingConfig = $shippingConfig;
- $this->_storeManager = $storeManager;
- $this->_carrierFactory = $carrierFactory;
- $this->_rateResultFactory = $rateResultFactory;
- $this->_shipmentRequestFactory = $shipmentRequestFactory;
- $this->_regionFactory = $regionFactory;
- $this->mathDivision = $mathDivision;
- $this->stockRegistry = $stockRegistry;
- $this->rateRequestFactory = $rateRequestFactory ?: ObjectManager::getInstance()->get(RateRequestFactory::class);
- }
- /**
- * Get shipping rate result model
- *
- * @return \Magento\Shipping\Model\Rate\Result
- */
- public function getResult()
- {
- if (empty($this->_result)) {
- $this->_result = $this->_rateResultFactory->create();
- }
- return $this->_result;
- }
- /**
- * Set shipping orig data
- *
- * @param array $data
- * @return void
- */
- public function setOrigData($data)
- {
- $this->_orig = $data;
- }
- /**
- * Reset cached result
- *
- * @return $this
- */
- public function resetResult()
- {
- $this->getResult()->reset();
- return $this;
- }
- /**
- * Retrieve configuration model
- *
- * @return \Magento\Shipping\Model\Config
- */
- public function getConfig()
- {
- return $this->_shippingConfig;
- }
- /**
- * Retrieve all methods for supplied shipping data
- *
- * @param \Magento\Quote\Model\Quote\Address\RateRequest $request
- * @return $this
- * @todo make it ordered
- */
- public function collectRates(\Magento\Quote\Model\Quote\Address\RateRequest $request)
- {
- $storeId = $request->getStoreId();
- if (!$request->getOrig()) {
- $request->setCountryId(
- $this->_scopeConfig->getValue(
- Shipment::XML_PATH_STORE_COUNTRY_ID,
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- $request->getStore()
- )
- )->setRegionId(
- $this->_scopeConfig->getValue(
- Shipment::XML_PATH_STORE_REGION_ID,
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- $request->getStore()
- )
- )->setCity(
- $this->_scopeConfig->getValue(
- Shipment::XML_PATH_STORE_CITY,
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- $request->getStore()
- )
- )->setPostcode(
- $this->_scopeConfig->getValue(
- Shipment::XML_PATH_STORE_ZIP,
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- $request->getStore()
- )
- );
- }
- $limitCarrier = $request->getLimitCarrier();
- if (!$limitCarrier) {
- $carriers = $this->_scopeConfig->getValue(
- 'carriers',
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- $storeId
- );
- foreach ($carriers as $carrierCode => $carrierConfig) {
- $this->collectCarrierRates($carrierCode, $request);
- }
- } else {
- if (!is_array($limitCarrier)) {
- $limitCarrier = [$limitCarrier];
- }
- foreach ($limitCarrier as $carrierCode) {
- $carrierConfig = $this->_scopeConfig->getValue(
- 'carriers/' . $carrierCode,
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
- $storeId
- );
- if (!$carrierConfig) {
- continue;
- }
- $this->collectCarrierRates($carrierCode, $request);
- }
- }
- return $this;
- }
- /**
- * Collect rates of given carrier
- *
- * @param string $carrierCode
- * @param \Magento\Quote\Model\Quote\Address\RateRequest $request
- * @return $this
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.NPathComplexity)
- */
- public function collectCarrierRates($carrierCode, $request)
- {
- /* @var $carrier \Magento\Shipping\Model\Carrier\AbstractCarrier */
- $carrier = $this->_carrierFactory->createIfActive($carrierCode, $request->getStoreId());
- if (!$carrier) {
- return $this;
- }
- $carrier->setActiveFlag($this->_availabilityConfigField);
- $result = $carrier->checkAvailableShipCountries($request);
- if (false !== $result && !$result instanceof \Magento\Quote\Model\Quote\Address\RateResult\Error) {
- $result = $carrier->processAdditionalValidation($request);
- }
- /*
- * Result will be false if the admin set not to show the shipping module
- * if the delivery country is not within specific countries
- */
- if (false !== $result) {
- if (!$result instanceof \Magento\Quote\Model\Quote\Address\RateResult\Error) {
- if ($carrier->getConfigData('shipment_requesttype')) {
- $packages = $this->composePackagesForCarrier($carrier, $request);
- if (!empty($packages)) {
- $sumResults = [];
- foreach ($packages as $weight => $packageCount) {
- $request->setPackageWeight($weight);
- $result = $carrier->collectRates($request);
- if (!$result) {
- return $this;
- } else {
- $result->updateRatePrice($packageCount);
- }
- $sumResults[] = $result;
- }
- if (!empty($sumResults) && count($sumResults) > 1) {
- $result = [];
- foreach ($sumResults as $res) {
- if (empty($result)) {
- $result = $res;
- continue;
- }
- foreach ($res->getAllRates() as $method) {
- foreach ($result->getAllRates() as $resultMethod) {
- if ($method->getMethod() == $resultMethod->getMethod()) {
- $resultMethod->setPrice($method->getPrice() + $resultMethod->getPrice());
- continue;
- }
- }
- }
- }
- }
- } else {
- $result = $carrier->collectRates($request);
- }
- } else {
- $result = $carrier->collectRates($request);
- }
- if (!$result) {
- return $this;
- }
- }
- if ($carrier->getConfigData('showmethod') == 0 && $result->getError()) {
- return $this;
- }
- // sort rates by price
- if (method_exists($result, 'sortRatesByPrice') && is_callable([$result, 'sortRatesByPrice'])) {
- $result->sortRatesByPrice();
- }
- $this->getResult()->append($result);
- }
- return $this;
- }
- /**
- * Compose Packages For Carrier.
- * Divides order into items and items into parts if it's necessary
- *
- * @param \Magento\Shipping\Model\Carrier\AbstractCarrier $carrier
- * @param \Magento\Quote\Model\Quote\Address\RateRequest $request
- * @return array [int, float]
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.NPathComplexity)
- */
- public function composePackagesForCarrier($carrier, $request)
- {
- $allItems = $request->getAllItems();
- $fullItems = [];
- $maxWeight = (double)$carrier->getConfigData('max_package_weight');
- /** @var $item \Magento\Quote\Model\Quote\Item */
- foreach ($allItems as $item) {
- if ($item->getProductType() == \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE
- && $item->getProduct()->getShipmentType()
- ) {
- continue;
- }
- $qty = $item->getQty();
- $changeQty = true;
- $checkWeight = true;
- $decimalItems = [];
- if ($item->getParentItem()) {
- if (!$item->getParentItem()->getProduct()->getShipmentType()) {
- continue;
- }
- $qty = $item->getIsQtyDecimal()
- ? $item->getParentItem()->getQty()
- : $item->getParentItem()->getQty() * $item->getQty();
- }
- $itemWeight = $item->getWeight();
- if ($item->getIsQtyDecimal()
- && $item->getProductType() != \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE
- ) {
- $productId = $item->getProduct()->getId();
- $stockItem = $this->stockRegistry->getStockItem($productId, $item->getStore()->getWebsiteId());
- if ($stockItem->getIsDecimalDivided()) {
- if ($stockItem->getEnableQtyIncrements() && $stockItem->getQtyIncrements()) {
- $itemWeight = $itemWeight * $stockItem->getQtyIncrements();
- $qty = round($item->getWeight() / $itemWeight * $qty);
- $changeQty = false;
- } else {
- $itemWeight = $itemWeight * $item->getQty();
- if ($itemWeight > $maxWeight) {
- $qtyItem = floor($itemWeight / $maxWeight);
- $decimalItems[] = ['weight' => $maxWeight, 'qty' => $qtyItem];
- $weightItem = $this->mathDivision->getExactDivision($itemWeight, $maxWeight);
- if ($weightItem) {
- $decimalItems[] = ['weight' => $weightItem, 'qty' => 1];
- }
- $checkWeight = false;
- } else {
- $itemWeight = $itemWeight * $item->getQty();
- }
- }
- } else {
- $itemWeight = $itemWeight * $item->getQty();
- }
- }
- if ($checkWeight && $maxWeight && $itemWeight > $maxWeight) {
- return [];
- }
- if ($changeQty
- && !$item->getParentItem()
- && $item->getIsQtyDecimal()
- && $item->getProductType() != \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE
- ) {
- $qty = 1;
- }
- if (!empty($decimalItems)) {
- foreach ($decimalItems as $decimalItem) {
- $fullItems = array_merge(
- $fullItems,
- array_fill(0, $decimalItem['qty'] * $qty, $decimalItem['weight'])
- );
- }
- } else {
- $fullItems = array_merge($fullItems, array_fill(0, $qty, $itemWeight));
- }
- }
- sort($fullItems);
- return $this->_makePieces($fullItems, $maxWeight);
- }
- /**
- * Make pieces
- * Compose packages list based on given items, so that each package is as heavy as possible
- *
- * @param array $items
- * @param float $maxWeight
- * @return array
- */
- protected function _makePieces($items, $maxWeight)
- {
- $pieces = [];
- if (!empty($items)) {
- $sumWeight = 0;
- $reverseOrderItems = $items;
- arsort($reverseOrderItems);
- foreach ($reverseOrderItems as $key => $weight) {
- if (!isset($items[$key])) {
- continue;
- }
- unset($items[$key]);
- $sumWeight = $weight;
- foreach ($items as $key => $weight) {
- if ($sumWeight + $weight < $maxWeight) {
- unset($items[$key]);
- $sumWeight += $weight;
- } elseif ($sumWeight + $weight > $maxWeight) {
- $pieces[] = (string)(double)$sumWeight;
- break;
- } else {
- unset($items[$key]);
- $pieces[] = (string)(double)($sumWeight + $weight);
- $sumWeight = 0;
- break;
- }
- }
- }
- if ($sumWeight > 0) {
- $pieces[] = (string)(double)$sumWeight;
- }
- $pieces = array_count_values($pieces);
- }
- return $pieces;
- }
- /**
- * Collect rates by address
- *
- * @param \Magento\Framework\DataObject $address
- * @param null|bool|array $limitCarrier
- * @return $this
- */
- public function collectRatesByAddress(\Magento\Framework\DataObject $address, $limitCarrier = null)
- {
- /** @var $request \Magento\Quote\Model\Quote\Address\RateRequest */
- $request = $this->rateRequestFactory->create();
- $request->setAllItems($address->getAllItems());
- $request->setDestCountryId($address->getCountryId());
- $request->setDestRegionId($address->getRegionId());
- $request->setDestPostcode($address->getPostcode());
- $request->setPackageValue($address->getBaseSubtotal());
- $request->setPackageValueWithDiscount($address->getBaseSubtotalWithDiscount());
- $request->setPackageWeight($address->getWeight());
- $request->setFreeMethodWeight($address->getFreeMethodWeight());
- $request->setPackageQty($address->getItemQty());
- /** @var \Magento\Store\Api\Data\StoreInterface $store */
- $store = $this->_storeManager->getStore();
- $request->setStoreId($store->getId());
- $request->setWebsiteId($store->getWebsiteId());
- $request->setBaseCurrency($store->getBaseCurrency());
- $request->setPackageCurrency($store->getCurrentCurrency());
- $request->setLimitCarrier($limitCarrier);
- $request->setBaseSubtotalInclTax($address->getBaseSubtotalInclTax());
- return $this->collectRates($request);
- }
- /**
- * Set part of carrier xml config path
- *
- * @param string $code
- * @return $this
- */
- public function setCarrierAvailabilityConfigField($code = 'active')
- {
- $this->_availabilityConfigField = $code;
- return $this;
- }
- }
|