Magento 2ドロップダウンリストを配送方法に追加


16

物流会社の配送方法を開発しています。この会社には、顧客が注文を受け取ることができる多くのオフィスがあります。APIで市区町村別にオフィスのリストを取得できますが、このステップをどれほどうまく表現できないのでしょうか?

今のところ、私\Magento\Quote\Model\Quote\Address\RateResult\Method は町のすべてのオフィスに新しく設定しました。大きな町では数が100を超えており、チェックアウトで100行を設定するのはあまり良くないと思います。

さまざまなチェックアウトデザインのパブリックモジュールになるため、選択した配送方法の近くにオフィスのリストを含むドロップダウンリストを表示し、ユーザーが選択した後に価格と方法を設定するにはどうすればよいですか。


@Zefirynこの投稿は非常に興味深いものでしたが、オフィスではなくAmastyのモジュール内にある店舗をセレクトで表示する必要がある場合、質問の2番目の部分をどのように行うのでしょうか?つまり、XMLコンポーネント「vendor_carrier_form」を埋めるためにAmastyのヘルパーを呼び出す場所はどこですか。おかげで
maverickk89

新しい質問がある場合は、[ 質問する ]ボタンをクリックして質問してください。コンテキストの提供に役立つ場合は、この質問へのリンクを含めてください。- 口コミより
ジャイ

それがあるように私はポストの最初の部分を使用するので、これは...新しい質問が、Zefirynで使用される方法のバリエーションではありません
maverickk89

回答:


17

Magentoのチェックアウトでは、配送方法の追加データのフォームはサポートされていません。しかし、これはshippingAdditionalチェックアウトでブロックを提供し、これを使用できます。次のソリューションは、標準のmagentoチェックアウトで機能します。

まず、何らかのフォームを配置できるコンテナーを準備しましょう。これを行うには、ファイルを作成しますview/frontend/layout/checkout_index_index.xml

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="checkout.root">
            <arguments>
                <argument name="jsLayout" xsi:type="array">
                    <item name="components" xsi:type="array">
                        <item name="checkout" xsi:type="array">
                            <item name="children" xsi:type="array">
                                <item name="steps" xsi:type="array">
                                    <item name="children" xsi:type="array">
                                        <item name="shipping-step" xsi:type="array">
                                            <item name="children" xsi:type="array">
                                                <item name="shippingAddress" xsi:type="array">
                                                    <item name="children" xsi:type="array">
                                                        <item name="shippingAdditional" xsi:type="array">
                                                            <item name="component" xsi:type="string">uiComponent</item>
                                                            <item name="displayArea" xsi:type="string">shippingAdditional</item>
                                                            <item name="children" xsi:type="array">
                                                                <item name="vendor_carrier_form" xsi:type="array">
                                                                    <item name="component" xsi:type="string">Vendor_Module/js/view/checkout/shipping/form</item>
                                                                </item>
                                                            </item>
                                                        </item>
                                                    </item>
                                                </item>
                                            </item>
                                        </item>
                                    </item>
                                </item>
                            </item>
                        </item>
                    </item>
                </argument>
            </arguments>
        </referenceBlock>
    </body>
</page>

ここでファイルを作成します Vendor/Module/view/frontend/web/js/view/checkout/shipping/form.js、ノックアウトテンプレートをレンダリングする。その内容は次のようになります

