Question

In the UK, we refer to our postcodes as either "SW1A 0AA" or "SW1A0AA" (with/without spaces).

The issue with this, is that when you Filter your Orders by Shipping Address (backend Sales Order Grid) you get 2 different results depending on whether you enter "SW1A 0AA" or "SW1A0AA".

enter image description here

Is there a wildcard or other method I could implement that would allow either entry to return the same results?

Was it helpful?

Solution

Magento 2 out-of-box functionality does not allow filtering a postcode with different variations of additional characters or white spaces. It is basically what you typed, what you get approach.

Magento 2 Sales Order Grid is based on the UI Component functionality. Part of that, the Magento\Framework\View\Element\UiComponent\DataProvider\FilterPool class is used to provide an extension point for filtering purposes. With this in mind, additional applier classes that represent custom logic to help form collection filters can be used.

We can see, how this class is configured inside the vendor/magento/module-sales/etc/di.xml configuration file.

<type name="Magento\Framework\View\Element\UiComponent\DataProvider\FilterPool">
    <arguments>
        <argument name="appliers" xsi:type="array">
            <item name="regular" xsi:type="object">Magento\Framework\View\Element\UiComponent\DataProvider\RegularFilter</item>
            <item name="fulltext" xsi:type="object">Magento\Framework\View\Element\UiComponent\DataProvider\FulltextFilter</item>
        </argument>
    </arguments>
</type>

Here is the implementation of the Magento\Framework\View\Element\UiComponent\DataProvider\RegularFilter::apply() method.

public function apply(Collection $collection, Filter $filter)
{
    $collection->addFieldToFilter($filter->getField(), [$filter->getConditionType() => $filter->getValue()]);
}

This is what the Shipping Address and Postcode fields search queries use in order to filter a collection of orders.

It means, that in order to provide custom logic for the Shipping Address search function, we should implement our own applier class and introduce the logic that will help to show orders for both postcode values including "SW1A 0AA" or "SW1A0AA".

Solution

The Postcode applier class can look like the below:

<?php declare(strict_types=1);

namespace Pronko\Postcode\UiComponent\DataProvider;

use Magento\Framework\Data\Collection;
use Magento\Framework\Api\Filter;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\View\Element\UiComponent\DataProvider\FilterApplierInterface;

class PostcodeFilter implements FilterApplierInterface
{
    /**
     * @param Collection $collection
     * @param Filter $filter
     * @throws LocalizedException
     */
    public function apply(Collection $collection, Filter $filter)
    {
        if ($this->isPostCodeField($filter)) {
            $postCodeCondition = $this->getPostCodeCondition($filter);
            $collection->addFieldToFilter($this->getPostCodeField($filter, $postCodeCondition), $postCodeCondition);
        } else {
            $collection->addFieldToFilter($filter->getField(), [$filter->getConditionType() => $filter->getValue()]);
        }
    }

    /**
     * @param Filter $filter
     * @return bool
     */
    private function isPostCodeField(Filter $filter): bool
    {
        return 'postcode' === substr($filter->getField(), -8);
    }

    /**
     * @param Filter $filter
     * @return array
     */
    private function getPostCodeCondition(Filter $filter): array
    {
        $postcodes = $this->getPostCodeValues($filter->getValue());

        $result = [];
        foreach ($postcodes as $key => $value) {
            $result['key_' . $key] = [$filter->getConditionType() => sprintf('%%%s%%', $value)];
        }
        return $result;
    }

    /**
     * @param string $postcode
     * @return array
     */
    private function getPostCodeValues(string $postcode): array
    {
        $cleanPostcode = preg_replace("/[^A-Za-z0-9]/", '', $postcode);
        $cleanPostcode = strtoupper($cleanPostcode);

        $result[] = $cleanPostcode;
        if (strlen($cleanPostcode) === 5) {
            $postcode = substr($cleanPostcode, 0, 2) . ' ' . substr($cleanPostcode, 2, 3);
        } elseif (strlen($cleanPostcode) === 6) {
            $postcode = substr($cleanPostcode, 0, 3) . ' ' . substr($cleanPostcode, 3, 3);
        } elseif (strlen($cleanPostcode) === 7) {
            $postcode = substr($cleanPostcode, 0, 4) . ' ' . substr($cleanPostcode, 4, 3);
        }
        $result[] = $postcode;
        return $result;
    }

    /**
     * @param Filter $filter
     * @param $condition
     * @return array
     */
    private function getPostCodeField(Filter $filter, $condition): array
    {
        $result = [];
        foreach (array_keys($condition) as $key) {
            $result[$key] = $filter->getField();
        }
        return $result;
    }
}

The apply() method will check that the filter name is a field, we are looking for. It should match the postcode value. The name of the filter can also include additional prefix information, we should just skip it for the purpose of finding the postcode.

The getPostCodeCondition() method returns a list of 2 postcode values, with and without space inside. For the UK postcodes, it is very important to find the right length of a postcode field.

Finally, in order to add the right amount of fields into the addFieldToFilter(), we use the getPostCodeField() method to return the correct keys and field names.

The Magento\Framework\Data\Collection\AbstractDb::addFieldToFilter() method will check that we pass 2 fields and 2 conditions and form the MySQL OR condition with the same name of the field and 2 values for the postcode.

The next step is to add this applier into the FilterPool list of applier via the Pronko\Postcode\etc\di.xml configuration file.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\View\Element\UiComponent\DataProvider\FilterPool">
        <arguments>
            <argument name="appliers" xsi:type="array">
                <item name="like" xsi:type="object">Pronko\Postcode\UiComponent\DataProvider\PostcodeFilter</item>
            </argument>
        </arguments>
    </type>
</config>

After cleaning config cache with bin/magento cache:clean config command, the new filtering logic should work.

Licensed under: CC-BY-SA with attribution
Not affiliated with magento.stackexchange
scroll top