magento2: Magento 2.1 Customer address attribute is not saved into database

Preconditions

  1. Magento 2.1.1

Steps to reproduce

  1. Add new custom attribute via InstallData.php with install function content:

     /** @var \Magento\Customer\Setup\CustomerSetup $customerSetup */
      $customerSetup = $this->customerSetupFactory->create(['setup' => $setup]);
    
      $attributesInfo = [
          'company_info' => [
              'label' => 'Company Info',
              'type' => 'static',
              'input' => 'textarea',
              'position' => 65,
              'visible' => true,
              'system' => false,
              'user_defined' => false,
              'required' => false,
              'validate_rules' => 'a:1:{s:15:"max_text_length";i:500;}'
          ],
          'telephone_secondary' => [
              'label' => 'Secondary Phone Number',
              'type' => 'static',
              'input' => 'text',
              'position' => 125,
              'visible' => true,
              'system' => false,
              'user_defined' => false,
              'required' => false,
          ]
      ];
    
      $columns = [
          'company_info' => [
              'type' => Table::TYPE_TEXT,
              'nullable' => true,
              'default' => null,
              'comment' => 'Company Info',
          ],
          'telephone_secondary' => [
              'type' => Table::TYPE_TEXT,
              'nullable' => true,
              'default' => null,
              'length' => 255,
              'comment' => 'Secondary Phone Number',
          ]
      ];
    
      $tableNames = [
          'customer_address_entity',
          'quote_address',
          'sales_order_address'
      ];
    
      foreach ($attributesInfo as $attributeCode => $attributeParams) {
          $customerSetup->addAttribute('customer_address', $attributeCode, $attributeParams);
          $customerSetup->getEavConfig()->getAttribute('customer_address', $attributeCode)
              ->setData('attribute_set_id', AddressMetadataInterface::ATTRIBUTE_SET_ID_ADDRESS)
              ->setData('used_in_forms', [
                  'customer_address_edit',
                  'customer_register_address',
                  'adminhtml_customer_address'
              ])
              ->save();
      }
    
      $connection = $setup->getConnection();
      foreach ($tableNames as $tableName) {
          foreach ($columns as $name => $definition) {
              $connection->addColumn($tableName, $name, $definition);
          }
      }
    
  2. Add fieldset.xml with following content:

<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:DataObject/etc/fieldset.xsd">
    <scope id="global">
        <fieldset id="customer_address">
            <field name="company_info">
                <aspect name="create" />
                <aspect name="update" />
                <aspect name="to_quote_address" />
            </field>
            <field name="telephone_secondary">
                <aspect name="create" />
                <aspect name="update" />
                <aspect name="to_quote_address" />
            </field>
        </fieldset>
        <fieldset id="sales_convert_order_address">
            <field name="company_info">
                <aspect name="to_quote_address" />
            </field>
            <field name="telephone_secondary">
                <aspect name="to_quote_address" />
            </field>
        </fieldset>
        <fieldset id="sales_convert_quote_address">
            <field name="company_info">
                <aspect name="to_order_address" />
                <aspect name="to_customer_address" />
            </field>
            <field name="telephone_secondary">
                <aspect name="to_order_address" />
                <aspect name="to_customer_address" />
            </field>
        </fieldset>
        <fieldset id="sales_copy_order_billing_address">
            <field name="company_info">
                <aspect name="to_order" />
            </field>
            <field name="telephone_secondary">
                <aspect name="to_order" />
            </field>
        </fieldset>
        <fieldset id="order_address">
            <field name="company_info">
                <aspect name="to_customer_address" />
            </field>
            <field name="telephone_secondary">
                <aspect name="to_customer_address" />
            </field>
        </fieldset>
    </scope>
</config>
  1. Go to checkout
  2. Insert data for new address (custom attributes are visible - that is correct)
  3. Go to billing step

Expected result

  1. Custom attributes values should be saved into database

Actual result

  1. Attributes values are not saved

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 27 (11 by maintainers)

Commits related to this issue

Most upvoted comments

I retract myself, in EE 2.14 the customers address attributes created in the backend are saved to the database when a address is created in the checkout.

Following the admin save controller used I create this install script for the attribute:

<?php

namespace MyVendor\MyModlue\Setup;

use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;

use Magento\Eav\Model\Entity\Attribute\SetFactory as AttributeSetFactory;
use Magento\Customer\Model\AttributeFactory;
use Magento\Store\Model\WebsiteFactory;
use Magento\Eav\Model\Config;

/**
 * @codeCoverageIgnore
 */