define([
    'jquery',
    'ko',
    'uiComponent',
    'Magento_Checkout/js/model/quote',
    'Magento_Checkout/js/model/shipping-service',
    'Vendor_Module/js/view/checkout/shipping/office-service',
    'mage/translate',
], function ($, ko, Component, quote, shippingService, officeService, t) {
    'use strict';

    return Component.extend({
        defaults: {
            template: 'Vendor_Module/checkout/shipping/form'
        },

        initialize: function (config) {
            this.offices = ko.observableArray();
            this.selectedOffice = ko.observable();
            this._super();
        },

        initObservable: function () {
            this._super();

            this.showOfficeSelection = ko.computed(function() {
                return this.ofices().length != 0
            }, this);

            this.selectedMethod = ko.computed(function() {
                var method = quote.shippingMethod();
                var selectedMethod = method != null ? method.carrier_code + '_' + method.method_code : null;
                return selectedMethod;
            }, this);

            quote.shippingMethod.subscribe(function(method) {
                var selectedMethod = method != null ? method.carrier_code + '_' + method.method_code : null;
                if (selectedMethod == 'carrier_method') {
                    this.reloadOffices();
                }
            }, this);

            this.selectedOffice.subscribe(function(office) {
                if (quote.shippingAddress().extensionAttributes == undefined) {
                    quote.shippingAddress().extensionAttributes = {};
                }
                quote.shippingAddress().extensionAttributes.carrier_office = office;
            });


            return this;
        },

        setOfficeList: function(list) {
            this.offices(list);
        },

        reloadOffices: function() {
            officeService.getOfficeList(quote.shippingAddress(), this);
            var defaultOffice = this.offices()[0];
            if (defaultOffice) {
                this.selectedOffice(defaultOffice);
            }
        },

        getOffice: function() {
            var office;
            if (this.selectedOffice()) {
                for (var i in this.offices()) {
                    var m = this.offices()[i];
                    if (m.name == this.selectedOffice()) {
                        office = m;
                    }
                }
            }
            else {
                office = this.offices()[0];
            }

            return office;
        },

        initSelector: function() {
            var startOffice = this.getOffice();
        }
    });
});

このファイルは、に配置する必要があるノックアウトテンプレートを使用します Vendor/Module/view/frontend/web/template/checkout/shipping/form.html

<div id="carrier-office-list-wrapper" data-bind="visible: selectedMethod() == 'carrier_method'">
    <p data-bind="visible: !showOfficeSelection(), i18n: 'Please provide postcode to see nearest offices'"></p>
    <div data-bind="visible: showOfficeSelection()">
        <p>
            <span data-bind="i18n: 'Select pickup office.'"></span>
        </p>
        <select id="carrier-office-list" data-bind="options: offices(),
                                            value: selectedOffice,
                                            optionsValue: 'name',
                                            optionsText: function(item){return item.location + ' (' + item.name +')';}">
        </select>
    </div>
</div>

これで、メソッド(コードで定義)が配送方法テーブルで選択されるときに表示される選択フィールドができました。いくつかのオプションでそれを埋める時間。値はアドレスに依存するため、最良の方法は、利用可能なオプションを提供するレストエンドポイントを作成することです。にVendor/Module/etc/webapi.xml

<?xml version="1.0"?>

<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">

    <!-- Managing Office List on Checkout page -->
    <route url="/V1/module/get-office-list/:postcode/:city" method="GET">
        <service class="Vendor\Module\Api\OfficeManagementInterface" method="fetchOffices"/>
        <resources>
            <resource ref="anonymous" />
        </resources>
    </route>
</routes>

インターフェースを次のVendor/Module/Api/OfficeManagementInterface.phpように定義します

namespace Vendor\Module\Api;

interface OfficeManagementInterface
{

    /**
     * Find offices for the customer
     *
     * @param string $postcode
     * @param string $city
     * @return \Vendor\Module\Api\Data\OfficeInterface[]
     */
    public function fetchOffices($postcode, $city);
}

でオフィスデータのインターフェイスを定義しますVendor\Module\Api\Data\OfficeInterface.php。このインターフェイスは、出力用のデータをフィルタリングするためにwebapiモジュールによって使用されるため、応答に追加する必要があるものは何でも定義する必要があります。

namespace Vendor\Module\Api\Data;

/**
 * Office Interface
 */
interface OfficeInterface
{
    /**
     * @return string
     */
    public function getName();

    /**
     * @return string
     */
    public function getLocation();
}

実際のクラスの時間。ですべてのインターフェイスの設定を作成することから始めますVendor/Module/etc/di.xml

<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Vendor\Module\Api\OfficeManagementInterface" type="Vendor\Module\Model\OfficeManagement" />
    <preference for="Vendor\Module\Api\Data\OfficeInterface" type="Vendor\Module\Model\Office" />
