0

I am trying to get the Custom Attribute I have in the sales_order_item to display on the rest/V1/orders/items API call. I was able to get the Attribute to display on the rest/V1/orders API call. Using a Magento\Sales\Api\OrderRepositoryInterface plugin. But the Attribute don't display on the rest/V1/orders/items API call. I was trying to use the OrderItemRepositoryInterface plugin, but I don't know what functions to add or if this is the correct way to do this.

Thanks.

1
  • can u share your code to get the Attribute to display on the rest/V1/orders API call? Commented Jul 2, 2020 at 5:17

2 Answers 2

1

You have to make your Custom Attribute attribute as extension attribute for Magento\Sales\Api\Data\OrderItemInterface

create extension_attributes.xml at your modules app/code/{Vendorname}/{ModuleName}/etc.

<?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\Sales\Api\Data\OrderItemInterface"> <attribute code="{Your_Custom_Field}" type="string" /> </extension_attributes> </config> 

I assume that your field as varchar that why I have add type as string type variable (type="string").

Update,

As you want to expose that rest/V1/orders/items Api point then you have to create a plugin on Magento\Sales\Model\Order\ProductOption::add()

Here the plugin Class:

<?php namespace {VendorName}\{ModuleName}\Plungin; use Magento\Sales\Api\Data\OrderItemInterface; class ProductOption { /** * @var \Magento\Sales\Api\Data\OrderItemExtensionFactory */ private $orderItemExtensionFactory; public function __construct( \Magento\Sales\Api\Data\OrderItemExtensionFactory $orderItemExtensionFactory ) { $this->orderItemExtensionFactory = $orderItemExtensionFactory; } public function beforeAdd( \Magento\Sales\Model\Order\ProductOption $subject, OrderItemInterface $orderItem ) { $extensionAttributes = $orderItem->getExtensionAttributes(); if(null=== $extensionAttributes){ $extensionAttributes= $this->orderItemExtensionFactory->create(); } $extensionAttributes->setCustomField($order->getCustomField()); $orderItem->setExtensionAttributes($extensionAttributes); } } 
1
  • Using this method I can see the custom attributes in the API call rest/V1/orders but not in the rest/V1/orders/items API call. Commented Feb 11, 2020 at 15:55
0

I know it is a little late, but for anyone else looking for a way to get label values of custom attributes, and configurable item values via the rest api.

I am presuming you want the custom attribute's label & value label, and not id & value_id as those do exist in the orders api.

There are 3 steps we need to make in order to find all of the different custom attributes labels.

  1. Fetch the orders: /rest/V1/orders
  2. To find the value of product_option.extension_attributes.custom_options, we will fetch the products and resolve the custom option labels from there:

/rest/V1/products?searchCriteria[filterGroups][0][filters][0][field]=entity_id&searchCriteria[filterGroups][0][filters][0][condition_type]=in&searchCriteria[filterGroups][0][filters][0][value]=${itemIds}

  1. Finally to find the value of product_option.extenstion_attributes.configurable_item_options, we will fetch the attributes by id, and resolve the attribute labels:

/rest/V1/products/attributes?searchCriteria[filterGroups][0][filters][0][field]=attribute_id&searchCriteria[filterGroups][0][filters][0][condition_type]=in&searchCriteria[filterGroups][0][filters][0][value]=${attributeIds}&fields=items[attribute_id,options,default_frontend_label]

Full example, with some optimisations, such as fetching the needed products and attributes at once (not something you can just copy and use, as it is dependent on some external helpers):

