Custom Attribute for quote table is not saving value - magento2

i am trying to add custom_attribute to quote table name as is_requested. this work fine, column is created. here is Upgrade schem
use Magento\Framework\Setup\UpgradeSchemaInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\Setup\ModuleContextInterface;
class UpgradeSchema implements UpgradeSchemaInterface
{
public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context)
{
$setup->startSetup();
$setup->getConnection()
->addColumn(
$setup->getTable('quote'),
'is_requested',
[
'type' => \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER,
'length' => 255,
'nullable' => true,
'default' => 0,
'comment' => 'Requested Quote'
]
);
$setup->endSetup();
}
}
now the problem is when i try to add value by this code
Magento\Quote\Model\QuoteFactory $quote;
$quote = $this->quote->create();
$quote->setCustomAttribute('is_requested', '1')->save();
value is not save in quote table.

You should add custom attributes through the InstallData or UpgradeData classes. The Schema classes are used to create custom tables.
First add the following to 'Setup/InstallData.php'
namespace [Vendor]\[Module]\Setup;
use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Quote\Setup\QuoteSetupFactory;
use Magento\Sales\Setup\SalesSetupFactory;
class InstallData implements InstallDataInterface {
private $eavSetupFactory;
private $quoteSetupFactory;
private $salesSetupFactory;
public function __construct(EavSetupFactory $eavSetupFactory, SalesSetupFactory $salesSetupFactory, QuoteSetupFactory $quoteSetupFactory)
{
$this->eavSetupFactory = $eavSetupFactory;
$this->quoteSetupFactory = $quoteSetupFactory;
$this->salesSetupFactory = $salesSetupFactory;
}
public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
{
$eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
$quoteSetup = $this->quoteSetupFactory->create(['setup' => $setup]);
$salesSetup = $this->salesSetupFactory->create(['setup' => $setup]);
$eavSetup->addAttribute(
\Magento\Catalog\Model\Product::ENTITY,
'is_requested',
[
'type' => 'int',
'label' => 'Is Requested',
'input' => 'boolean',
'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_GLOBAL,
'visible' => false,
'required' => false,
'user_defined' => false,
'default' => '',
'searchable' => false,
'filterable' => false,
'comparable' => false,
'enabled' => false,
'visible_on_front' => false,
'used_in_product_listing' => false,
'unique' => false,
'apply_to' => ''
]
);
$attributeOptions = [
'type' => 'int',
'visible' => true,
'required' => false
];
$quoteSetup->addAttribute('quote_item', 'is_requested', $attributeOptions);
$salesSetup->addAttribute('order_item', 'is_requested', $attributeOptions);
}
}
Create the file 'etc/events.xml' in your module (if it doesn't exist yet) and add this code.
<?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_quote_item_set_product">
<observer name="set_item_custom_attributes" instance="[Vendor]\[Module]\Observer\SetItemCustomAttributes" />
</event>
</config>
Then create the observer at 'Observer/SetItemCustomAttributes.php' in your module and add the following code.
namespace [Vendor]\[Module]\Observer;
use Magento\Framework\Event\ObserverInterface;
class SetItemCustomAttributes implements ObserverInterface
{
/**
* #param \Magento\Framework\Event\Observer $observer
* #return void
* #SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function execute(\Magento\Framework\Event\Observer $observer)
{
$quoteItem = $observer->getQuoteItem();
$product = $observer->getProduct();
$quoteItem->setIsRequested($product->getAttributeText('is_requested'));
}
}
After that add the following to the 'etc/di.xml' in your module.
<?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\Quote\Model\Quote\Item\ToOrderItem">
<plugin name="iefd_delivery_quote_to_order_item" type="[Vendor]\[Module]\Plugin\IsRequestedQuoteToOrderItem"/>
</type>
</config>
Finally create 'Plugin/IsRequestedQuoteToOrderItem.php' and add the following code.
namespace [Vendor]\[Module]\Plugin;
class IsRequestedQuoteToOrderItem
{
public function aroundConvert(
\Magento\Quote\Model\Quote\Item\ToOrderItem $subject,
\Closure $proceed,
\Magento\Quote\Model\Quote\Item\AbstractItem $item,
$additional = []
) {
/** #var $orderItem \Magento\Sales\Model\Order\Item */
$orderItem = $proceed($item, $additional);
$orderItem->setIsRequested($item->getIsRequested());
return $orderItem;
}
}