</config>

次にVendor\Module\Model\OfficeManagement.php、実際にデータを取得するロジックを実行するクラスを作成します。

namespace Vednor\Module\Model;

use Vednor\Module\Api\OfficeManagementInterface;
use Vednor\Module\Api\Data\OfficeInterfaceFactory;

class OfficeManagement implements OfficeManagementInterface
{
    protected $officeFactory;

    /**
     * OfficeManagement constructor.
     * @param OfficeInterfaceFactory $officeInterfaceFactory
     */
    public function __construct(OfficeInterfaceFactory $officeInterfaceFactory)
    {
        $this->officeFactory = $officeInterfaceFactory;
    }

    /**
     * Get offices for the given postcode and city
     *
     * @param string $postcode
     * @param string $limit
     * @return \Vendor\Module\Api\Data\OfficeInterface[]
     */
    public function fetchOffices($postcode, $city)
    {
        $result = [];
        for($i = 0, $i < 4;$i++) {
            $office = $this->officeFactory->create();
            $office->setName("Office {$i}");
            $office->setLocation("Address {$i}");
            $result[] = $office;
        }

        return $result;
    }
}

そして最後OfficeInterfaceVendor/Module/Model/Office.php

namespace Vendor\Module\Model;

use Magento\Framework\DataObject;
use Vendor\Module\Api\Data\OfficeInterface;

class Office extends DataObject implements OfficeInterface
{
    /**
     * @return string
     */
    public function getName()
    {
        return (string)$this->_getData('name');
    }

    /**
     * @return string
     */
    public function getLocation()
    {
        return (string)$this->_getData('location');
    }
}

これにより、選択フィールドが表示され、アドレスが変更されたときに更新されます。しかし、フロントエンド操作のためのもう1つの要素が欠落しています。エンドポイントを呼び出す関数を作成する必要があります。それへの呼び出しがすでに含まれているVendor/Module/view/frontend/web/js/view/checkout/shipping/form.jsし、それがあるVendor_Module/js/view/checkout/shipping/office-serviceに行くべきクラスVendor/Module/view/frontend/web/js/view/checkout/shipping/office-service.js次のコードで:

define(
    [
        'Vendor_Module/js/view/checkout/shipping/model/resource-url-manager',
        'Magento_Checkout/js/model/quote',
        'Magento_Customer/js/model/customer',
        'mage/storage',
        'Magento_Checkout/js/model/shipping-service',
        'Vendor_Module/js/view/checkout/shipping/model/office-registry',
        'Magento_Checkout/js/model/error-processor'
    ],
    function (resourceUrlManager, quote, customer, storage, shippingService, officeRegistry, errorProcessor) {
        'use strict';

        return {
            /**
             * Get nearest machine list for specified address
             * @param {Object} address
             */
            getOfficeList: function (address, form) {
                shippingService.isLoading(true);
                var cacheKey = address.getCacheKey(),
                    cache = officeRegistry.get(cacheKey),
                    serviceUrl = resourceUrlManager.getUrlForOfficeList(quote);

                if (cache) {
                    form.setOfficeList(cache);
                    shippingService.isLoading(false);
                } else {
                    storage.get(
                        serviceUrl, false
                    ).done(
                        function (result) {
                            officeRegistry.set(cacheKey, result);
                            form.setOfficeList(result);
                        }
                    ).fail(
                        function (response) {
                            errorProcessor.process(response);
                        }
                    ).always(
                        function () {
                            shippingService.isLoading(false);
                        }
                    );
                }
            }
        };
    }
);

さらに2つのjsファイルを使用します。Vendor_Module/js/view/checkout/shipping/model/resource-url-managerエンドポイントへのURLを作成し、非常に簡単です

