Generating EDI files with custom XML schemas pt. 1

With the growing popularity of the drop-shipping business model, you often find yourself in a situation when you need to export a number of orders in a particular format and send them to your drop-shipper. Out of the box, Magento offers the functionality you could use. It can generate data in two most universal formats: CSV and XML. But this may not be an optimal solution for you if you need the data to be collected and formatted in a specific way. And although a bunch of decent third-party tools for exporting orders exist, you may still not be able to find a suitable one.

In this article, we will create a feature that will help us to generate EDI files. EDI stands for Electronic Data Interchange but there isn’t a single standard that it follows. In our case, it’s going to be just a customized text/csv file.

As is so often the case with Magento we will need to create custom functionality. The solution I present in this article is based on custom XML schemas.

Let’s start with creating a skeleton of a module that we’ll call Magently_Dropshipping. If you are not sure how to create a module, refer to this post. The next step is to implement a mass action for orders.

Create a sales_order_grid.xml file in the view/adminhtml/uicomponent/ directory of your module with the following content:


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?xml version="1.0" encoding="UTF-8"?> <listing xmlns:xsi="" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd"> <listingToolbar name="listing_top"> <massaction name="listing_massaction"> <action name="generate_edi_file"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="type" xsi:type="string">generate_edi_file</item> <item name="label" xsi:type="string" translate="true">Generate EDI File</item> <item name="url" xsi:type="url" path="dropshipping/massprint/edi"/> </item> </argument> </action> </massaction> </listingToolbar> </listing>

The most important part here is the url. In order to make it work we need to add a route so that our module takes over handling requests coming to this URL. As we are going to use the feature only in the admin panel, let’s put the routes.xml file in adminhtml area directory:


1 2 3 4 5 6 7 8 9 <?xml version="1.0"?> <config xmlns:xsi="" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd"> <router id="admin"> <route id="dropshipping" frontName="dropshipping"> <module name="Magently_Dropshipping"/> </route> </router> </config>

The only thing missing now is the controller. Create one inside the Controller/Adminhtml/MassPrint directory of your module and call it Edi.php. It should extend Magento\Backend\App\Action. Put the following code as the body of the execute method:


1 2 3 4 5 6 7 8 9 10 11 12 $fileContent = [ 'type' => 'string', 'value' => '', /** TODO - create content */ 'rm' => true ]; return $this->fileFactory->create( $this->getFileName(), $fileContent, \Magento\Framework\App\Filesystem\DirectoryList::VAR_DIR, 'text/csv' );

We’ll take care of the value later. For now, the generated file only contains an empty string. For brevity’s sake, I’m going to skip over two things - injecting dependencies (like $this->fileFactory, which is an instance of Magento\Framework\App\Response\Http\FileFactory) and the implementation of getFileName method. It’s up to you whether you use a fixed file name or generate it on the fly. At this point, you should be able to select orders and download an EDI file by selecting Generate EDI File from the Actions dropdown.

Getting the orders to process

Now that we’re able to download the file, we need to fill it with actual content. The next logical step would be to get the orders that should be included in the file. In the controller, inject the following dependencies:


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 /** * @var \Magento\Sales\Api\OrderRepositoryInterface */ protected $orderRepository; /** * @var \Magento\Framework\Api\SearchCriteriaBuilder */ protected $searchCriteriaBuilder; /** * @var \Magento\Sales\Model\ResourceModel\Order\CollectionFactory */ private $orderCollectionFactory; /** * @var \Magento\Ui\Component\MassAction\Filter */ private $filter;

We are generating the file in the execute method and that’s where we need the orders. Add the following line at the beginning of the method:

$orders = $this->getOrders();

Next, we have to implement the getOrders method. Here is what it could look like:


1 2 3 4 5 6 7 8 private function getOrders(): ?OrderSearchResultInterface { $collection = $this->filter->getCollection($this->orderCollectionFactory->create()); $oderIds = $collection->getAllIds; $this->searchCriteriaBuilder->addFilter(OrderInterface::ENTITY_ID, $orderIds, 'in'); $searchCriteria = $this->searchCriteriaBuilder->create(); return $this->orderRepository->getList($searchCriteria); }

Now we don’t want to cram all our logic in the controller. Let’s create a separate class that will process the orders. Create a Magently\Dropshipping\ExportProcessor\Edi class and inject it as a dependency in our controller. Then modify the execute method of the controller as follows:


1 2 3 4 5 6 7 8 9 10 11 12 13 public function execute(): ResponseInterface { $orders = $this->getOrders(); $edi = $this->exportProcessor->execute($orders); /** TODO - implement execute */ $fileContent = [ 'type' => 'string', 'value' => $edi, 'rm' => true ]; (...) }

For this to work, we have to create the execute method inside the export processor. Let’s add it with the following implementation:


1 2 3 4 5 6 7 8 9 10 public function execute(OrderSearchResultInterface $orders): ?string { /** TODO - implement contentGeneratorFactory */ $contentGenerator = $this->contentGeneratorFactory->create(); foreach ($orders->getItems() as $order) { $orderData = $this->getOrderData($order); $contentGenerator->generate($orderData); /** TODO - implement generate */ } return $contentGenerator->getOutput(); /** TODO - implement getOutput */ }

There are two new elements there: $this->contentGeneratorFactory (I’ll return to it in Part 2.), and the getOrderData method. The latter is just for preprocessing the order data for ease of manipulation and returning it as an array. Its implementation isn’t that important, let’s just say it has the following signature:

private function getOrderData(OrderInterface $order): array

We have a framework of the feature with still a few things to implement, but let’s leave it for now and jump to those custom XML schemas.