Related

System values from Product attributes are not recovered by filter

I have a custom product attributes. In backoffice, the values are well recovered, but in layer navigation, the filter can't find the values
Any ideas ?
This is the attribute creation :
$eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
$eavSetup->addAttribute(
\Magento\Catalog\Model\Product::ENTITY, 'longueur_tasse_vertuo', [
'type' => 'text',
'backend' => 'Magento\Eav\Model\Entity\Attribute\Backend\ArrayBackend',
'frontend' => '',
'label' => 'Longueur de tasse Vertuo',
'input' => 'multiselect',
'class' => '',
'source' => 'Cpy\Catalog\Model\Config\Product\CupSizeVertuooption',
'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_STORE,
'visible' => true,
'required' => false,
'group' => '',
'sort_order' => 203,
'searchable' => false,
'filterable' => true,
'comparable' => false,
'used_in_product_listing' => true,
'visible_on_front' => true
]
);
This is the source model
class CupSizeVertuooption extends AbstractSource
{
/**
* #var \Magento\Eav\Model\ResourceModel\Entity\AttributeFactory
*/
protected $_eavAttrEntity;
/**
* #param \Magento\Eav\Model\ResourceModel\Entity\AttributeFactory $eavAttrEntity
* #codeCoverageIgnore
*/
public function __construct(
\Magento\Eav\Model\ResourceModel\Entity\AttributeFactory $eavAttrEntity
) {
$this->_eavAttrEntity = $eavAttrEntity;
}
public function getAllOptions()
{
$this->_options = [];
$this->_options[] = ['label' => 'Espresso', 'value' => '1'];
$this->_options[] = ['label' => 'Double Espresso', 'value' => '2'];
$this->_options[] = ['label' => 'Gran Lungo', 'value' => '3'];
$this->_options[] = ['label' => 'Mug', 'value' => '4'];
$this->_options[] = ['label' => 'Alto', 'value' => '5'];
return $this->_options;
}
/**
* Retrieve option array
*
* #return array
*/
public function getOptionArray()
{
$_options = array();
foreach ($this->getAllOptions() as $option) {
$_options[$option['value']] = $option['label'];
}
return $_options;
}
/**
* Get a text for option value
*
* #param string|integer $value
* #return string|bool
*/
public function getOptionText($value)
{
foreach ($this->getAllOptions() as $option) {
if ($option['value'] == $value) {
return $option['label'];
}
}
return false;
}
/**
* Retrieve flat column definition
*
* #return array
*/
public function getFlatColumns()
{
$attributeCode = $this->getAttribute()->getAttributeCode();
return [
$attributeCode => [
'unsigned' => false,
'default' => null,
'extra' => null,
'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
'length' => 255,
'nullable' => true,
'comment' => $attributeCode . ' column',
],
];
}
/**
* Retrieve Indexes(s) for Flat
*
* #return array
*/
public function getFlatIndexes()
{
$indexes = [];
$index = 'IDX_' . strtoupper($this->getAttribute()->getAttributeCode());
$indexes[$index] = ['type' => 'index', 'fields' => [$this->getAttribute()->getAttributeCode()]];
return $indexes;
}
/**
* Retrieve Select For Flat Attribute update
*
* #param int $store
* #return \Magento\Framework\DB\Select|null
*/
public function getFlatUpdateSelect($store)
{
return $this->_eavAttrEntity->create()->getFlatUpdateSelect($this->getAttribute(), $store);
}
}
As you can see, he is able to recover the attributes...but not his content.
Nevertheless, if I go to check in my backend properties the values are well there;
Also, this is the block definition :
<block class="Magento\Catalog\Block\Product\View\Description" name="product.info.cafe.cupsize" template="Magento_Catalog::product/view/cafe/cup-size.phtml" before="product.info.price" >
<arguments>
<argument name="at_call" xsi:type="string">getLongueurTasse</argument>
<argument name="at_code" xsi:type="string">longueur_tasse</argument>
<argument name="at_type" xsi:type="string">text</argument>
<argument name="css_class" xsi:type="string">product-nespresso-cupsize</argument>
<argument name="at_label" xsi:type="string">Taille des tasses :</argument>
<argument name="add_attribute" xsi:type="string">itemprop="nespresso-cupsize"</argument>
</arguments>
</block>
<block class="Magento\Catalog\Block\Product\View\Description" name="product.info.cafe.cupsize.vertuo" template="Magento_Catalog::product/view/cafe/vertuo/cup-size.phtml" before="product.info.price" >
<arguments>
<argument name="at_call" xsi:type="string">getLongueurTasseVertuo</argument>
<argument name="at_code" xsi:type="string">longueur_tasse_vertuo</argument>
<argument name="css_class" xsi:type="string">product-nespresso-cupsize-vertuo</argument>
<argument name="at_label" xsi:type="string">Taille des tasses :</argument>
<argument name="add_attribute" xsi:type="string">itemprop="nespresso-cupsize"</argument>
</arguments>
</block>
The main issue belongsz to the class vendor/magento/module-catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php
The method
protected function _getIndexableAttributes($multiSelect)
{
$select = $this->getConnection()->select()->from(
['ca' => $this->getTable('catalog_eav_attribute')],
'attribute_id'
)->join(
['ea' => $this->getTable('eav_attribute')],
'ca.attribute_id = ea.attribute_id',
[]
)->where(
$this->_getIndexableAttributesCondition()
);
if ($multiSelect == true) {
$select->where('ea.backend_type = ?', 'varchar')->where('ea.frontend_input = ?', 'multiselect');
} else {
$select->where('ea.backend_type = ?', 'int')->where('ea.frontend_input IN( ? )', ['select', 'boolean']);
}
return $this->getConnection()->fetchCol($select);
}
For multiselect, the required type was varchar while my attribute had a type text.
So all we have to do is to change the attribute type to varchar and then play the indexer once again to get the association geing written in the table catalog_product_index_eav making it possible to be appearing in the filter.
This is for the quick and immediate fix. In the same time, I send the issue to magento to make this multiselect where condition be able to read both text and varchar type, feel free to check it : https://github.com/magento/magento2/issues/29676