define(
    [
        'Magento_Customer/js/model/customer',
        'Magento_Checkout/js/model/quote',
        'Magento_Checkout/js/model/url-builder',
        'mageUtils'
    ],
    function(customer, quote, urlBuilder, utils) {
        "use strict";
        return {
            getUrlForOfficeList: function(quote, limit) {
                var params = {postcode: quote.shippingAddress().postcode, city: quote.shippingAddress().city};
                var urls = {
                    'default': '/module/get-office-list/:postcode/:city'
                };
                return this.getUrl(urls, params);
            },

            /** Get url for service */
            getUrl: function(urls, urlParams) {
                var url;

                if (utils.isEmpty(urls)) {
                    return 'Provided service call does not exist.';
                }

                if (!utils.isEmpty(urls['default'])) {
                    url = urls['default'];
                } else {
                    url = urls[this.getCheckoutMethod()];
                }
                return urlBuilder.createUrl(url, urlParams);
            },

            getCheckoutMethod: function() {
                return customer.isLoggedIn() ? 'customer' : 'guest';
            }
        };
    }
);

Vendor_Module/js/view/checkout/shipping/model/office-registry結果をローカルストレージに保持する方法です。そのコードは次のとおりです。

define(
    [],
    function() {
        "use strict";
        var cache = [];
        return {
            get: function(addressKey) {
                if (cache[addressKey]) {
                    return cache[addressKey];
                }
                return false;
            },
            set: function(addressKey, data) {
                cache[addressKey] = data;
            }
        };
    }
);

さて、フロントエンドで作業する必要があります。しかし今、解決すべき別の問題があります。チェックアウトはこのフォームについて何も知らないので、選択結果をバックエンドに送信しません。これを実現するには、extension_attributes機能を使用する必要があります。これはmagento2で、残りの呼び出しにいくつかの追加データが予想されることをシステムに通知する方法です。それなしでは、magentoはそれらのデータを除外し、コードに到達することはありません。

最初にVendor/Module/etc/extension_attributes.xml定義する:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Api/etc/extension_attributes.xsd">
    <extension_attributes for="Magento\Quote\Api\Data\AddressInterface">
        <attribute code="carrier_office" type="string"/>
    </extension_attributes>
</config>

この値はform.jsthis.selectedOffice.subscribe()定義により既にリクエストに挿入されています。したがって、上記の構成では、入り口でのみ通過します。コードで取得するには、プラグインを作成しますVendor/Module/etc/di.xml

<type name="Magento\Quote\Model\Quote\Address">
    <plugin name="inpost-address" type="Vendor\Module\Quote\AddressPlugin" sortOrder="1" disabled="false"/>
</type>

そのクラスの中

namespace Vendor\Module\Plugin\Quote;

use Magento\Quote\Model\Quote\Address;
use Vendor\Module\Model\Carrier;

class AddressPlugin
{
    /**
     * Hook into setShippingMethod.
     * As this is magic function processed by __call method we need to hook around __call
     * to get the name of the called method. after__call does not provide this information.
     *
     * @param Address $subject
     * @param callable $proceed
     * @param string $method
     * @param mixed $vars
     * @return Address
     */
    public function around__call($subject, $proceed, $method, $vars)
    {
        $result = $proceed($method, $vars);
        if ($method == 'setShippingMethod'
            && $vars[0] == Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE
            && $subject->getExtensionAttributes()
            && $subject->getExtensionAttributes()->getCarrierOffice()
        ) {
            $subject->setCarrierOffice($subject->getExtensionAttributes()->getCarrierOffice());
        }
        elseif (
            $method == 'setShippingMethod'
            && $vars[0] != Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE
        ) {
            //reset office when changing shipping method
            $subject->getCarrierOffice(null);
        }
        return $result;
    }
}

もちろん、どこに値を保存するかは、要件に完全に依存します。上記のコードでは、テーブルとイベント()に追加の列carrier_officeを作成する必要がありますquote_addresssales_addressVendor/Module/etc/events.xml

<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    <event name="sales_model_service_quote_submit_before">
        <observer name="copy_carrier_office" instance="Vendor\Module\Observer\Model\Order" />
    </event>
</config>

これにより、見積住所に保存されたデータが販売住所にコピーされます。