class InstallData implements InstallDataInterface
{
    /**
     * @var \Magento\Customer\Model\AttributeFactory
     */
    private $_attrFactory;

    /**
     * @var \Magento\Eav\Model\Entity\Attribute\SetFactory
     */
    private $_attrSetFactory;

    /**
     * @var \Magento\Store\Model\WebsiteFactory
     */
    private $websiteFactory;

    /**
     * @var \Magento\Eav\Model\Config
     */
    protected $_eavConfig;

    /**
     * Customer Address Entity Type instance
     *
     * @var \Magento\Eav\Model\Entity\Type
     */
    protected $_entityType;
    
    public function __construct(
        AttributeFactory $attrFactory,
        WebsiteFactory $websiteFactory,
        Config $eavConfig,
        AttributeSetFactory $attrSetFactory
    )
    {   
        $this->_attrFactory = $attrFactory;
        $this->_attrSetFactory = $attrSetFactory;
        $this->websiteFactory = $websiteFactory;
        $this->_eavConfig = $eavConfig;
    }

    public function install(ModuleDataSetupInterface $setup,
                            ModuleContextInterface $context)
    {

        /** @var $attribute \Magento\Customer\Model\Attribute */
        $attribute = $this->_attrFactory->create();
        $website = $this->websiteFactory->create();

        $this->_entityType = $this->_eavConfig->getEntityType('customer_address');

        $attribute->setWebsite($website);

        $data['attribute_code'] = 'my_attribute;
        $data['frontend_label'] = 'My Attribute';
        $data['frontend_input'] = 'text';
        $data['is_visible'] = true;
        $data['backend_model'] = null;
        $data['source_model'] = null;
        $data['backend_type'] = 'varchar';
        $data['is_user_defined'] = 1;
        $data['is_system'] = 0;
        $data['sort_order'] = 1000;

        // add set and group info
        $data['attribute_set_id'] = $this->_entityType->getDefaultAttributeSetId();

        /** @var $attrSet \Magento\Eav\Model\Entity\Attribute\Set */
        $attrSet = $this->_attrSetFactory->create();

        $data['attribute_group_id'] = $attrSet->getDefaultGroupId($data['attribute_set_id']);
        
        $data['used_in_forms'] =
            ['adminhtml_customer_address', 'customer_address_edit', 'customer_register_address', 'customer_address'];

        $data['entity_type_id'] = $this->_entityType->getId();
        $data['validate_rules'] = array();

        $attribute->addData($data);

        $attribute->save();
    }
}

This create the attribute en the eav_attribute table, and add a column in this tables: magento_customercustomattributes_sales_flat_quote_address magento_customercustomattributes_sales_flat_order_address

And probably do other stuff. With this script I did not need to modify any js file

Can this work on the CE ?

@Bartlomiejsz I think my problem is in the InstallData.php. I use this method which is a bit different from your. I post here my code

namespace EspertoMagento\MyAttribute\Setup;

use Magento\Customer\Setup\CustomerSetupFactory;
use Magento\Eav\Model\Entity\Attribute\SetFactory as AttributeSetFactory;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
 

class InstallData implements InstallDataInterface
{
    
    /**
     * @var CustomerSetupFactory
     */
    protected $customerSetupFactory;
    
    /**
     * @var AttributeSetFactory
     */
    private $attributeSetFactory;
    
    /**
     * @param CustomerSetupFactory $customerSetupFactory
     * @param AttributeSetFactory $attributeSetFactory
     */
    public function __construct(
        CustomerSetupFactory $customerSetupFactory,
        AttributeSetFactory $attributeSetFactory
    ) {
        $this->customerSetupFactory = $customerSetupFactory;
        $this->attributeSetFactory = $attributeSetFactory;
    }
 
    
    public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
    {
        
        /** @var CustomerSetup $customerSetup */
        $customerSetup = $this->customerSetupFactory->create(['setup' => $setup]);
        
        $customerEntity = $customerSetup->getEavConfig()->getEntityType('customer_address');
        $attributeSetId = $customerEntity->getDefaultAttributeSetId();
        
        /** @var $attributeSet AttributeSet */
        $attributeSet = $this->attributeSetFactory->create();
        $attributeGroupId = $attributeSet->getDefaultGroupId($attributeSetId);
        
        $customerSetup->addAttribute('customer_address', 'my_attribute_code', [
            'type' => 'varchar',
            'label' => 'My Attribute Label',
            'input' => 'text',
            'required' => false,
            'visible_on_front' => true,
            'visible' => true,
            'user_defined' => false,
            'sort_order' => 1000,
            'position' => 1000,
            'system' => 0,
            'is_used_in_grid' => true,
			'is_visible_in_grid' => true,
	    	'is_filterable_in_grid' => true,
	    	'is_searchable_in_grid' => true
        ]);
        
        $attribute = $customerSetup->getEavConfig()->getAttribute('customer_address', 'my_attribute_code')->addData([
            'attribute_set_id' => $attributeSetId,
            'attribute_group_id' => $attributeGroupId,
            'used_in_forms' => ['adminhtml_customer_address', 'customer_address_edit', 'customer_register_address']
        ]);
        
        $attribute->save();

    }
}

