Skip to content

Commit

Permalink
LYNX-305: Add estimated cost of shipping methods for guest cart (#176)
Browse files Browse the repository at this point in the history
* LYNX-305: GraphQL query to obtain estimated cost of shipping methods for cart based on country/address

* LYNX-314: Been able to run tests locally when add product to cart fixture

* LYNX-305: CR changes

---------

Co-authored-by: eliseacornejo <ecornejo@adobe.com>
Co-authored-by: Sumesh P <142383015+sumesh-GL@users.noreply.github.com>
  • Loading branch information
3 people authored and svera committed Jan 22, 2024
1 parent a182f92 commit c9453c9
Show file tree
Hide file tree
Showing 7 changed files with 888 additions and 29 deletions.
2 changes: 1 addition & 1 deletion app/code/Magento/Catalog/Test/Fixture/Product.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class Product implements RevertibleDataFixtureInterface
'media_gallery_entries' => [],
'tier_prices' => [],
'created_at' => null,
'updated_at' => null,
'updated_at' => null
];

private const DEFAULT_PRODUCT_LINK_DATA = [
Expand Down
99 changes: 99 additions & 0 deletions app/code/Magento/OfflineShipping/Test/Fixture/TablerateFixture.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php
/**
* Copyright 2023 Adobe
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe and its suppliers, if any. The intellectual
* and technical concepts contained herein are proprietary to Adobe
* and its suppliers and are protected by all applicable intellectual
* property laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained from
* Adobe.
*/
declare(strict_types=1);

namespace Magento\OfflineShipping\Test\Fixture;

use Magento\Framework\DataObject;
use Magento\TestFramework\Fixture\Api\ServiceFactory;
use Magento\TestFramework\Fixture\Api\DataMerger;
use Magento\TestFramework\Fixture\RevertibleDataFixtureInterface;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\Framework\ObjectManagerInterface;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\DB\Adapter\AdapterInterface;
use Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate;

class TablerateFixture implements RevertibleDataFixtureInterface
{
private const DEFAULT_DATA = [
'website_id' => 1,
'dest_country_id' => 'US',
'dest_region_id' => 0,
'dest_zip' => '*',
'condition_name' => 'package_qty',
'condition_value' => 1,
'price' => 10,
'cost' => 0
];

/**
* @var AdapterInterface $connection
*/
private AdapterInterface $connection;

/**
* @var ObjectManagerInterface $objectManager
*/
private ObjectManagerInterface $objectManager;

/**
* @param ServiceFactory $serviceFactory
* @param DataMerger $dataMerger
*/
public function __construct(
private ServiceFactory $serviceFactory,
private DataMerger $dataMerger,
) {
$this->objectManager = Bootstrap::getObjectManager();
$this->connection = $this->objectManager->get(ResourceConnection::class)->getConnection();
}

/**
* @inheritDoc
*/
public function apply(array $data = []): ?DataObject
{
$resourceModel = $this->objectManager->create(Tablerate::class);
$data = $this->dataMerger->merge($this::DEFAULT_DATA, $data);
$columns = [
'website_id',
'dest_country_id',
'dest_region_id',
'dest_zip',
'condition_name',
'condition_value',
'price',
'cost'
];
$resourceModel->getConnection()->insertArray(
$resourceModel->getMainTable(),
$columns,
[
$data
]
);
return new DataObject($data);
}

/**
* @inheritDoc
*/
public function revert(DataObject $data): void
{
$resourceModel = $this->objectManager->create(Tablerate::class);
$this->connection->query("DELETE FROM {$resourceModel->getTable('shipping_tablerate')};");
}
}
55 changes: 55 additions & 0 deletions app/code/Magento/QuoteGraphQl/Model/FormatMoneyTypeData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php
/**
* Copyright 2023 Adobe
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe and its suppliers, if any. The intellectual
* and technical concepts contained herein are proprietary to Adobe
* and its suppliers and are protected by all applicable intellectual
* property laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained from
* Adobe.
*/
declare(strict_types=1);

namespace Magento\QuoteGraphQl\Model;

class FormatMoneyTypeData
{
/**
* Converts money data to unified format
*
* @param array $data
* @param string $currencyCode
* @return array
*/
public function execute(array $data, string $currencyCode): array
{
if (isset($data['amount'])) {
$data['amount'] = [
'value' => $data['amount'],
'currency' => $currencyCode
];
}

/** @deprecated The field should not be used on the storefront */
$data['base_amount'] = null;

if (isset($data['price_excl_tax'])) {
$data['price_excl_tax'] = [
'value' => $data['price_excl_tax'],
'currency' => $currencyCode
];
}

if (isset($data['price_incl_tax'])) {
$data['price_incl_tax'] = [
'value' => $data['price_incl_tax'],
'currency' => $currencyCode
];
}
return $data;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<?php
/**
* Copyright 2023 Adobe
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe and its suppliers, if any. The intellectual
* and technical concepts contained herein are proprietary to Adobe
* and its suppliers and are protected by all applicable intellectual
* property laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained from
* Adobe.
*/
declare(strict_types=1);

namespace Magento\QuoteGraphQl\Model\Resolver;

use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Api\ExtensibleDataObjectConverter;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Quote\Api\CartRepositoryInterface;
use Magento\Quote\Api\Data\AddressInterface;
use Magento\Quote\Api\Data\CartInterface;
use Magento\Quote\Api\Data\ShippingMethodInterface;
use Magento\Quote\Api\ShipmentEstimationInterface;
use Magento\Quote\Model\MaskedQuoteIdToQuoteIdInterface;
use Magento\Quote\Model\Quote\AddressFactory;
use Magento\Quote\Model\Cart\ShippingMethodConverter;
use Magento\QuoteGraphQl\Model\FormatMoneyTypeData;

/**
* @inheritdoc
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class EstimateShippingMethods implements ResolverInterface
{
/**
* @param MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId
* @param CartRepositoryInterface $cartRepository
* @param AddressFactory $addressFactory
* @param ShipmentEstimationInterface $shipmentEstimation
* @param ExtensibleDataObjectConverter $dataObjectConverter
* @param ShippingMethodConverter $shippingMethodConverter
* @param FormatMoneyTypeData $formatMoneyTypeData
*/
public function __construct(
private MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId,
private CartRepositoryInterface $cartRepository,
private AddressFactory $addressFactory,
private ShipmentEstimationInterface $shipmentEstimation,
private ExtensibleDataObjectConverter $dataObjectConverter,
private ShippingMethodConverter $shippingMethodConverter,
private FormatMoneyTypeData $formatMoneyTypeData,
) {
}

/**
* @inheritdoc
*/
public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
{
$this->validateInput($args);
try {
$cart = $this->cartRepository->get($this->maskedQuoteIdToQuoteId->execute($args['input']['cart_id']));
} catch (NoSuchEntityException $ex) {
throw new GraphQlInputException(
__(
'Could not find a cart with ID "%masked_id"',
[
'masked_id' => $args['input']['cart_id']
]
)
);
}
return $this->getAvailableShippingMethodsForAddress($args, $cart);
}

/**
* Validates arguments passed to resolver
*
* @param array $args
* @throws GraphQlInputException
*/
private function validateInput(array $args)
{
if (empty($args['input']['cart_id'])) {
throw new GraphQlInputException(__('Required parameter "cart_id" is missing'));
}

if (empty($args['input'][AddressInterface::KEY_COUNTRY_ID])) {
throw new GraphQlInputException(
__(
'Required parameter "%country_id" is missing',
[
'country_id' => AddressInterface::KEY_COUNTRY_ID
]
)
);
}

if (isset($args['input']['address']) && empty($args['input']['address'])) {
throw new GraphQlInputException(__('Parameter(s) for address are missing'));
}

if (isset($args['input']['address']['region']) && empty($args['input']['address']['region'])) {
throw new GraphQlInputException(__('Parameter(s) for region are missing'));
}
}

/**
* Get the list of available shipping methods given a cart, country_id and optional customer address parameters
*
* @param array $args
* @param CartInterface $cart
* @return array
*/
private function getAvailableShippingMethodsForAddress(array $args, CartInterface $cart): array
{
/** @var $address AddressInterface */
$address = $this->addressFactory->create();
$shippingMethods = [];

$address->addData([
AddressInterface::KEY_COUNTRY_ID => $args['input'][AddressInterface::KEY_COUNTRY_ID]
]);
if (!empty($args['input']['address'])) {
$data = $args['input']['address'];
if (!empty($data['region'])) {
$address->addData([
AddressInterface::KEY_REGION => $data['region'][AddressInterface::KEY_REGION] ?? '',
AddressInterface::KEY_REGION_ID => $data['region'][AddressInterface::KEY_REGION_ID] ?? '',
AddressInterface::KEY_REGION_CODE => $data['region'][AddressInterface::KEY_REGION_CODE] ?? ''
]);
}
$address->addData([
AddressInterface::KEY_FIRSTNAME => $data[AddressInterface::KEY_FIRSTNAME] ?? '',
AddressInterface::KEY_LASTNAME => $data[AddressInterface::KEY_LASTNAME] ?? '',
AddressInterface::KEY_MIDDLENAME => $data[AddressInterface::KEY_MIDDLENAME] ?? '',
AddressInterface::KEY_PREFIX => $data[AddressInterface::KEY_PREFIX] ?? '',
AddressInterface::KEY_SUFFIX => $data[AddressInterface::KEY_SUFFIX] ?? '',
AddressInterface::KEY_VAT_ID => $data[AddressInterface::KEY_VAT_ID] ?? '',
AddressInterface::KEY_COMPANY => $data[AddressInterface::KEY_COMPANY] ?? '',
AddressInterface::KEY_TELEPHONE => $data[AddressInterface::KEY_TELEPHONE] ?? '',
AddressInterface::KEY_CITY => $data[AddressInterface::KEY_CITY] ?? '',
AddressInterface::KEY_STREET => $data[AddressInterface::KEY_STREET] ?? '',
AddressInterface::KEY_POSTCODE => $data[AddressInterface::KEY_POSTCODE] ?? '',
AddressInterface::KEY_FAX => $data[AddressInterface::KEY_FAX] ?? '',
AddressInterface::CUSTOM_ATTRIBUTES => $data[AddressInterface::CUSTOM_ATTRIBUTES] ?? ''
]);
}
foreach ($this->shipmentEstimation->estimateByExtendedAddress($cart->getId(), $address) as $method) {
$shippingMethods[] = $this->formatMoneyTypeData->execute(
$this->dataObjectConverter->toFlatArray($method, [], ShippingMethodInterface::class),
$cart->getCurrency()->getQuoteCurrencyCode()
);
}
return $shippingMethods;
}
}
Loading

0 comments on commit c9453c9

Please sign in to comment.