ポーランド語のキャリアInPostのモジュール用にこれを書いたので、コードを壊すかもしれないいくつかの名前を変更しましたが、これがあなたに必要なものを与えることを望みます。

[編集]

@sanganが尋ねたキャリアモデル

namespace Vendor\Module\Model;

use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Phrase;
use Magento\Quote\Model\Quote\Address\RateRequest;
use Magento\Shipping\Model\Carrier\AbstractCarrier;
use Magento\Shipping\Model\Carrier\CarrierInterface;
use Magento\Shipping\Model\Simplexml\ElementFactory;

class Carrier extends AbstractCarrier implements CarrierInterface
{
    const CARRIER_CODE = 'mycarier';

    const METHOD_CODE = 'mymethod';

    /** @var string */
    protected $_code = self::CARRIER_CODE;

    /** @var bool */
    protected $_isFixed = true;

    /**
     * Prepare stores to show on frontend
     *
     * @param RateRequest $request
     * @return \Magento\Framework\DataObject|bool|null
     */
    public function collectRates(RateRequest $request)
    {
        if (!$this->getConfigData('active')) {
            return false;
        }

        /** @var \Magento\Shipping\Model\Rate\Result $result */
        $result = $this->_rateFactory->create();

        /** @var \Magento\Quote\Model\Quote\Address\RateResult\Method $method */
        $method = $this->_rateMethodFactory->create();
        $method->setCarrier($this->_code);
        $method->setCarrierTitle($this->getConfigData('title'));

        $price = $this->getFinalPriceWithHandlingFee(0);
        $method->setMethod(self::METHOD_CODE);
        $method->setMethodTitle(new Phrase('MyMethod'));
        $method->setPrice($price);
        $method->setCost($price);
        $result->append($method);;

        return $result;
    }


    /**
     * @return array
     */
    public function getAllowedMethods()
    {
        $methods = [
            'mymethod' => new Phrase('MyMethod')
        ];
        return $methods;
    }
}

延長された返信をありがとう、私はあなたの方法を使用して私の問題を解決しようとし、最近結果を返信します。
Siarhey Uchukhlebau

@Zefiryn私はカスタム配送方法を作成しました、その下に顧客の配送アカウント番号を持つドロップダウンが表示されます(カスタム顧客属性が作成されています)ので、このドロップダウンを表示する必要がある場合、コードの何パーセントが役立ちますか?あなたが提供したコードから何を拾うべきですか?
シリーンN

@shireen約70%と言います。マシンを取得する部分をアカウント番号に変更する必要があります。APIの定義はわずかに異なり、jsはその一部です
-Zefiryn

私は、このモジュールを試してみました...しかし、そののでどんなmodule.if作業共有してくださいすべての変更が表示されない
sangan

成功したモジュールを追加した後..チェックアウトajaxの読み込みを続けます。以下のようなコンソールエラーで:require.js:166 Uncaught Error:Script error for:Vendor_Module / js / view / checkout / shipping / model / office-registry。 requirejs.org/docs/errors.html#scripterror
サンガン

2

以前に提供されていたものを、歪めずに拡張する新しい答えを追加しています。

これは、次のものQuoteAddressPluginにフックしていたルートです。

1. Magento\Checkout\Api\ShippingInformationManagementInterface::saveAddressInformation()
2. Magento\Quote\Model\QuoteRepository::save() 
3. Magento\Quote\Model\QuoteRepository\SaveHandler::save() 
4. Magento\Quote\Model\QuoteRepository\SaveHandler::processShippingAssignment() 
5. Magento\Quote\Model\Quote\ShippingAssignment\ShippingAssignmentPersister::save()
6. Magento\Quote\Model\Quote\ShippingAssignment\ShippingAssignmentProcessor::save()
7. Magento\Quote\Model\Quote\ShippingAssignment\ShippingProcessor::save()
8. Magento\Quote\Model\ShippingMethodManagement::apply() 