import fetch from 'node-fetch' import { path, uniq } from 'ramda' import checkStatus from '../../../../common/utils/checkStatus' import { getLastUpdate, setLastUpdate } from '../../utils/storage' import config from '../../config.json' import mapOrder from './mapOrder' const getOrdersUrl = async () => { const previousUpdate = await getLastUpdate() return `${config.magento.endpointAddress}/orders?searchCriteria[sortOrders][0][direction]=desc&searchCriteria[sortOrders][0][field]=updated_at&searchCriteria[filterGroups][0][filters][0][conditionType]=gteq&searchCriteria[filterGroups][0][filters][0][field]=updated_at&searchCriteria[filterGroups][0][filters][0][value]=${previousUpdate}` } const getItemsProductsUrl = items => { // Filter out items that have a parent id, as their parents are the configurable products which we want to work with, and not the children. // Filter out items that don't have product options, as we don't need to enrich them either. const itemIds = uniq( items .filter(item => !item.parent_item_Id && item.product_option) .map(item => +item.product_id) ).join(',') return `${config.magento.endpointAddress}/products?searchCriteria[filterGroups][0][filters][0][field]=entity_id&searchCriteria[filterGroups][0][filters][0][condition_type]=in&searchCriteria[filterGroups][0][filters][0][value]=${itemIds}` } const getItemAttributesUrl = options => { const attributeIds = uniq(options.map(option => +option.option_id)).join(',') return `${config.magento.endpointAddress}/products/attributes?searchCriteria[filterGroups][0][filters][0][field]=attribute_id&searchCriteria[filterGroups][0][filters][0][condition_type]=in&searchCriteria[filterGroups][0][filters][0][value]=${attributeIds}&fields=items[attribute_id,options,default_frontend_label]` } const getHeaders = () => { return { Authorization: `Bearer ${config.magento.accessToken}`, Accept: '*/*', 'Accept-Encoding': 'gzip, deflate, br', 'User-Agent': 'IDS/1.0' } } const innerFetch = url => { return fetch(url, { headers: getHeaders() }) .then(checkStatus) .then(res => res.json()) .catch(console.log.bind(console)) } const fetchOrderProducts = items => innerFetch(getItemsProductsUrl(items)) const fetchItemAttributes = options => innerFetch(getItemAttributesUrl(options)) const getCustomOptionsValues = (customOptions, foundProduct) => { return customOptions.map(option => { const productOption = foundProduct.options.find( productOption => +productOption.option_id === +option.option_id ) if (!productOption) { return option } const splitOptionValues = option.option_value .split(',') .map(innerValue => +innerValue.trim()) // We could have multiple selection under option_value const optionValueTitle = productOption.values .filter(value => splitOptionValues.includes(+value.option_type_id)) .map(foundOption => foundOption.title) .join(', ') return Object.assign({}, option, { option_title: productOption.title, option_value_title: optionValueTitle }) }) } const getConfiguratbleOptionsValues = (itemOptions, allAttributes) => { const attributes = allAttributes.items return itemOptions.map(option => { const foundAttribute = attributes.find( attribute => +attribute.attribute_id === +option.option_id ) if (!foundAttribute) { return option } const optionValueTitle = foundAttribute.options.find( attrOption => +attrOption.value === +option.option_value ).label return Object.assign({}, option, { option_title: foundAttribute.default_frontend_label, option_value_title: optionValueTitle }) }) } const addProductOptionValues = (order, allProducts, allAttributes) => { // Check if there are options to fetch if (order.items.every(item => !item.product_option)) { // Nothing to add here return order } const orderClone = Object.assign({}, order) orderClone.items = orderClone.items.map(item => { const foundProduct = allProducts.items.find( product => +product.id === +item.product_id ) const extensionAttributes = path( ['product_option', 'extension_attributes'], item ) if (!foundProduct || !extensionAttributes) { return item } if (extensionAttributes.custom_options) { extensionAttributes.custom_options = getCustomOptionsValues( extensionAttributes.custom_options, foundProduct ) } const itemOptions = extensionAttributes.configurable_item_options if (itemOptions) { extensionAttributes.configurable_item_options = getConfiguratbleOptionsValues( itemOptions, allAttributes ) } return item }) return orderClone } // Prefetch all products in one request as to not send too many requests to the server const prefetchAllProducts = async orders => { const allItems = orders.flatMap(order => order.items) if (!allItems.length) { return {} } return await fetchOrderProducts(allItems) } // Prefetch all configurable_item_options in one request as to not send too many requests to the server const prefetchAllItemAttributes = async orders => { const allAttributes = orders.flatMap(order => order.items .flatMap(item => path( [ 'product_option', 'extension_attributes', 'configurable_item_options' ], item ) ) .filter(x => x) ) if (!allAttributes.length) { return {} } return await fetchItemAttributes(allAttributes) } // Items that come from the order api don't include custom attributes labels // so we need to fetch full order items const addOrderItems = async response => { if (!response || !response.items) { return response } const orders = response.items if (!orders || !orders.length) { return response } const allProducts = await prefetchAllProducts(orders) const allAttributes = await prefetchAllItemAttributes(orders) return response.items.map(order => addProductOptionValues(order, allProducts, allAttributes) ) } export const fetchOrders = async () => { const url = await getOrdersUrl() await setLastUpdate() return innerFetch(url) .then(addOrderItems) .then(res => { return Array.isArray(res) && res.map(mapOrder) }) .catch(console.log.bind(console)) } export default fetchOrders 

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.