Magento 2 Plugins / Interceptors accessing and modifying $this object

I have a plugin that i want to modify functionality of a method within specific class in Magento 2 however am not quite sure on how to access the original object and return the modified data.
Original Method
protected function _initTotals()
{
$source = $this->getSource();
$this->_totals = [];
$this->_totals['subtotal'] = new \Magento\Framework\DataObject(
['code' => 'subtotal', 'value' => $source->getSubtotal(), 'label' => __('Subtotal')]
);
/**
* Add shipping
*/
if (!$source->getIsVirtual() && ((double)$source->getShippingAmount() || $source->getShippingDescription())) {
$this->_totals['shipping'] = new \Magento\Framework\DataObject(
[
'code' => 'shipping',
'field' => 'shipping_amount',
'value' => $this->getSource()->getShippingAmount(),
'label' => __('Shipping & Handling'),
]
);
}
/**
* Add discount
*/
if ((double)$this->getSource()->getDiscountAmount()) {
if ($this->getSource()->getDiscountDescription()) {
$discountLabel = __('Discount (%1)', $source->getDiscountDescription());
} else {
$discountLabel = __('Discount');
}
$this->_totals['discount'] = new \Magento\Framework\DataObject(
[
'code' => 'discount',
'field' => 'discount_amount',
'value' => $source->getDiscountAmount(),
'label' => $discountLabel,
]
);
}
$this->_totals['grand_total'] = new \Magento\Framework\DataObject(
[
'code' => 'grand_total',
'field' => 'grand_total',
'strong' => true,
'value' => $source->getGrandTotal(),
'label' => __('Grand Total'),
]
);
/**
* Base grandtotal
*/
if ($this->getOrder()->isCurrencyDifferent()) {
$this->_totals['base_grandtotal'] = new \Magento\Framework\DataObject(
[
'code' => 'base_grandtotal',
'value' => $this->getOrder()->formatBasePrice($source->getBaseGrandTotal()),
'label' => __('Grand Total to be Charged'),
'is_formated' => true,
]
);
}
return $this;
}
This i have set to have a plugin to modify functionality of method above with di.xml:
<type name="Magento\Sales\Block\Order\Totals">
<plugin disabled="false" name="Harrigo_EverDiscountLabel_Plugin_Magento_Sales_Block_Order_Totals" sortOrder="10" type="Harrigo\EverDiscountLabel\Plugin\Magento\Sales\Block\Order\Totals"/>
</type>
Plugin
class Totals
{
public function after_initTotals(
\Magento\Sales\Block\Order\Totals $subject,
$result
) {
if ((double)$subject->getSource()->getDiscountAmount() != 0 OR $subject->getSource()->getDiscountDescription() != null) {
if ($subject->getSource()->getDiscountDescription()) {
$discountLabel = __('Offer (%1)', $source->getDiscountDescription());
} else {
$discountLabel = __('Offer');
}
$subject->_totals['discount'] = new \Magento\Framework\DataObject(
[
'code' => 'discount',
'field' => 'discount_amount',
'value' => $source->getDiscountAmount(),
'label' => $discountLabel,
]
);
}
return $subject;
}
}
Have used $subject instead of $this within the plugin, this does not work for me however. How do I access the $this object within the plugin to add / overwrite $this->_totals['discount'] and return the updated $this object from within the plugin. I have it working fine with a standard preference but would rather use a plugin if possible.
I think you should check this before implementing above code.
http://devdocs.magento.com/guides/v2.0/extension-dev-guide/plugins.html
As per devdocs for Magento2 protected functions can not be intercepted so We can not use plugins for that.
May be that is causing issue in your case.
Hope this helps!