最後のメソッドは、Magento\Quote\Model\Quote\Address::setShippingMethod()実際に呼び出した呼び出しでしMagento\Quote\Model\Quote\Address::__call()た。今、私はプラグインのためのより良い場所を見つけました、それはMagento\Quote\Model\ShippingAssignment::setShipping()メソッドです。したがって、プラグイン部分は次のように書き換えることができます。

<type name="Magento\Quote\Model\ShippingAssignment">
    <plugin name="carrier-office-plugin" type="Vendor\Module\Plugin\Quote\ShippingAssignmentPlugin" sortOrder="1" disabled="false"/>
</type>

プラグイン自体:

namespace Vednor\Module\Plugin\Quote;

use Magento\Quote\Api\Data\AddressInterface;
use Magento\Quote\Api\Data\ShippingInterface;
use Magento\Quote\Model\ShippingAssignment;
use Vendor\Module\Model\Carrier;

/**
 * ShippingAssignmentPlugin
 */
class ShippingAssignmentPlugin
{
    /**
     * Hook into setShipping.
     *
     * @param ShippingAssignment $subject
     * @param ShippingInterface $value
     * @return Address
     */
    public function beforeSetShipping($subject, ShippingInterface $value)
    {
        $method = $value->getMethod();
        /** @var AddressInterface $address */
        $address = $value->getAddress();
        if ($method === Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE
            && $address->getExtensionAttributes()
            && $address->getExtensionAttributes()->getCarrierOffice()
        ) {
            $address->setCarrierOffice($address->getExtensionAttributes()->getCarrierOffice());
        }
        elseif ($method !== Carrier::CARRIER_CODE.'_'.Carrier::METHOD_CODE) {
            //reset inpost machine when changing shipping method
            $address->setCarrierOffice(null);
        }
        return [$value];
    }
}

1

@Zefiryn、私は問題に遭遇しました: quote.shippingAddress().extensionAttributes.carrier_office = office;

ゲストとして初めてチェックアウト(新しいプライベートウィンドウ)に入ると(ただし、登録済みクライアントでも同じことが起こります)、最初の「次へ」の後、属性officeがデータベースに保存されません。コンソールでは正しい出力が表示されますが:console.log(quote.shippingAddress().extensionAttributes.carrier_office);

最初のチェックアウトページに戻り、再度officeを選択すると、保存されます。この動作の理由は何でしょうか?

私は使用しようとしました: address.trigger_reload = new Date().getTime(); rateRegistry.set(address.getKey(), null); rateRegistry.set(address.getCacheKey(), null); quote.shippingAddress(address);

しかし、成功せずに...


0

@Zefiryn、上記のプラグインがどのように機能するかを簡単に説明できますか?私が知っているように、特定のオブジェクトに存在しないメソッドを実行しようとすると__callメソッドが実行されるため、少し混乱しています。app / code / Magento / Quote / Model / Quote / Address.phpにはそのようなメソッドが表示されないので、コメントのみです:

/** * Sales Quote address model ... * @method Address setShippingMethod(string $value)

  1. メソッドの実装がないときに、傍受を使用するのはなぜですか?
  2. 次の私が見る$subject->setInpostMachine$subject->getCarrierOffice(null);んが、それは何のsetInpostMachineが(存在しないため、上記のプラグインのメソッドが再び実行されることを意味する)と住所クラスでgetCarrierOffice()メソッド?私にはループのように見えます。
  3. Magentoはどこから実行しsetShippingMethod()ますか?この方法は通常どのように使用されますか?Magentoコードで類似のインターセプトを見つけることはできません。

さて、テスト用に書いたモジュールに基づいて答えを用意しました。inpost_machineフィールドを使用しているため、この場所でcarrier_officeに正しく変更されませんでした。第二に、私はこのモジュールを開発していたときにsetShippingMethodAddressInterfaceオブジェクトの呼び出しを除いて、選択されたキャリアとアドレスの両方を拡張属性で送信できる場所を見つけられませんでした。そのようなメソッドがないため、around__callを使用してsetShippingMethod呼ばれた、または他の魔法のフィールド。今より良い場所を見つけたので、新しい返信で投稿します。
ゼフィリン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.