In my there isn’t the code

$columns = [
			'my_attribute_code' => [
			'type' => Table::TYPE_TEXT,
			'nullable' => true,
			'default' => null,
			'length' => 255,
			'comment' => '',
			]
		  ];

		  $tableNames = [
			  'customer_address_entity',
			  'quote_address',
			  'sales_order_address'
		  ];
		  
		  $connection = $setup->getConnection();
		  foreach ($tableNames as $tableName) {
			  foreach ($columns as $name => $definition) {
				  $connection->addColumn($tableName, $name, $definition);
			  }
		  }

@EspertoMagento sorry for not answering for so long. Exactly, I have used extension_attributes. I did it some time ago, but as far as I remember, what I did was:

  1. Adding new attribute in InstallData.php as in I wrote in issue.
  2. Adding extension_attributes:
<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="telephone_secondary" type="string" />
	</extension_attributes>
</config>
  1. Adding requirejs-config file to overrite nedded scripts:
var config = {
    map: {
        "*": {
            'Magento_Checkout/js/action/place-order': 'Vendor_Module/js/action/place-order',
            'Magento_Checkout/js/action/select-billing-address': 'Vendor_Module/js/action/select-billing-address',
            'Magento_Checkout/js/action/set-billing-address': 'Vendor_Module/js/action/set-billing-address',
            'Magento_Checkout/js/model/new-customer-address': 'Vendor_Module/js/model/new-customer-address',
            'Magento_Checkout/js/model/shipping-save-processor/default': 'Vendor_Module/js/model/shipping-save-processor/default',
            'Magento_Customer/js/model/address-list': 'Vendor_Module/js/model/address-list',
            'Magento_Customer/js/model/customer': 'Vendor_Module/js/model/customer',
            'Magento_Customer/js/model/customer-addresses': 'Vendor_Module/js/model/customer-addresses',
            'Magento_Customer/js/model/customer/address': 'Vendor_Module/js/model/customer/address'
        }
    }
};
  1. js/action/place-order.js - only changed section of file:
payload = {
	cartId: quote.getQuoteId(),
	billingAddress: quote.billingAddress(),
	paymentMethod: paymentData
};
// MODIFICATION START + jquery added as dependency
var additionalData = {extension_attributes: {}};
if (payload.billingAddress.telephone_secondary !== undefined) {
	additionalData.extension_attributes.telephone_secondary = payload.billingAddress.telephone_secondary;
	delete payload.billingAddress.telephone_secondary;
}
payload.billingAddress = $.extend(payload.billingAddress, additionalData);
// MODIFICATION END
  1. js/action/select-billing-address.js
return function (billingAddress) {
	var address = null;
	// MODIFICATION START
	if(billingAddress.extension_attributes !== undefined) {
		var additionalData = {};
		if (billingAddress.extension_attributes.telephone_secondary !== undefined) {
			additionalData.telephone_secondary = billingAddress.extension_attributes.telephone_secondary;
			delete billingAddress.extension_attributes.telephone_secondary;
		}
		billingAddress = $.extend(billingAddress, additionalData);
	}
	// MODIFICATION END
  1. js/action/set-billing-address.js
} else {
	serviceUrl = urlBuilder.createUrl('/carts/mine/billing-address', {});
	payload = {
		cartId: quote.getQuoteId(),
		address: quote.billingAddress()
	};
}
// MODIFICATION START
var additionalData = {extension_attributes: {}};
if (payload.address.telephone_secondary !== undefined) {
	additionalData.extension_attributes.telephone_secondary = payload.address.telephone_secondary;
	delete payload.address.telephone_secondary;
}
payload.address = $.extend(payload.address, additionalData);
// MODIFICATION END
  1. js/model/new-customer-address.js
prefix: addressData.prefix,
suffix: addressData.suffix,
vatId: addressData.vat_id,
// MODIFICATION START
telephone_secondary: addressData.telephone_secondary,
// MODIFICATION END
  1. js/model/shipping-save-processor/default.js
