Magento 2 : Address additional attributes

Posted Almost 3 years ago. Visible to the public.

In this example, you can learn how to add additional attributes for customer address

Steps

  1. Add additional attributes to EAV table
  2. Create extension attributes to link with models to save additional attributes values in databases
  3. Create fieldset.xml file to copy the additional attributes value to order table
  4. Ui component customization to show the data in admin

Add additional attributes to EAV table

In this example, district and sub_district attributes will be added in the EAV table using following code


<?php
/**
 * CreateAddressAdditionalAttributes
 *
 * @copyright Copyright © 2021 Vasan. All rights reserved.
 * @author    psureshvasan@vasan.com
 */

namespace Vasan\Customer\Setup\Patch\Data;


use Magento\Customer\Model\Customer;
use Magento\Customer\Setup\CustomerSetupFactory;
use Magento\Eav\Model\Entity\Attribute\SetFactory as AttributeSetFactory;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Framework\Setup\Patch\PatchInterface;

class CreateAddressAdditionalAttributes implements DataPatchInterface
{

    /**
     * @var ModuleDataSetupInterface
     */
    protected $moduleDataSetup;

    /**
     * @var CustomerSetupFactory
     */
    protected $customerSetupFactory;

    /**
     * @var AttributeSetFactory
     */
    protected $attributeSetFactory;

    /**
     * CreateAddressAdditionalAttributes constructor.
     * @param ModuleDataSetupInterface $moduleDataSetup
     * @param CustomerSetupFactory $customerSetupFactory
     * @param AttributeSetFactory $attributeSetFactory
     */
    public function __construct(
        ModuleDataSetupInterface $moduleDataSetup,
        CustomerSetupFactory $customerSetupFactory,
        AttributeSetFactory $attributeSetFactory
    ) {
        $this->moduleDataSetup = $moduleDataSetup;
        $this->customerSetupFactory = $customerSetupFactory;
        $this->attributeSetFactory = $attributeSetFactory;
    }

    public static function getDependencies()
    {
        return [];
    }

    public function getAliases()
    {
        return [];
    }

    public function apply()
    {
        $customerSetup = $this->customerSetupFactory->create(['setup' => $this->moduleDataSetup]);

        $customerEntity = $customerSetup->getEavConfig()->getEntityType('customer_address');
        $attributeSetId = $customerEntity->getDefaultAttributeSetId();
        $attributeSet = $this->attributeSetFactory->create();
        $attributeDefaultGroupId = $attributeSet->getDefaultGroupId($attributeSetId);

        $customerSetup->removeAttribute('customer_address', 'district');
        $customerSetup->addAttribute('customer_address', 'district', [
            'type' => 'varchar',
            'label' => 'District',
            'input' => 'text',
            'required' => true,
            'sort_order' => 101,
            'position' => 101,
            'visible' => true,
            'system' => false,
            'user_defined'  => true,
            'group'            => 'General',
            'global'           => true,
            'visible_on_front' => true,
        ]);

        $district = $customerSetup->getEavConfig()->getAttribute('customer_address', 'district');

        if ($district->getId()) {
            $district->addData([
                'used_in_forms' => ['adminhtml_customer_address',
                    'customer_account_create', 'customer_register_address', 'customer_address_edit']
            ]);
            $district->save();
        }

        $customerSetup->removeAttribute('customer_address', 'sub_district');
        $customerSetup->addAttribute('customer_address', 'sub_district', [
            'type' => 'varchar',
            'label' => 'Sub District',
            'input' => 'text',
            'required' => true,
            'sort_order' => 102,
            'position' => 102,
            'visible' => true,
            'system' => false,
            'user_defined'  => true,
            'group'            => 'General',
            'global'           => true,
            'visible_on_front' => true,
        ]);

        $subDistrict = $customerSetup->getEavConfig()->getAttribute('customer_address', 'sub_district');

        if ($subDistrict->getId()) {
            $subDistrict->addData([
                'used_in_forms' => ['adminhtml_customer_address',
                    'customer_account_create', 'customer_register_address', 'customer_address_edit']
            ]);
            $subDistrict->save();
        }
    }
}



The extension_attributes.xml should be created inside etc folder


<?xml version="1.0"?>
<!--
/**
 * extension_attributes
 *
 * @copyright Copyright © 2021 Vasan. All rights reserved.
 * @author    psureshvasan@vasan.com
 */
-->

<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="district" type="string" />
        <attribute code="sub_district" type="string" />
    </extension_attributes>
     <extension_attributes for="Magento\Sales\Api\Data\OrderAddressInterface">
         <attribute code="district" type="string" />
         <attribute code="sub_district" type="string" />
   </extension_attributes>

    <extension_attributes for="Magento\Customer\Api\Data\AddressInterface">
        <attribute code="district" type="string" />
        <attribute code="sub_district" type="string" />
    </extension_attributes>
</config>



Create fieldset.xml file to copy the additional attributes value to order table

The fieldset.xml file should be created in etc folder