How to add new custom field to billing address section in magento2

I have tried to add new field in magento2 billing address section. I have followed below link to add new field in shipping address block
http://oyenetwork.com/articles/magento2-devliery-date-module-creation-from-scratch/
I have added new field to shipping address section successfully. But in my site, I have been using "Virtual products". So I want to add my new custom field into billing section. I just modified that "LayoutProcessorPlugin.php" code like below
$jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['children']['payments-list']['children']['delivery_date']
instead of
$jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']['shippingAddress']['children']['shipping-address-fieldset']['children']['delivery_date']
But it doesn't working. How to add my new custom field to Billing address block in magento2?
Please check below code to save custom address attribute in customer,checkout shipping and billing form and also save in order table.
Module Name : Ccc_Checkout
Script to create custom attribute for address and order
app/code/Ccc/Checkout/Setup/UpgradeData.php
<?php
namespace Ccc\Checkout\Setup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\UpgradeDataInterface;
use Magento\Eav\Model\Config;
use Magento\Eav\Model\Entity\Attribute\SetFactory as AttributeSetFactory;
class UpgradeData implements UpgradeDataInterface
{
private $eavSetupFactory;
/**
* #var Config
*/
private $eavConfig;
/**
* #var AttributeSetFactory
*/
private $attributeSetFactory;
public function __construct(
Config $eavConfig,
EavSetupFactory $eavSetupFactory,
AttributeSetFactory $attributeSetFactory
)
{
$this->eavSetupFactory = $eavSetupFactory;
$this->eavConfig = $eavConfig;
$this->attributeSetFactory = $attributeSetFactory;
}
/**
* {#inheritdoc}
*/
public function upgrade(
ModuleDataSetupInterface $setup,
ModuleContextInterface $context
) {
$setup->startSetup();
if (version_compare($context->getVersion(), '0.0.2','<')) {
$this->addUnitNumberFieldToAddress($setup);
}
if (version_compare($context->getVersion(), '0.0.3','<')) {
$this->updateUnitAttribute($setup);
}
$setup->endSetup();
}
/**
* put your comment there...
*
* #param mixed $setup
*/
protected function addUnitNumberFieldToAddress($setup)
{
$eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
$eavSetup->addAttribute('customer_address', 'unit_number', [
'type' => 'varchar',
'input' => 'text',
'label' => 'Unit Number',
'visible' => true,
'required' => false,
'user_defined' => true,
'system'=> false,
'group'=> 'General',
'sort_order' => 71,
'global' => true,
'visible_on_front' => true,
]);
$customAttribute = $this->eavConfig->getAttribute('customer_address', 'unit_number');
$customAttribute->setData(
'used_in_forms',
['adminhtml_customer_address','customer_address_edit','customer_register_address']
);
$customAttribute->save();
$installer = $setup;
$installer->getConnection()->addColumn(
$installer->getTable('quote_address'),
'unit_number',
[
'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
'length' => 255,
'comment' => 'Unit Number'
]
);
$installer->getConnection()->addColumn(
$installer->getTable('sales_order_address'),
'unit_number',
[
'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
'length' => 255,
'comment' => 'Unit Number'
]
);
}
public function updateUnitAttribute($setup)
{
$eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
$eavSetup->updateAttribute('customer_address', 'unit_number', 'sort_order', '71');
}
}
app/code/Ccc/Checkout/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">
<type name="Magento\Checkout\Block\Checkout\LayoutProcessor">
<plugin disabled="false" name="BillingLayoutProcessor" sortOrder="99" type="Ccc\Checkout\Plugin\Block\Checkout\LayoutProcessor"/>
</type>
<type name="Magento\Quote\Model\BillingAddressManagement">
<plugin disabled="false" name="Ccc_Checkout_Plugin_Magento_Quote_Model_BillingAddressManagement" sortOrder="10" type="Ccc\Checkout\Plugin\Magento\Quote\Model\BillingAddressManagement"/>
</type>
<type name="Magento\Quote\Model\Quote\Address\BillingAddressPersister">
<plugin disabled="false" name="BillingAddressSave" sortOrder="10" type="Ccc\Checkout\Plugin\Magento\Quote\Model\Quote\Address\BillingAddressPersister"/>
</type>
<type name="Magento\Quote\Model\ShippingAddressManagement">
<plugin disabled="false" name="Ccc_Checkout_Plugin_Magento_Quote_Model_ShippingAddressManagement" sortOrder="10" type="Ccc\Checkout\Plugin\Magento\Quote\Model\ShippingAddressManagement"/>
</type>
</config>
app/code/Ccc/Checkout/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_success">
<observer name="custome_address_attribute_save" instance="Ccc\Checkout\Observer\SaveUnitNumberInOrder"/>
</event>
</config>
app/code/Ccc/Checkout/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="unit_number" type="string"/>
</extension_attributes>
</config>
Create Plugin to display custom attribute in checkout billing and shipping form
app/code/Ccc/Checkout/Plugin/Block/Checkout/LayoutProcessor
<?php
namespace Ccc\Checkout\Plugin\Block\Checkout;
use \Magento\Checkout\Block\Checkout\LayoutProcessor as MageLayoutProcessor;
class LayoutProcessor
{
protected $_customAttributeCode = 'unit_number';
public function afterProcess(MageLayoutProcessor $subject, $jsLayout)
{
if (isset($jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']
['payment']['children']['payments-list']['children']))
{
foreach ($jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['children']['payments-list']['children'] as $key => $payment)
{
$paymentCode = 'billingAddress'.str_replace('-form','',$key);
$jsLayout['components']['checkout']['children']['steps']['children']['billing-step']['children']['payment']['children']['payments-list']['children'][$key]['children']['form-fields']['children'][$this->_customAttributeCode] = $this->getUnitNumberAttributeForAddress($paymentCode);
}
}
if(isset($jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']['shippingAddress']['children']['shipping-address-fieldset'])
){
$jsLayout['components']['checkout']['children']['steps']['children']['shipping-step']['children']['shippingAddress']['children']['shipping-address-fieldset']['children'][$this->_customAttributeCode] = $this->getUnitNumberAttributeForAddress('shippingAddress');
}
return $jsLayout;
}
public function getUnitNumberAttributeForAddress($addressType)
{
return $customField = [
'component' => 'Magento_Ui/js/form/element/abstract',
'config' => [
'customScope' => $addressType.'.custom_attributes',
'customEntry' => null,
'template' => 'ui/form/field',
'elementTmpl' => 'ui/form/element/input'
],
'dataScope' => $addressType.'.custom_attributes' . '.' . $this->_customAttributeCode,
'label' => 'Unit Number',
'provider' => 'checkoutProvider',
'sortOrder' => 71,
'validation' => [
'required-entry' => false
],
'options' => [],
'filterBy' => null,
'customEntry' => null,
'visible' => true,
];
}
}
To save custom attribute in checkout
app/code/Ccc/Checkout/Plugin/Magento/Quote/Model/ShippingAddressManagement
<?php
namespace Ccc\Checkout\Plugin\Magento\Quote\Model;
class ShippingAddressManagement
{
protected $logger;
public function __construct(
\Psr\Log\LoggerInterface $logger
) {
$this->logger = $logger;
}
public function beforeAssign(
\Magento\Quote\Model\ShippingAddressManagement $subject,
$cartId,
\Magento\Quote\Api\Data\AddressInterface $address
) {
$extAttributes = $address->getExtensionAttributes();
if (!empty($extAttributes)) {
try {
$address->setUnitNumber($extAttributes->getUnitNumber());
} catch (\Exception $e) {
$this->logger->critical($e->getMessage());
}
}
}
}
app/code/Ccc/Checkout/Plugin/Magento/Quote/Model/BillingAddressManagement
<?php
namespace Ccc\Checkout\Plugin\Magento\Quote\Model;
class BillingAddressManagement
{
protected $logger;
public function __construct(
\Psr\Log\LoggerInterface $logger
) {
$this->logger = $logger;
}
public function beforeAssign(
\Magento\Quote\Model\BillingAddressManagement $subject,
$cartId,
\Magento\Quote\Api\Data\AddressInterface $address,
$useForShipping = false
) {
$extAttributes = $address->getExtensionAttributes();
if (!empty($extAttributes)) {
try {
$address->setUnitNumber($extAttributes->getUnitNumber());
} catch (\Exception $e) {
$this->logger->critical($e->getMessage());
}
}
}
}
app/code/Ccc/Checkout/Ccc/Checkout/Plugin/Magento/Quote/Model/Quote/Address
<?php
namespace Ccc\Checkout\Plugin\Magento\Quote\Model\Quote\Address;
class BillingAddressPersister
{
protected $logger;
public function __construct(
\Psr\Log\LoggerInterface $logger
) {
$this->logger = $logger;
}
public function beforeSave(
\Magento\Quote\Model\Quote\Address\BillingAddressPersister $subject,
$quote,
\Magento\Quote\Api\Data\AddressInterface $address,
$useForShipping = false
) {
$extAttributes = $address->getExtensionAttributes();
if (!empty($extAttributes)) {
try {
$address->setUnitNumber($extAttributes->getUnitNumber());
} catch (\Exception $e) {
$this->logger->critical($e->getMessage());
}
}
}
}
To set custom attribute in extension attribute
app/code/Ccc/Checkout/view/frontend/requirejs-config.js
var config = {
config: {
mixins: {
'Magento_Checkout/js/model/payment/method-group': {
'Ccc_Checkout/js/model/payment/method-group-mixin': true
},
'Magento_Checkout/js/action/set-billing-address': {
'Ccc_Checkout/js/action/set-billing-address-mixin': true
},
'Magento_Checkout/js/action/set-shipping-information': {
'Ccc_Checkout/js/action/set-shipping-information-mixin': true
},
'Magento_Checkout/js/action/create-shipping-address': {
'Ccc_Checkout/js/action/create-shipping-address-mixin': true
},
'Magento_Checkout/js/action/place-order': {
'Ccc_Checkout/js/action/set-billing-address-mixin': true
},
'Magento_Checkout/js/action/create-billing-address': {
'Ccc_Checkout/js/action/set-billing-address-mixin': true
}
}
}
};
app/code/Ccc/Checkout/view/frontend/web/js/action/create-shipping-address-mixin.js
define([
'jquery',
'mage/utils/wrapper',
'Magento_Checkout/js/model/quote'
], function ($, wrapper,quote) {
'use strict';
return function (setShippingInformationAction) {
return wrapper.wrap(setShippingInformationAction, function (originalAction, messageContainer) {
if (messageContainer.custom_attributes != undefined) {
$.each(messageContainer.custom_attributes , function( key, value ) {
messageContainer['custom_attributes'][key] = {'attribute_code':key,'value':value};
});
}
return originalAction(messageContainer);
});
};
});
app/code/Ccc/Checkout/view/frontend/web/js/action/set-billing-address-mixin.js
define([
'jquery',
'mage/utils/wrapper',
'Magento_Checkout/js/model/quote'
], function ($, wrapper,quote) {
'use strict';
return function (setBillingAddressAction) {
return wrapper.wrap(setBillingAddressAction, function (originalAction, messageContainer) {
var billingAddress = quote.billingAddress();
if(billingAddress != undefined) {
if (billingAddress['extension_attributes'] === undefined) {
billingAddress['extension_attributes'] = {};
}
if (billingAddress.customAttributes != undefined) {
$.each(billingAddress.customAttributes, function (key, value) {
if($.isPlainObject(value)){
value = value['value'];
}
billingAddress['extension_attributes'][key] = value;
});
}
}
return originalAction(messageContainer);
});
};
});
app/code/Ccc/Checkout/view/frontend/web/js/action/set-shipping-information-mixin.js
define([
'jquery',
'mage/utils/wrapper',
'Magento_Checkout/js/model/quote'
], function ($, wrapper,quote) {
'use strict';
return function (setShippingInformationAction) {
return wrapper.wrap(setShippingInformationAction, function (originalAction, messageContainer) {
var shippingAddress = quote.shippingAddress();
if (shippingAddress['extension_attributes'] === undefined) {
shippingAddress['extension_attributes'] = {};
}
if (shippingAddress.customAttributes != undefined) {
$.each(shippingAddress.customAttributes , function( key, value ) {
if($.isPlainObject(value)){
value = value['value'];
}
shippingAddress['customAttributes'][key] = value;
shippingAddress['extension_attributes'][key] = value;
});
}
return originalAction(messageContainer);
});
};
});
To save custom attribute in orders
app/code/Ccc/Checkout/Observer/SaveUnitNumberInOrder.php
<?php
namespace Ccc\Checkout\Observer;
class SaveUnitNumberInOrder implements \Magento\Framework\Event\ObserverInterface
{
public function execute(\Magento\Framework\Event\Observer $observer) {
$order = $observer->getEvent()->getOrder();
$quote = $observer->getEvent()->getQuote();
if ($quote->getBillingAddress()) {
$order->getBillingAddress()->setUnitNumber($quote->getBillingAddress()->getExtensionAttributes()->getUnitNumber());
}
if (!$quote->isVirtual()) {
$order->getShippingAddress()->setUnitNumber($quote->getShippingAddress()->getUnitNumber());
}
return $this;
}
}
To display custom attribute in customer account you need to overright customer edit.phtml file from vendor to your theme like below :
app/design/frontend/custom_theme/theme_name/Magento_Customer/templates/address/edit.phtml
<div class="field unit_number">
<label class="label" for="unit_number"><span><?php echo $block->escapeHtml(__('Unit Number')) ?></span></label>
<div class="control">
<input type="text" name="unit_number"
value="<?= $block->escapeHtmlAttr($block->getAddress()->getCustomAttribute('unit_number') ? $block->getAddress()->getCustomAttribute('unit_number')->getValue() : '') ?>"
title="<?= $block->escapeHtmlAttr($block->getAddress()->getCustomAttribute('unit_number') ? $block->getAddress()->getCustomAttribute('unit_number')->getValue() : '') ?>"
class="input-text <?= $block->escapeHtmlAttr($block->getAddress()->getCustomAttribute('unit_number') ? $block->getAddress()->getCustomAttribute('unit_number')->getValue() : '') ?>"
id="unit_number">
</div>
</div>
Maybe late, anyway:
$jsLayout['components']
['checkout']['children']
['steps']['children']
['billing-step']['children']
['payment']['children']
['afterMethods']['children']
['billing-address-form']['children']
['form-fields']['children']
[$customAttributeCode] = $customField;
should do the trick.
I can show my field in this way in the billing form.
Anyway, I am still studying which action should I capture to retrieve this value and to send it to the server.
Webkul has a great article on How To Add Custom Field At Billing Address Form In Magento 2, though it is not covering the full details on how to save this field to database and show on order details screen but it is helpful in displaying the field at the billing address.
https://webkul.com/blog/add-custom-field-billing-address-form-magento-2/

Symfony2 entity type check box set preferred choices

I have the following form filed in the edit form.
->add('district', 'entity', array(
'class' => 'AdminBundle:Districts',
'query_builder' => function(EntityRepository $repository) {
return $repository->createQueryBuilder('c')
->where('c.status =:status')
->setparameter('status','1');
},
'property' => 'districtName',
'preferred_choices' => array($details->getDistrict()),
'multiple' => TRUE,
'expanded' => TRUE,
'required' => true,
)
)
Output of this is checkboxes. I can check more districts here.
In the edit mode how to set the preferred choices?
OK, you need to use an EventListener against the form. See documentation for more information.
This will allow you to pre set form data
Example ()
/* Form */
namespace Company\YourBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Doctrine\ORM\EntityRepository;
use Company\YourBundle\Form\EventListener\YourEventListener;
class FormType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->addEventSubscriber(new YourEventListener($builder->getFormFactory()));
}
public function getName() {
return 'company_formtype';
}
}
/* Event Listener (You may require to pass more data to this class from your form as I have little information to help you with)*/
namespace Company\YourBundle\Form\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
class ActivityListener implements EventSubscriberInterface {
private $form;
public function __construct($form) {
$this->form = $form;
}
public static function getSubscribedEvents() {
return array(
FormEvents::PRE_SET_DATA => 'onPreSetData',
);
}
public function onPreSetData(FormEvent $e) {
$data = $e->getData();
$form = $e->getForm();
if ($form->has('district')) {
$form->remove('district');
}
$form->add($this->form->createNamed('district', 'entity', null, array(
'class' => 'AdminBundle:Districts',
'query_builder' => function(EntityRepository $repository) {
return $repository->createQueryBuilder('c')
->where('c.status =:status')
->setparameter('status','1')},
'property' => 'districtName',
'preferred_choices' => $data['id'] ? /** in edit mode set the preferred **/ ? null,
'multiple' => TRUE,
'expanded' => TRUE,
'required' => true,
));
}
}

The Choice constraint expects a valid callback

I updated Symfony2 to 2.1 and when I trying submit form I am getting error:
The Choice constraint expects a valid callback
source code from form type class:
$builder->add('type', 'choice',
array(
'expanded' => true,
'multiple' => false,
'choice_list' => new TypeChoices(),
'required' => true,
)
)
TypeChoices class:
class TypeChoices implements ChoiceListInterface {
public static $choices = array(
'full-time' => 'Full time',
'part-time' => 'Part time',
'freelance' => 'Freelance',
);
public static function getChoiceNameByValue($value)
{
return self::$choices[$value];
}
public function getChoices()
{
return self::$choices;
}
public static function getTypeChoicesKeys()
{
return array_keys(self::$choices);
}
public static function getPreferredChoiceKey()
{
return 'full-time';
}
}
Could someone give me any advice?
Maybe you could try to extend the SimpleChoiceList class, this way:
ChoiceList code:
class TypeChoices extends SimpleChoiceList
{
public static $choices = array(
'full-time' => 'Full time',
'part-time' => 'Part time',
'freelance' => 'Freelance',
);
/**
* Constructor.
*
* #param array $preferredChoices Preffered choices in the list.
*/
public function __construct(array $preferredChoices = array()) // PASS MORE ARGUMENT IF NEEDED
{
parent::__construct(
static::$choices,
$preferredChoices
);
}
}
Form type code:
->add('type', 'choice', array(
'choice_list' => new TypeChoices(),
...
))