payload = {
	addressInformation: {
		shipping_address: quote.shippingAddress(),
		billing_address: quote.billingAddress(),
		shipping_method_code: quote.shippingMethod().method_code,
		shipping_carrier_code: quote.shippingMethod().carrier_code
	}
};
// MODIFICATION START + jquery added as dependency
var additionalData = {extension_attributes: {}};
payload.addressInformation = $.extend(payload.addressInformation, additionalData);
additionalData = {extension_attributes: {}};
if (payload.addressInformation.shipping_address.telephone_secondary !== undefined) {
	additionalData.extension_attributes.telephone_secondary = payload.addressInformation.shipping_address.telephone_secondary;
	delete payload.addressInformation.shipping_address.telephone_secondary;
}
payload.addressInformation.shipping_address = $.extend(payload.addressInformation.shipping_address, additionalData);
additionalData = {extension_attributes: {}};
if (payload.addressInformation.billing_address.telephone_secondary !== undefined) {
	additionalData.extension_attributes.telephone_secondary = payload.addressInformation.billing_address.telephone_secondary;
	delete payload.addressInformation.billing_address.telephone_secondary;
}
payload.addressInformation.billing_address = $.extend(payload.addressInformation.billing_address, additionalData);
// MODIFICATION END
  1. js/model/address-list.js
define(
    [
        'ko',
        'Magento_Customer/js/model/customer-addresses' // MOD
    ],
  1. js/model/customer-addresses.js
define(
    [
        'jquery',
        'ko',
        'Magento_Customer/js/model/customer/address' // MOD
    ],
  1. js/model/customer/address.js
suffix: addressData.suffix,
vatId: addressData.vat_id,
// MODIFICATION START
telephone_secondary: addressData.telephone_secondary,
// MODIFICATION END
  1. 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="checkout_submit_all_after">
        <observer name="save_additional_fields" instance="Vendor\Module\Observer\SaveAdditionalFields" />
    </event>
</config>
  1. Observer/SaveAdditionalFields.php
<?php
namespace Vendor\Module\Observer;
use \Magento\Framework\Event\ObserverInterface;
use \Magento\Framework\Event\Observer;
use \Magento\Framework\Exception\LocalizedException;
class SaveAdditionalFields implements ObserverInterface
{
    /**
     * @var \Magento\Sales\Api\OrderAddressRepositoryInterface
     */
    protected $orderAddressRepository;
    /**
     * @var \Magento\Sales\Api\OrderRepositoryInterface
     */
    protected $orderRepository;
    /**
     * @param \Magento\Sales\Api\OrderAddressRepositoryInterface $orderAddressRepository
     */
    public function __construct(
        \Magento\Sales\Api\OrderAddressRepositoryInterface $orderAddressRepository,
        \Magento\Sales\Api\OrderRepositoryInterface $orderRepository
    ) {
        $this->orderAddressRepository = $orderAddressRepository;
        $this->orderRepository = $orderRepository;
    }
    /**
     * {@inheritdoc}
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function execute(Observer $observer)
    {
        /** @var \Magento\Sales\Model\Order $orderInstance */
        $orderInstance = $observer->getEvent()->getOrder();
        /** @var \Magento\Quote\Model\Quote $quoteInstance */
        $quoteInstance = $observer->getEvent()->getQuote();
        $quoteShippingAddress = $quoteInstance->getShippingAddress();
        $quoteBillingAddress = $quoteInstance->getBillingAddress();
        $orderShippingAddress = $orderInstance->getShippingAddress();
        $orderBillingAddress = $orderInstance->getBillingAddress();
        if ($orderShippingAddress !== null && $quoteShippingAddress !== null) {
            $orderShippingAddress->setData(
                'telephone_secondary',
                $quoteShippingAddress->getData('telephone_secondary')
            );
        }
        if ($orderBillingAddress !== null && $quoteBillingAddress !== null) {
            $orderBillingAddress->setData(
                'telephone_secondary',
                $quoteBillingAddress->getData('telephone_secondary')
            );
        }
        try
        {
            $this->orderRepository->save($orderInstance);
            if ($orderShippingAddress !== null) {
                $this->orderAddressRepository->save($orderShippingAddress);
            }
            if ($orderBillingAddress !== null) {
                $this->orderAddressRepository->save($orderBillingAddress);
            }
        }
        catch (\Exception $e)
        {
            throw new LocalizedException(__('Something went wrong while saving additional fields to order addresses'));
        }
    }
}