<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
 * fieldset
 *
 * @copyright Copyright © 2021 Vasan. All rights reserved.
 * @author    psureshvasan@vasan.com
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:DataObject/etc/fieldset.xsd">
    <scope id="global">
        <fieldset id="sales_convert_quote_address">
            <field name="sub_district">
                <aspect name="to_order_address" />
                <aspect name="to_customer_address" />
            </field>
            <field name="district">
                <aspect name="to_order_address" />
                <aspect name="to_customer_address" />
            </field>
        </fieldset>
        <fieldset id="order_address">
            <field name="sub_district">
                <aspect name="to_customer_address" />
            </field>
            <field name="district">
                <aspect name="to_customer_address" />
            </field>
        </fieldset>
    </scope>
</config>



Ui component customization to show the data in admin

The customer_address_form.xml file should be created to show additional attributes in backend address creation


<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd" component="Magento_Customer/js/form/components/form">

    <fieldset name="general">
        <field name="city" component="Magento_Ui/js/form/element/select" formElement="select">
            <settings>
                <validation>
                    <rule name="required-entry" xsi:type="boolean">true</rule>
                </validation>
                <dataType>text</dataType>
                <label translate="true">City</label>
            </settings>
            <formElements>
                <select>
                    <settings>
                        <options class="Vasan\Customer\Model\Config\Source\CityWithRegion"/>
                        <filterBy>
                            <field>region_id</field>
                            <target>${ $.provider }:${ $.parentScope }.region_id</target>
                        </filterBy>
                    </settings>
                </select>
            </formElements>
        </field>
        <field name="district" component="Magento_Ui/js/form/element/select" formElement="select">
            <settings>
                <validation>
                    <rule name="required-entry" xsi:type="boolean">true</rule>
                </validation>
                <dataType>text</dataType>
                <label translate="true">District</label>
            </settings>
            <formElements>
                <select>
                    <settings>
                        <options class="Vasan\Customer\Model\Config\Source\District"/>
                        <filterBy>
                            <field>city</field>
                            <target>${ $.provider }:${ $.parentScope }.city</target>
                        </filterBy>
                    </settings>
                </select>
            </formElements>
        </field>

        <field name="sub_district" component="Vasan_Customer/js/form/element/subdistrict" formElement="select">
            <settings>
                <dataType>text</dataType>
                <label translate="true">Sub District</label>
            </settings>
            <formElements>
                <select>
                    <settings>
                        <caption translate="true">-- Please Select a Sub District--</caption>
                    </settings>
                </select>
            </formElements>
        </field>

        <field name="postcode" component="Vasan_Customer/js/form/element/postcode" formElement="select">
            <settings>
                <dataType>text</dataType>
                <label translate="true">Postcode</label>
            </settings>
            <formElements>
                <select>
                    <settings>
                        <caption translate="true">-- Please Select a Postcode --</caption>
                    </settings>
                </select>
            </formElements>
        </field>
    </fieldset>
</form>


The custom select ui components are used to load the data of sub_district and postal code
The ui componets fetch the data based on the from previous component value and ajax is used to fetch only relevant data and avoid slowness.

subdistrict.js


define([
    'jquery',
    'underscore',
    'uiRegistry',
    'mage/storage',
    'Magento_Ui/js/form/element/select'

], function ($, _, registry, storage, Select) {
    'use strict';

    return Select.extend({
        defaults: {
            skipValidation: false,
            imports: {
                update: '${ $.parentName }.district:value'
            }
        },


        update: function (value) {
            var latestValue = this.value._latestValue;
            var serviceUrl  = '/rest/V1/directory/getdistrictdata/' + value;
            var self = this;
            if (value !== '') {
                $('body').trigger('processStart');
                storage.get(serviceUrl)
                    .done(function (response) {
                        var dataString = JSON.stringify(response);
                        var data = JSON.parse(dataString) ;
                        self.setOptions(data);
                        self.source.set(self.dataScope, latestValue);
                        $('body').trigger('processStop');
                    }).fail(function (response) {

                    $('body').trigger('processStop');
                });
            }
        }
    });
});


postcode.js


define([
    'jquery',
    'underscore',
    'uiRegistry',
    'mage/storage',
    'Magento_Ui/js/form/element/select'

], function ($, _, registry, storage, Select) {
    'use strict';

    return Select.extend({
        defaults: {
            skipValidation: false,
            savedPostcode: '',
            imports: {
                update: '${ $.parentName }.sub_district:value'
            }
        },

        /**
         *
         *
         * @param {String} value - country
         */
        setDifferedFromDefault: function (value) {
            this._super();

            if (value) {
                this.savedPostcode = value;
            }
        },

        update: function (value) {
            var serviceUrl  = '/rest/V1/directory/getpostcodedata/' + value;
            var self = this;
            if (value !== '') {

                $('body').trigger('processStart');
                storage.get(serviceUrl)
                    .done(function (response) {
                        var dataString = JSON.stringify(response);
                        var data = JSON.parse(dataString) ;
                        self.setOptions(data);
                        self.source.set(self.dataScope, self.savedPostcode);
                        $('body').trigger('processStop');
                    }).fail(function (response) {

                    $('body').trigger('processStop');
                });
            }

        }
    });
});


vasan
Last edit
Almost 3 years ago
vasan
Keywords
customer, address, attributes
Posted by vasan to vasan's deck (2021-07-23 15:30)